Java垃圾回收(整理)

时间:2023-03-09 08:14:37
Java垃圾回收(整理)

Java垃圾回收

Garbage Collection:GC;

什么样的对象才是垃圾?怎样判断一个对象引用是不是垃圾?

垃圾回收算法:Mark-Sweep(标记-清除)算法,Copying(复制)算法,Mark-Compact(标记-整理)算法,分代收集算法Generational
Collection,分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域。一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young
Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。目前大部分垃圾收集器对于新生代都采取Copying算法,因为新生代中每次垃圾回收都要回收大部分对象,也就是说需要复制的操作次数较少,但是实际中并不是按照1:1的比例来划分新生代的空间的,一般来说是将新生代划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden空间和其中的一块Survivor空间,当进行回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor空间中,然后清理掉Eden和刚才使用过的Survivor空间。而由于老年代的特点是每次回收都只回收少量对象,一般使用的是Mark-Compact算法。注意,在堆区之外还有一个代就是永久代(Permanet
Generation),它用来存储class类、常量、方法描述等。对永久代的回收主要回收两部分内容:废弃常量和无用的类;

在评价算法时,我们要考虑许多方面:时间,cpu,内存,对程序编译和运行的影响等;

垃圾收集器:黑人问号???(不是很懂很了解)

新生代收集器使用的收集器:Serial、PraNew、Parallel Scavenge;

老年代收集器使用的收集器:Serial Old、Parallel Old、CMS;

Serial收集器(复制算法):新生代单线程收集器,标记和清理都是单线程,优点是简单高效;

Serial Old收集器(标记-整理算法):老年代单线程收集器,Serial收集器的老年代版本;

ParNew收集器(停止-复制算法):新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;

Parallel Scavenge收集器(停止-复制算法):并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%,
吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景;

Parallel Old收集器(停止-复制算法):Parallel
Scavenge收集器的老年代版本,并行收集器,吞吐量优先;

CMS(Concurrent Mark Sweep)收集器(标记-清理算法):高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu
追求高响应时间的选择;

内存分配:对象的内存分配,往大方向上讲就是在堆上分配,对象主要分配在新生代的Eden Space和From Space,少数情况下会直接分配在老年代。如果新生代的Eden
Space和From Space的空间不足,则会发起一次GC,如果进行了GC之后,Eden
Space和From Space能够容纳该对象就放在Eden Space和From
Space。在GC的过程中,会将Eden Space和From
 Space中的存活对象移动到To Space,然后将Eden Space和From
Space进行清理。如果在清理的过程中,To Space无法足够来存储某个对象,就会将该对象移动到老年代中。在进行了GC之后,使用的便是Eden
space和To Space了,下次GC时会将存活对象复制到From
Space,如此反复循环。当对象在Survivor区躲过一次GC的话,其对象年龄便会加1,默认情况下,如果对象年龄达到15岁,就会移动到老年代中。一般来说,大对象会被直接分配到老年代,所谓的大对象是指需要大量连续存储空间的对象,最常见的一种大对象就是大数组,比如:

byte[] data = new byte[4*1024*1024];

这种一般会直接在老年代分配存储空间。当然分配的规则并不是百分之百固定的,这要取决于当前使用的是哪种垃圾收集器组合和JVM的相关参数。

GC执行机制:由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GC和Full
GC;

Scavenge GC:一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Scavenge GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来;

Full GC:对整个堆进行整理,包括Young、Tenured和Perm。Full
GC因为需要对整个堆进行回收,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节;有如下原因可能导致Full
GC:

1.年老代(Tenured)被写满,

2.持久代(Perm)被写满,

3.System.gc()被显示调用,

4.上一次GC之后Heap的各域分配策略动态变化;

有了GC之后Java就不会出现内存问题吗?答案是否定的,很多情况都会出现:

1.静态集合类像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector等应用着;

2.各种连接,数据库连接,网络连接,IO连接等没有显示调用close关闭,不被GC回收导致内存泄露;

3.监听器的使用,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露;

未完待续,学到了再加上~~~~~~

@Arya0624