本章节主要讲一下JVM的几种垃圾回收算法以及垃圾收集器。
众所周知,JVM中,虚拟机栈,本地方法栈,程序计数器都随线程的生而生,随着线程的死而死,实现了内存的自动整理。因此JVM的垃圾回收主要集中在堆和方法区中,其中堆是垃圾回收的重点。
一、对象判活
JVM回收的前提是对象不可用,不可用的对象还存在在内存中,堆积多了就需要清理,不然内存不够,因此判断对象可不可用是关键。
判断对象是否可用有两种方法:引用计数、可达性分析
引用计数:每个对象都有一个引用计数属性,当新增一个引用时,引用计数+1,释放一个引用时,引用计数-1。缺点是无法解决对象相互循环引用的问题。
可达性分析:从GC Roots向下搜索,搜索所走过的路径就是引用链,当一个对象到达GC Roots没有任何引用链时,其就是不可用的。
在java语言中,GC Roots:java虚拟机栈中引用的对象、本地方法中JNI引用的对象、方法区中常量引用的对象、方法区中类静态属性实体引用的对象。
二、垃圾回收算法
1、标记-清除算法
标记-清除算法,就跟其名字一样,分为两个部分:标记、清除。先标记需要回收的对象,然后统一清除。这个算法是垃圾回收算法最基本的算法,后面的算法都是根据这个算法的缺点进行改进的。
缺点:1.效率不高,标记和清除过程的效率都不高,需要标记处整个内存区域中要回收的对象,然后清除,效率不高;2.产生大量不连续的空间碎片,当出现一个需要内存比较大的对象时,由于无法找到足够大的连续的空间,就不得不提前出发一次垃圾回收行为。
2、复制算法
复制算法是将内存分为大小相等的两块空间,每次只使用其中一块,当使用的那块空间快满时,将存活的对象都复制到未使用的那块空间里,且严格按照内存地址依次排列,与此同时,GC线程将更新存活对象的内存引用地址指向新的内存地址,然后直接清除使用的那块空间。
缺点:内存的利用率不高,只使用了一半的内存,当存活率比较高时,发生垃圾回收的概率会增大
优点:不会产生大量不连续的内存碎片
3、标记-整理算法
标记-整理算法,又称为标记-压缩算法,是针对老年代提出来的,老年代存活率比较高,若使用复制算法,分出一半的空间来存放对象,很容易就占满一半的内存,会频繁出发垃圾回收行为。标记-整理算法是将内存中存活的对象有序的向内存的一端移动,移动结束后,然后清理掉边界以外的内存空间。
优点:1.不会产生大量不连续的空间碎片;2.内存利用率高
4、分代回收算法
其实该回收算法是有人针对上面三种算法而提出来的一种算法,根据堆的分代使用不同的算法,年轻代存活率比较低,发生回收的概率比较大,每次只要复制少部分存活的对象即可完成收集,因此推荐使用复制算法。而老年代存活率比较高,没有额外的空间作为担保,因此必须使用标记-整理或标记-清除算法。
三、垃圾收集器
1、Serial收集器
Serial收集器是最古老、最稳定、效率高的垃圾收集器,但是只会使用一个线程去回收,回收过程STW(Stop The World),会产生较长时间的停顿。无论是年轻代还是老年代都是串行,年轻代使用复制算法,老年代使用标记-整理算法。
参数控制:-XX:+UseSerialGC 串行收集器
2、Parallel收集器
Paralle收集器是Serial收集器的多线程版本。
2.1、Parallel New收集器
相对于Serial收集器而言,ParallelNew收集器是并行的年轻代的收集器,使用多线程去回收,回收过程STW(Stop The World),也会产生停顿时间。年轻代使用并行,老年代串行,年轻代使用复制算法,老年代使佣标记-整理算法。
参数控制:-XX:+UseParNewGC 串行收集器
-XX:ParallelGCthreads 限制线程的数量
2.2、Parallel Scavenge收集器
Parallel Scavenge收集器与Parallel New收集器类似,也是并行的,通过多线程去回收,但是这个收集器更注重系统的吞吐量。通过控制参数打开自适应调节策略,虚拟机动态根据当前程序运行的状态,动态调节已获得适合的停顿时间或者最大的吞吐量;或者通过参数控制GC时间的毫秒数或者GC时间的比例
年轻代串行,老年代并行,年轻代使用复制算法,老年代使用标记-整理算法
参数控制:-XX:+UseParallelGC 串行收集器
2.3、Parallel Old收集器
Parallel Old收集器是Parallel Scavenge收集器的老年代版本,是老年代使用的收集器。老年代使用标记-整理算法,且是并行的。
参数控制:-XX:+UseParOldGC 串行收集器
3、CMS(Concurrent Mark Sweep)收集器
CMS收集器是一种以获取最短回收停顿时间为目标的收集器,其使用的是标记-清除算法,其是老年代的垃圾收集器,其收集过程为初始标记,并发标记,重新标记,并发清除。
初始标记:简单标记一下GC Roots能直接关联到的对象,会STW(Stop The World)
并发标记:由前阶段标记过的对象出发,所有可到达的对象都在本阶段中标记。
重新标记:重新修改一下并发标记过程中因为程序运行标记发生变动的那部分标记记录,会STW(Stop The World),停顿时间比初始标记时间长,但是比并打标记的时间短。此阶段标记从新生代晋升的对象、新分配到老年代的对象以及在并发阶段被修改了的对象。
并发清除:并发清除标记的对象
优点:并发处理;停顿时间短
缺点:产生不连续的内存碎片;并发时降低系统的吞吐量
-XX:+UseConcMarkSweepGC
使用CMS收集器-XX:+ UseCMSCompactAtFullCollection
Full GC后,进行一次碎片整理;整理过程是独占的,会引起停顿时间变长-XX:+CMSFullGCsBeforeCompaction
设置进行几次Full GC后,进行一次碎片整理-XX:ParallelCMSThreads
设定CMS的线程数量(一般情况约等于可用CPU数量)
4、G1收集器
至于G1收集器,这里只是大致讲一下,本人对这个回收器了解的不太深,若想详细了解G1收集器,请看我转载的文章《》
G1收集器是hotSpot公司研发出来,希望代替CMS收集器的,其使用的是标记-整理算法。虽然G1被授予极大的期望,但是由于新技术的不确定性,目前使用的最多的还是CMS收集器。
与CMS收集器相比G1收集器有以下特点:
- 空间整合,G1收集器采用标记整理算法,不会产生内存空间碎片。分配大对象时不会因为无法找到连续空间而提前触发下一次GC。
- 可预测停顿,这是G1的另一大优势,降低停顿时间是G1和CMS的共同关注点,但G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为N毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。
上面提到的垃圾收集器,收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔阂了,它们都是一部分(可以不连续)Region的集合。