JAVA-虚拟机-GC算法&回收器
Posted wanhua.wu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAVA-虚拟机-GC算法&回收器相关的知识,希望对你有一定的参考价值。
垃圾回收主要内容:
1. 那些内存需要回收?
2. 什么时候回收?
3. 如何回收?
垃圾回收主要针对运行时数据区那些区域?
运行时数据区的线程私有区域有:虚拟机栈,本地方法栈,程序计数器等;
栈中的栈帧随着方法的进入和退出执行入栈和出栈,每个栈帧的内存分配在编译期就已经确定;
随着线程或方法的结束,内存也随着回收;
运行时数据区的线程共享区域有:方法区,堆;
方法区和堆只有程序处于运行期才能确定创建那些对象,因此这部分内存分配和回收都是动态的;
垃圾回收的重点区域;
一,对象存活判断
1,引用计数算法
给对象添加一个引用计数器,被引用时计数器值+1,引用失效计数器值-1,当计数器值为0时对象不可能再被使用;
主流Java虚拟机未选用该算法管理内存(未解决对象之间相互循环引用的问题)
实现简单,判断效率高(应用:FlashPlayer,Python等)
2,可达性分析算法
将"GC Roots"对象作为起始节点,向下搜索,搜索走过的路径为引用链;当一个对象到GC Roots没有引用链时,则该对象是不可用的;
可作为"GC Roots"的对象:
【1】,方法区中静态属性引用的对象
【2】,方法区中常量引用的对象
【3】,虚拟机栈引用的对象 (栈帧中本地变量表)
【4】,本地方法栈中JNI引用的对象 (Native方法)
二,垃圾回收算法
1,标记-清除算法
定义:先标记要回收的对象,然后统一回收;
适用:存活对象较多的垃圾回收
缺点:
【1】,效率低; 标记和清除的过程效率不高;
【2】,空间问题; 标记清除后产生大量不连续的内存碎片,给大对象分配内存时没有足够连续的内存空间,导致提前出发垃圾回收动作。
2,复制算法
定义:将可用内存划分成相等大小两块,每次只使用其中一块,当这一块用完后将还存活的对象复制到另一块,
然后将已使用过的内存一次清理。
适用:存活对象较少的垃圾回收
优点:每次对整个半区进行内存回收,不用考虑内存碎片问题,只要移动堆顶指针,按顺序分配内存即可;
实现简单,运行高效
缺点:将内存缩小了一半
其他:
将新生代内存按照8:1:1分为Eden,From Survivor,To Survivor三个空间,每次使用Eden和From Survivor两个空间给对象分配内存,
当内存不足垃圾回收时,将存活对象复制到To Survivor空间,然后清理Eden和From Survivor空间;这样相当于内存指浪费了10%;
如果10%的To Survivor空间不够存放存活对象时需要老年代进行分配担保(将存活对象通过分配担保机制直接进入老年代)
3,标记-整理算法
定义:先标记要回收的对象,将存活对象移至一端,最后清理端边界以外的内存
4,分代收集算法
定义:根据对象存活周期将内存划分为新生代和老年代,然后根据每个年代的特点使用合适的回收算法;
如:新生代存活对象少可以采用复制算法; 老年代存活对象多并且没有分配担保必须使用标记清理或标记整理回收算法
三,垃圾回收器
1,Serial收集器
定义:单线程收集器,收集时必须暂停其他所有用户线程,直到收集结束。
适用:新生代
配置:
-XX:PretenureSizeThreshold
-XX:HandlePromotionFailure
其他:
【1】,单CPU环境Serial收集器没有现成交互开销,因此单线程的收集效率最高
【2】,对于Client模式下的桌面应用,分配给虚拟机的内存不会很大,对于一两百兆的新生代内存回收停顿时间完全控制在一百多毫秒以内,
停顿不频繁发生,Serial收集器是最好的选择;
【3】,收集过程会暂停服务(Stop the world)
2,ParNew收集器
定义:是Serial收集器的多线程版本
适用:新生代
配置:
-XX:PretenureSizeThreshold
-XX:HandlePromotionFailure
-XX:+UseConcMarkSweepGC (设置默认新生代收集器)
-XX:+UserParNewGC (指定ParNew作为新生代收集器)
-XX:ParallelGCThreads(限制垃圾收集的线程数)
其他:
【1】,与Serial收集器的控制参数,收集算法,Stop the world,对象分配规则,回收策略完全一样
【2】,是运行Server模式下虚拟机首选新生代收集器(唯一能和CMS收集器配合工作)
CMS是并发收集器,第一次实现让收集线程和用户线程同时工作;
CMS属老年代收集器,无法与Parallel Scavenge配合工作;
CMS关注回收的停顿时间(暂停用户线程时间),停顿时间越短越适合于用户交互的程序,因为有较高的响应速度
【3】,单CPU环境没有Serial收集器效率高
【4】,并行的多线程收集器
3,Parallel Scavenge收集器
定义:和ParNew收集器一样的收集器,区别于主要关注吞吐量的控制和GC自适应调节策略;
注: 吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间) ; 如果虚拟机总共运行100分钟,收集花费1分钟,则吞吐量为99%
适用:新生代; 较高的吞吐量,导致高效率的CPU利用率,主要适合后台运算
配置:
-XX:MaxGCPauseMillis (控制回收停顿的最大时间 ; 注:时间越小会牺牲吞吐量和新生代空间)
-XX:GCTimeRatio (设置吞吐量 0< X < 100 ; 垃圾回收时间占总时间的比例, 吞吐量的倒数)
如果将GCTimeRatio设置为19,最大GC时间占总时间的5%(即1/(1+19))
该值默认为99,则最大GC时间为1%
-XX:+UseAdaptiveSizePolicy (打开GC自适应调节策略)
GC自适应调节策略:虚拟机根据当前系统性能,自动调节参数已提供最合适的时间和最大吞吐量;
调节的参数包括:①,新生代大小(-Xmn),Eden和Survivor空间比例(-XX:SurvivorRatio)
②,晋升老年代对象的年龄(-XX:PretenureSizeThreshold)
... ...
GC自适应调节策略将内存管理交给虚拟机完成,只需要设置基本内存(-Xmx),停顿时间(MaxGCPauseMillis),吞吐量 (GCTimeRatio)等参数给虚拟机设立优化目标;
其他:
【1】并行:多条垃圾回收线程并行工作,单用户线程处于等待状态
并发:用户线程和回收线程同时进行
【2】,并行的多线程收集器
4,Serial Old收集器
定义:是Serial收集器的老年代版本的单线程收集器
适用:老年代(标记-整理算法); 主要给Client模式下的虚拟机使用;
其他:
如果在Server模式下主要有两大用途:①,在JDK1.5及之前的版本中与Parallel Scavenge收集器搭配使用
②,作为CMS收集器的后备预案(并发收集发生Concurrent Mode Failure时使用)
5,Parallel Old收集器
定义:是Parallel Scavenge收集器的老年代版本的多线程收集器
使用:老年代(标记-整理算法)
其他:
【1】,JDK1.6开始提供该收集器
6,CMS收集器(Concurrent Mark Sweep)
定义:CMS是一款并发收集,低停顿的收集器
关注目标:最短回收停顿时间
适用:互联网站,B/S系统服务端(较快的响应速度,最短的系统停顿时间)较好的用户体验
算法:标记-清除
回收步骤:
【1】初始标记(标记GC Roots能直接关联到的对象)(stop the world)
【2】并发标记(进行GC Roots Tracing的过程)
【3】重新标记(修正并发标记期间因用户程序继续运行而导致标记产生变动的对象的标记记录)(stop the world)
【4】并发清除
执行时间:
T(并发标记,并发清除) > T(重新标记) > T(初始标记)
由于耗时最长的并发标记和并发清除和用户线程一起工作,因此总体上CMS回收过程和用户线程一起并发执行的。
配置:
-XX:+UseConcMarkSweepGC 使用CMS收集器
-XX:ParallelCMSThreads 设定CMS的线程数量(一般情况约等于可用CPU数量)
缺点:
【1】对CPU资源非常敏感
并发阶段会占用部分线程(CPU资源)导致应用程序变慢,总吞吐量降低。
CMS默认回收线程数 = (CPU数量 + 3)/4; CPU数量↑回收线程↓; CPU数量=4时回收线程占用25%CPU资源;
CPU数量 < 4时,如CPU数量=2,则回收线程会占用一半CPU资源,导致用户程序执行速度直接降低50%,i-CMS收集器可以解决此问题;
增量式并发收集器(i-CMS): 并发标记和并发清理时让GC线程和用户线程交替运行,尽量减少GC线程独占资源的时间
【2】无法处理浮动垃圾,可能出现Concurrent Mode Failure失败而产生Full GC
并发清理时用户线程还在运行而不断产生的垃圾,由于在标记之后,CMS无法当次收集清理,只能等下次GC时清理,这部分垃圾称为“浮动垃圾”;
由于当次无法处理浮动垃圾,这些浮动垃圾又占有一定内存,又考虑要预留给用户线程足够的内存,因此让老年代提供预留空间;
因此CMS默认老年代使用68%时触发一次回收,百分比配置(-XX:CMSInitiatingOccupancyFraction)
如果CMSInitiatingOccupancyFraction参数设置过高,预留空间不足就会出现Concurrent Mode Failure失败,
此时虚拟机启动Serial Old收集器进行老年代垃圾回收停顿时间变长,性能降低。
【3】回收后空间碎片过多
因为CMS采用标记-清除算法因此回收后会产生大量空间碎片,无法给大对象分配连续内存空间而触发Full GC;
+UseCMSCompactAtFullCollection参数,用于在触发Full GC之前开启内存碎片整理过程,整理阶段不能并发,因此停顿时间加长。
-XX:CMSFullGCsBeforeCompaction参数,设置进行多少次不整理的Full GC之后,进行一次带整理的Full GC。
7,G1收集器
定义:一款面向服务端应用的垃圾收集器
算法:标记-整理
特点:
【1】并行与并发
充分利用多CPU,多核环境缩短Stop the World停顿时间,使用并发方式回收避免了GC时停顿java线程
【2】分代收集
【3】空间整合(使用了标记-整理回收算法,避免了大量空间内存碎片的产生)
【4】可预测的停顿(G1在追求低停顿同时建立了可预测的停顿时间模型,可以让使用者设置M毫秒内,GC所需要的时间不超过N毫秒)
原理:
1,G1将新生代和老年代分为大小相等的独立区域,进行全区域垃圾回收,新生代和老年代不再是物理隔离,都是部分独立区域的集合;
2,通过计算每个区域垃圾堆积的价值(回收可得到的空间/回收所需要的时间),然后根据价值大小有优先级地进行垃圾回收,保证了回收的效率;
3,虚拟机发现独立区域中的Reference类型数据进行写操作时,判断其他独立区域是否有Reference数据引用的对象(即老年代对象是否引用了新生代对象),
如果是就将引用信息通过CardTable记录到独立区域的Remembered Set中,在垃圾回收的时候将Remembered Set加入到GC根节点的枚举范围,可避免使用
可达性算法判断对象存活而进行的全堆扫描,也避免有存活对象的遗漏。
回收步骤:
【1】初始标记(标记GC Roots能直接关联到的对象,并修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确的Region中创建对象)(Stop the World)
【2】并发标记(从GC Roots开始对堆中对象可达性分析,找出存活对象)(和用户线程并发进行)
【3】最终标记(修正并发标记期间因用户线程运行的而产生变动对象的标记记录,虚拟机将这些对象记录到Remembered Set Logs中,然后合并到Rembered Set中)(Stop the World)
【4】筛选回收(对每个独立区域进行价值排序,根据用户期望的GC停顿时间制定回收计划)
四,回收器的使用
1,参数
UseSerialGC(使用Serial + Serial Old组合收集器)
UseParNewGC(使用ParNew + Serial Old组合收集器)
UseConcMarkSweepGC(使用ParNew + CMS + Serial Old组合收集器; Serial Old是CMS出现Concurrent Mode Failure失败后备用收集器)
UseParallelGC(使用Parallel Scavenge + Serial Old组合收集器)
UseParallelOldGC(使用Parallel Scavenge + Parallel Old组合回收器)
SurvivorRatio(新生代中Eden与Survivor容量比值; 默认是8,即Eden : Survivor = 8:1)
PretenureSizeThreshold(直接晋升到老年代对象的大小,超过此参数直接被分配在老年代)
MaxTenuringThreshold(晋升到老年代的对象年龄,每个对象Minor GC之后年龄+1, 超过此参数直接进入老年代)
UseAdaptiveSizePolicy(动态调整Java堆中个区域大小和进入老年代的年龄)
HandlePromotionFailure(是否允许担保分配失败,即老年代剩余空间不足应付新生代整个Eden和Survivor区所有对象都存活的极端情况)
ParallelGCThreads(设置并行GC回收时的线程数)
GCTimeRatio(GC时间占总时间比率,默认99,即允许1%的GC时间。 仅使用Parallel Scavenge收集器生效)
MaxGCPauseMillis(设置GC最大停顿时间。 仅使用Parallel Scavenge收集器生效)
CMSInitiatingOccupancyFraction(设置CMS在老年代空间被使用多少后触发一次GC, 默认68%, 仅使用CMS收集器生效)
UseCMSCompactAtFullCollection(设置CMS完成回收后是否要进行一次内存碎片整理, 仅使用CMS收集器生效)
CMSFullGCsBeforeCompaction(设置CMS在进行若干次回收后再启动一次内存碎片整理)
-XX:+PrintGCDetails(打印虚拟机日志)
2,回收器的组合使用
|
新生代
|
年老代
|
说明
|
组合1
|
Serial
|
Serial Old
|
都是单线程进行GC; GC时暂停所有应用线程
|
组合2
|
Serial
|
CMS+Serial Old
|
CMS是并发GC,和应用线程并发工作,不需要暂停所有应用线程
CMS出现Concurrent Mode Failure失败后使用Serial Old收集器
|
组合3
|
ParNew
|
CMS
|
用-XX:+UseParNewGC选项来开启
ParNew是Serial的并行版本,可以指定GC线程数,默认GC线程数为CPU的数量
使用-XX:ParallelGCThreads选项指定GC的线程数
用-XX:+UseConcMarkSweepGC选项,则新生代默认使用ParNew GC策略。
|
组合4
|
ParNew
|
Serial Old
|
用-XX:+UseParNewGC选项来开启
|
组合5
|
Parallel Scavenge
|
Serial Old
|
Parallel Scavenge策略主要是关注一个可控的吞吐量:应用程序运行时间 / (应用程序运行时间 + GC时间);
会使得CPU的利用率尽可能的高,适用于后台持久运行的应用程序,而不适用于交互较多的应用程序。
|
组合6
|
Parallel Scavenge
|
Parallel Old
|
Parallel Old是Serial Old的并行版本
|
组合7
|
G1
|
G1
|
-XX:+UnlockExperimentalVMOptions -XX:+UseG1GC 开启
-XX:MaxGCPauseMillis =50 暂停时间目标
-XX:GCPauseIntervalMillis =200 暂停间隔目标
-XX:+G1YoungGenSize=512m 年轻代大小
-XX:SurvivorRatio=6 新生代中Eden与Survivor容量比值
|
以上是关于JAVA-虚拟机-GC算法&回收器的主要内容,如果未能解决你的问题,请参考以下文章
Java 虚拟机原理垃圾回收算法 ( 设置 JVM 命令参数输出 GC 日志 | GC 日志输出示例 | GC 日志分析 )
Java虚拟机(JVM)-- GC概述 & GC四大算法之引用计数法
Java 虚拟机原理垃圾回收算法 ( Java 虚拟机内存分区 | 垃圾回收机制 | 引用计数器算法 | 引用计数循环引用弊端 )