主要内容:
- 垃圾回收算法
- HotSpot的算法实现
- 垃圾回收器
- 回收时间点
1.垃圾回收算法
1.1标记-清除算法:分为标记和清除两个阶段,首先标记需要回收的对象,标记完成后统一回收
缺点:
效率问题:标记和回收的效率都不高;
空间问题:会产生大量不连续的内存碎片
1.2复制回收算法:为了解决标记-清除算法内存碎片化的缺陷而被提出的算法。按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉如图:
缺点:可用内存被压缩到了原本的一半。内存利用率只有50%;
优点:这种算法虽然实现简单,内存效率高,不易产生碎片
1.3标记整理算法:结合了以上两个算法,为了避免缺陷而提出。标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象
2.HotSpot的算法实现
2.1分代回收算法:根据对象存活的不同生命周期将内存划分为不同的域,
- 新生代(YoungGeneration)(复制回收算法)
- 年老代(Tenured/OldGeneration)
2.2分代原因:对象的生命周期不同所以要分代
8:1:1的原因 在Eden区基本都会被回收(达到98%),回收不了的在放s0 同理再放入S1有效的增加了内存利用率
分配担保:当S0放不下的时候会放入老年代
新生代:老年生代 默认1:2
2.3新生代进入年老代条件
- 大对象直接进入老年代 -XX:PretenureSizeThreshold=314578=3M(设置参数大于3M属于大对象)
- 长期存活的对象将进入老年代、age大约15 --XX:MaxTenuringThreshold=15
- 动态对象年龄判定:Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半直接计入老年代
2.4空间分配担保(设置担保失败)
在发生Minor GC之前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果这个条件成立,那么Minor GC可以确保是安全的。如果不成立,则虚拟机会查看HandlePromotionFailure设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次Minor GC,尽管这次Minor GC是有风险的;如果小于,或者HandlePromotionFailure设置不允许冒险,那这时也要改为进行一次FullGC。
2.5什么样的对象需要被GC :
判断算法 :
引用计数法:这种方式的问题是无法解决循环引用的问题
可达性分析:。通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。
2.6什么可以成为GC Roots:
- 虚拟机栈中本地变量表引用的对象(在运行不能被回收)
- 方法区中:
1) 类静态变量引用的对象
2) 常量引用的对象
- 本地方法栈中JNI引用的对象 (在运行不能被回收)不可达是不是就一定会被回收
注意: 即使是不可达对象也不是必须回收:判定条件是否还需要执行finalize()方法,不能就销毁,执行了便可以成功与GC Roots的引用链链接
2.7引用类型:
- 强引用:New()的对象
- 软引用:内存不时回收(缓存)
- 弱引用:下次垃圾回收一定会回收
- 需引用:只能在对象被回收时的到一个通知
3.垃圾回收器
垃圾回收器种类图
注意:强大的CMS只能与Serial和ParNew组合
垃圾回收过程图
3.1用于新生代的回收器(新生代都是复制回收算法)
- Serial:单线程、用于Client模式、复制回收算法
- ParNew:多线程、多核情况下对资源利用率会提高(单核情况下由于线程切换没有serial快)、复制回收算法
- Parallel Scavenge:并行多线程、复制回收算法、吞吐量优先
吞吐量 = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间)
吞吐量:更有效的利用CPU资源(有利于人机交互效果)
3.2由于老年代
- Serial Old:标记整理(压缩)算法
- Parallel Old:标记整理(压缩)算法
- CMS:标记清除算法:
初始标记(只有CMS线程)、快、根(GC Roots)可以直接关联到的对象;
并发标记(和用户线程一起)、主要标记过程,标记全部对象;
重新标记(只有CMS线程)、由于并发标记时,用户线程依然运行,因此在正式清理前,再做修正、比初始标记时间长、比并发标记时间短;
并发清除(和用户线程一起)、清除不是整理,产生内存碎片;
优点:
并发收集,低停顿
缺点:
- CPU敏感
- GC线程占用CPU资源导致吞吐量下降(用户的执行速度降低)
- 无法处理浮动垃圾
- 因为它采用的是标记-清除算法。有可能有些垃圾在标记之后,需要等到下一次GC才会被回收。如果CMS运行期间无法满足程序需要,那么就会临时启用Serial Old收集器来重新进行老年代的回收。
- 产生大量的碎片
- 会出现老年代还有很大的空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次full G
- G1
年轻代
存活对象会被拷贝/移动到一个或多个“Survivor”区域,存活时间够长的直接移到“老年代”区域。这里会stop theworld,但young GC的过程是多线程执行的。
年老代:
初始标记:标记引用老年代里对象的Survivor区域,这些Survivor区域叫root区域 ;
扫描root区域:扫描survivor区域,找到引用老年代里内容的对象。young GC开始前要完成
并发标记:找出整个堆里可达的对象,计算各个区域的对象存活率。这个阶段可能会被年轻代的GC中断
重新标记Remark:对并发标记阶段的结果查漏补缺,使用snapshot-at-the-beginning(SATB)算法,比CMS使用的算法快很多;而且会回收空区域(没有存活对象的区域)
清理:先stop theworld,收集对象存活率最低的区域,然后清理Remembered Set。接着应用可以运行了,这时会重置空闲区域,并将这些区域返回给free list。年轻代和老年代同时回收
拷贝:将存活对象拷贝到没有使用的区域里
4.回收时间点
4.1安全点:程序执行时不是所有的地方都能停止下来开始GC,只有到达安全点才能暂停。
产生安全点的指令:
- 方法调用
- 循环跳转
- 异常跳转
- 设置GC主动中断
4.2安全域
垃圾回收安全点机制保证了程序执行的时候,在不太长的时间就会遇到可进入gc的安全点。但是如果线程处于sleep状态或者blocked状态的时候,这时线程无法响应jvm的中断请求,就需要安全区域。
安全区域是指在一段代码片段中,引用关系不会发生变化,在该区域的任何地方发生gc都是安全的。
当代码执行到安全区域时,首先标示自己已经进入了安全区域,那样如果在这段时间里jvm发起gc,就不用管标示自己在安全区域的那些线程了,在线程离开安全区域时,会检查系统是否正在执行gc,如果是那么就等到gc完成后再离开安全区域。
参考:《深入理解java虚拟机》第二版