Java虚拟机知识点【GC】
一、垃圾收集算法
标记-清除算法
首先标记出所有需要回收的对象,然后统一回收所有被标记的对象。该算法的效率不高,而且存在内存碎片的问题。复制算法
将内存按容量划分为大小相等的两块,每次只使用其中一块进行内存分配,当这块内存用完了,就将还存活的对象全部复制到另一块内存,然后把使用过的内存空间一次清理掉。该算法能解决标记清除算法的效率问题。但是因为需要将内存分一半,代价更高。标记-整理算法
标记出所有需要回收的对象,让存活的对象向一端移动,然后直接清理掉端边界以外的内存。该算法能解决标记清除算法的内存碎片问题,以及复制算法在对象存活率高时,进行多次复制的效率变低的问题。分代收集算法
新生代中,每次垃圾收集时都有大批对象死去,只有少量存活,此时就得使用复制算法,这样只要付出少量存活对象的复制成本就可以完成收集;
老年代中,对象成活率高、没有额外空间对他进行分配担保,就得使用标记清理或标记整理算法;
分代收集算法将堆空间划分为年轻代yang与老年代old,年轻代又被分为eden区和survivor区,survivor区又被分为from区与to区。 默认按8:1划分eden区和survivor区。eden区是连续的内存空间,因此在eden区分配内存极快。hotspot虚拟机使用指针碰撞和tlab来加快eden区的内存分配,并保障线程安全。
分代收集算法的执行流程
- 新建的对象优先分配在eden区;
- 当eden区满了,就会触发minor gc,eden中的存活对象被移动到survivor0,eden被清空;
- 等eden区再满了,再次触发minor gc,eden和survivor0中的存活对象又会被复制到survivor1,s0和eden被清空,然后下一轮s0与s1交换角色,如此循环往复。
- 当两个survivor区切换了几次(hotspot虚拟机默认15次)之后,仍然存活的对象,将被复制到老年代。
minor gc:发生在新生代的gc,因为java对象都具备朝生夕灭的特性,所以minor gc非常频繁,一般回收速度也比较快。
major gc/full gc:发生在老年代年的gc,出现full gc经常伴随至少一次的minor gc(非绝对,如parallel scavenge)。full gc的速度一般会比minor gc慢10倍以上,所以要合理设置年轻代与老年代的大小,尽量减少full gc的操作。
minor gc的触发条件:
- 当eden区满时触发。
full gc的触发条件:
- 调用system.gc时,系统会建议执行full gc,但是不一定执行。
- 老年代空间不足时触发。
- 方法区(永久代/元空间)空间不足时触发。
- 通过minor gc后进入老年代的平均大小大于老年代的可用连续内存时触发。
- 由eden区、from space区向to space区复制时,对象大小大于to space可用内存,则把该对象转存到老年代,且老年代的可用连续内存小于该对象大小时触发。
二、内存分配和回收策略
对象优先在eden区分配
当eden区没有足够的内存空间进行分配时,虚拟机将发起
一次minor gc。大对象直接进入老年代
大量连续内存的java对象,比如很长的字符串以及数组,会被直接分配到老年代,因此写程序时应该尽量避免。长期存活的对象将进入老年代
当两个survivor区切换了几次(hotspot虚拟机默认15次)之后,仍然存活的对象,将被复制到老年代。动态对象年龄判定
如果s0空间中相同年龄所有对象大小的总和大于s0空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须达到阈值。空间分配担保
在发生minor gc之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么minor gc 可以确保是安全的。如果不成立,则虚拟机会查看handlepromotionfailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次minor gc,尽管这次minor gc是有风险的;如果小于,或者handlepromotionfailure设置不允许冒险,那这时也要改为进行一次full gc。
三、垃圾收集器
- serial:单线程的收集器。==复制算法==
- parnew:serial 收集器的多线程版本。==复制算法==
- parallel scavenge:类似parnew的收集器,其他收集器关注于尽可能缩短 stop the world 的时间, 而parallel 收集器更关注系统的吞吐量,支持自适应调节策略。==复制算法==
- serial old:serial 收集器的老年代版本。==标记整理算法==
- parallel old:parallel scavenge 收集器的老年代版本。==标记整理算法==
- cms:concurrent mark sweep 收集器是一种以获取最短回收停顿时间为目标的收集器。==标记清除算法==
cms 的运作过程
- 初始标记(initial mark):标记出老年代里面存活的对象,这些对象或者是从gc roots直接指向的,或者是被年轻代存活对象指向的。会导致 stw,速度最快。
- 并发标记(concurrent mark):从上个阶段找到的所有根节点开始遍历整个老年代,标记存活的对象。速度慢,但是是和程序并发执行的。
- 重新标记(final remark):由于之前的并发标记是并发过程,可能无法赶上应用程序的修改速度。所以需要重新标记来完成标记整个老生代存活对象的标记。会导致 stw,速度快。
- 并发清除(concurrent sweep):并发清除死亡的对象。速度慢,但是是和程序并发执行的。
cms 的整体流程
cms 的缺点
- 对cpu资源非常敏感。cms默认回收线程数是(cpu数量+3)/4。
- 无法处理浮动垃圾,可能出现“concurrent mode failure”失败而导致另一次full gc的产生。cms并发清理阶段用户线程还在运行,伴随程序运行自然有新的垃圾不断产生,这部分垃圾出现在标记过程之后,cms无法在当次收集中处理掉它们,只好留到下次gc再清理。这部分垃圾就是浮动垃圾。因为垃圾收集阶段的用户线程还要运行,所以cms不像其他收集器那样等老年代几乎填满了在收集,会预留一部分空间。-xx:cmsinitiatingoccupancyfraction可以设置触发的百分比。当预留的内存无法满足程序需要,就会出现出现“concurrent mode failure”失败,此时,虚拟机启动后备方案:临时启用serial old。
- 标记-清除算法的缺陷。易产生内存碎片。解决方法:通过参数配置,用于cms在full gc 时开启内存碎片的合并整理过程,内存整理过程无法并发,会导致stw时间变长,因此有另一个参数配置,用于设置执行多少次不压缩的full gc后,执行压缩会full gc。
7.g1:面向服务端应用。
g1 的特点
- 并发与并行:充分利用多cpu、多核环境的硬件优势,缩短stw。
- 分代收集:保留分代概念,能独立管理整个gc堆。
- 空间整理:基于“标记-整理“,局部(两个region)上看基于“复制”算法。所以不会产生内存空间碎片。
- 可预知的停顿:这是g1相对于cms的另一大优势,能让使用者明确指定一个长度为m毫秒的时间片段内,消耗在垃圾收集上的时间不得超过n毫秒。
g1 的运作过程
- 初始标记(initial mark):类似cms
- 并发标记(concurrent mark):类似cms
- 最终标记(remark):类似cms
- 筛选回收(cleanp):在此阶段将对象从一个或多个区域复制到单一区域,同时整理和释放内存。
8.zgc:jdk 11 引入的,号称具有更低延迟的垃圾收集器,利用有色指针、加载屏障等技术,将 stw 控制在一次,只做一次扫描就能实现垃圾收集。
四、垃圾收集器的组合方式
参数 | 功能 |
---|---|
-xx:+useconcmarksweepgc | 自动启用-xx:+useparnewgc |
-xx:+useparallelgc | 自动启用-xx:+useparalleloldgc。server模式下的默认值。 |
-xx:+useparalleloldgc | 自动启用-xx:+useparallelgc |
-xx:+useparnewgc | jdk8不能单独启用 |
-xx:+useserialgc | serial + serial old。client模式下的默认值。 |
-xx:+useg1gc | 使用g1垃圾收集器 |
参考资料:《深入理解java虚拟机(第二版)》、《java虚拟机规范(java se 8版)》、gc algorithms: implementations
下一篇: img的onload的另类用法