jvm学习-垃圾回收器(四)
查看当前机器所使用的垃圾回收器
jmap -heap 13297
jvm回收流程
jvm内存结构
Person p=new Person();
1.程序里面创建一个对象会向向eden区申请空间
2..当Eden区满的时候,会触发第一次Minor gc,把还活着的对象拷贝到Survivor From区;当Eden区再次出发Minor gc的时候,会扫描Eden区和From区,对两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域,并将Eden区和From区清空。
3.当后续Eden区又发生Minor gc的时候,会对Eden区和To区进行垃圾回收,存活的对象复制到From区,并将Eden区和To区清空
4.部分对象会在From区域和To区域中复制来复制去,如此交换15次(由JVM参数MaxTenuringThreshold决定,这个参数默认是15),最终如果还存活,就存入老年代。
4.后面每次回收纯活对象的年龄都会+1 当年龄到达15(默认)则移动到老年代(可通过-XX:MaxTenuringThreshold 调整)
合适的年龄设置,设置太大,又会导致大量真实的长期存货对象不能及时放到老年代,最终导致托管到永久代。设置太小,又会导致非长期存活对象直接托管到老年代
5.当老年代内存紧张则会触发(Full GC) 回收老年代垃圾对象
from区和to区容纳不了纯活对象会自动担保放到老年代,会照成本来纯活时间不长的对象 到了老年代。占用老年代内存 导致老年代内存增长过快 最终导致频繁fullgc 所以 新生代的内存也要根据合理调配。还有就是From和To的饱和度,比如设置过大,每次回收后到另外一个Survivor的存活的饱和度都很低会造成空间浪费 比如每次回收最大封装不超过100m,但是离我们设置的Survivor设置了500M造成了800M浪费
新生代中98%的对象都是"朝生夕死"的,所以并不需要按照1 : 1的比例来划分内存空间,而是将内存(新生代内存)分为一块较大的Eden(伊甸园)空间和两块较小的Survivor(幸存者)空间
jvm的几种垃圾回收器
serial收集器、parnew收集器、parallel scavenge收集器、serial old 收集器、parallel old收集器、cms收集器、g1收集器
如果两个收集器之间存在连线,则说明它们可以搭配使用。虚拟机所处的区域则表示它是属于新生代还是老年代收集器。
新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:CMS、Serial Old、Parallel Old
整堆收集器: G1
常用搭配组合
1、-XX:+UseSerialGC = Serial + Serial Old
默认情况下不会是这种选项,HotSpot虚拟机会根据计算及配置和JDK版本自动选择收集器
2.-XX:+UseParNewGC = ParNew + SerialOld
这个组合已经很少用(在某些版本中已经废弃) 注 设置年轻代为多线程收集。可与CMS收集同时使用。在serial基础上实现的多线程收集器。
3.-XX:+UseConcMarkSweepGC = ParNew + CMS + Serial Old
4.-XX:+UseParallelGC = Parallel Scavenge + Parallel Old (1.8默认)
5.-XX:+UseParallelOldGC = Parallel Scavenge + Parallel Old
UseParallelGC 与 UseParallelOldGC 的区别: +UseParallelGC = Parallel Scavenge + Parallel Old +UseParallelOldGC = Parallel Scavenge + Parallel Old -UseParallelOldGC = Parallel Scavenge + Serial Old
6.-XX:+UseG1GC = G1
查看JDK8默认使用的垃圾收集器
java +XX:+PrintCommandLineFlags -version
java -XX:+PrintGCDetails -version
并行,并发,吞吐量?
并行收集:指多条垃圾收集线程并行工作,但此时用户线程仍处于等待状态。
并发收集:指用户线程与垃圾收集线程同时工作(不一定是并行的可能会交替执行)。用户程序在继续运行,而垃圾收集程序运行在另一个CPU上。
吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值(吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间 ))。例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%
serial收集器
特点
最老的一种垃圾回收期 ,稳定,效率高
采用单线程回收
新生代采用复制算法 老年代采用标记压缩算法
当jvm垃圾回收时会"stop then world"短暂的暂停所有用户线程
适合用在client程序如:app 桌面程序
参数配置
-XX:+UseSerialGC 默认老年代也使用serial收集器 可以单独配置老年代收集器
ParNew
特点
ParNew收集器其实就是Serial收集器的多线程版本。
新生代并行回收 老年代串行回收
并行回收需要cpu多核支持
多核cpu的首选,如果单核使用此收集器 性能会低于serial收集器 是Server模式的默认收集器
ParNew收集器是许多运行在Server模式下的虚拟机中首选的新生代收集器,因为它是除了Serial收集器外,唯一一个能与CMS收集器配合工作的。
参数配置
-XX:+UseParNewGC 启用收集器
-XX:ParallelGCThreads设置回收线程数量,一般,最好与 CPU 数量相当,避免过多的线程数影响垃圾收集性能。在默认情况下,当 CPU 数量小于 8 个,ParallelGCThreads 的值等于 CPU 数量,大于 8 个,ParallelGCThreads 的值等于 3+[5*CPU_Count]/8]。以
流程图
ParallelScavenge收集器
特点
类似ParNew也是多线程回收
参数配置
流程图
Serial Old 收集器
特点
同样是单线程收集器,采用标记-整理算法。
主要也是使用在Client模式下的虚拟机中。也可在Server模式下使用
Parallel Old 收集器
是Parallel Scavenge收集器的老年代版本
特点
多线程,采用标记-整理算法。
注重高吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge+Parallel Old 收集器
流程图
CMS收集器
特点
老年代收集器
基于标记-清除算法实现。并发收集、低停顿。
CMS 收集器主要关注于系统停顿时间。CMS 是 Concurrent Mark Sweep 的缩写,意为并发标记清除,从名称上可以得知,它使用的是标记-清除算法,同时它又是一个使用多线程并发回收的垃圾收集器。
CMS 工作时,主要步骤有:初始标记、并发标记、重新标记、并发清除和并发重置。其中初始标记和重新标记是独占系统资源的,而并发标记、并发清除和并发重置是可以和用户线程一起执行的。因此,从整体上来说,CMS 收集不是独占式的,它可以在应用程序运行过程中进行垃圾回收。
- 初始标记:为了收集应用程序的对象引用需要暂停应用程序线程,该阶段完成后,应用程序线程再次启动。
- 并发标记:从第一阶段收集到的对象引用开始,遍历所有其他的对象引用。
- 重标记:由于第三阶段是并发的,对象引用可能会发生进一步改变。因此,应用程序线程会再一次被暂停以更新这些变化,并且在进行实际的清理之前确保一个正确的对象引用视图。这一阶段十分重要,因为必须避免收集到仍被引用的对象。
- 并发清理:所有不再被应用的对象将从堆里清除掉。
- 并发重置:收集器做一些收尾的工作,以便下一次GC周期能有一个干净的状态。
缺点
参数设置
-XX:ConcGCThreads:并发的GC线程数
-XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理,其目的是为了减少内存碎片 配合-XX:CMSFullGCsBeforeCompaction使用
-XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一次
-XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92%)
-XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整 设置了这个值表示是每次使用设定值
-XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,目的在于减少老年代对年轻代的引用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在标记阶段
-XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW的时间
-XX:+CMSParallelRemarkEnabled:在重新标记的时候并行执行(多线程执行),加快标记时间
流程图
G1收集器
一款面向服务端应用的垃圾收集器。
特点
并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿时间。部分收集器原本需要停顿Java线程来执行GC动作,G1收集器仍然可以通过并发的方式让Java程序继续运行。
分代收集:G1能够独自管理整个Java堆,并且采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。
空间整合:G1运作期间不会产生空间碎片,收集后能提供规整的可用内存。
可预测的停顿:G1除了追求低停顿外,还能建立可预测的停顿时间模型。能让使用者明确指定在一个长度为M毫秒的时间段内,消耗在垃圾收集上的时间不得超过N毫秒。
G1为什么能建立可预测的停顿时间模型?
因为它有计划的避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里面的垃圾堆积的大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。这样就保证了在有限的时间内可以获取尽可能高的收集效率。
G1与其他收集器的区别:
其他收集器的工作范围是整个新生代或者老年代、G1收集器的工作范围是整个Java堆。在使用G1收集器时,它将整个Java堆划分为多个大小相等的独立区域(Region)。虽然也保留了新生代、老年代的概念,但新生代和老年代不再是相互隔离的,他们都是一部分Region(不需要连续)的集合。
G1收集器存在的问题:
Region不可能是孤立的,分配在Region中的对象可以与Java堆中的任意对象发生引用关系。在采用可达性分析算法来判断对象是否存活时,得扫描整个Java堆才能保证准确性。其他收集器也存在这种问题(G1更加突出而已)。会导致Minor GC效率下降。
G1收集器是如何解决上述问题的?
采用Remembered Set来避免整堆扫描。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型进行写操作时,会产生一个Write Barrier暂时中断写操作,检查Reference引用对象是否处于多个Region中(即检查老年代中是否引用了新生代中的对象),如果是,便通过CardTable把相关引用信息记录到被引用对象所属的Region的Remembered Set中。当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆进行扫描也不会有遗漏。
如果不计算维护 Remembered Set 的操作,G1收集器大致可分为如下步骤:
初始标记:仅标记GC Roots能直接到的对象,并且修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建新对象。(需要线程停顿,但耗时很短。)
并发标记:从GC Roots开始对堆中对象进行可达性分析,找出存活对象。(耗时较长,但可与用户程序并发执行)
最终标记:为了修正在并发标记期间因用户程序执行而导致标记产生变化的那一部分标记记录。且对象的变化记录在线程Remembered Set Logs里面,把Remembered Set Logs里面的数据合并到Remembered Set中。(需要线程停顿,但可并行执行。)
筛选回收:对各个Region的回收价值和成本进行排序,根据用户所期望的GC停顿时间来制定回收计划。(可并发执行)
参数:
-XX:+UnlockExperimentalVMOptions –XX:+UseG1GC 启用G1收集器
-XX:MaxGCPauseMills=20,-XX:GCPauseIntervalMills=200 可以非常精确的控制停顿时间 停顿时间为20时垃圾收集不超过200
G1收集器运行示意图:
不同收集器性能测试
测试代码
static HashMap map = new HashMap(); public static void main(String[] args){ long begintime = System.currentTimeMillis(); for(int i=0;i<10000;i++){ if(map.size()*512/1024/1024>=400){ map.clear();//保护内存不溢出 System.out.println("clean map"); } byte[] b1; for(int j=0;j<100;j++){ b1 = new byte[512]; map.put(System.nanoTime(), b1);//不断消耗内存 } } long endtime = System.currentTimeMillis(); System.out.println("运行总耗时:"+(endtime-begintime)); }
serial测试结果
jvm参数:-Xmx512M -Xms512M -XX:+UseSerialGC
ParNew测试结果
jvm参数:-Xmx512M -Xms512M -XX:+UseParNewGC -XX:ParallelGCThreads=6 (按照我们上面设置线程数量原则 我的电脑是6核 所以我设置6)
ParallelScavenge
jvm参数:-Xmx512M -Xms512M -XX:+UseParallelOldGC -XX:ParallelGCThreads=6
jvm参数:-Xmx512M -Xms512M -XX:+UseParallelGC -XX:ParallelGCThreads=6
CMS
jvm参数:-Xmx512M -Xms512M -XX:+UseConcMarkSweepGC -XX:ParallelGCThreads=6
G1收集器
jvm参数:-Xmx512M -Xms512M -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC
注意:不能完全以上面的数据评估收集器好坏。因为每次执行结果都不一样 我只是放出第一次执行结果