对象已死吗
引用计数法
- 给对象添加一个引用计数器,每当其他地方引用它,计数值加1;引用失效,计数值减1
- 无法解决对象之间相互循环引用的问题
可达性分析算法
- 通过一系列“GC Roots”的对象作为起始点,向下搜索,搜索路径称为“引用链”。如果一个对象到GC Roots没有任何引用链相连时(GC Roots到这个对象不可达),则对象不可用
- GC Roots对象:
- 虚拟机栈中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈JNI引用的对象
引用
- 强引用:程序中普遍存在,类似“Object oj = new Object()”,GC永远不会回收被强引用的对象
- 软引用:有用但非必需。在将要发生内存溢出异常前,GC会将这些对象列入回收范围内进行第二次回收;如果仍然没有足够内存,才会抛出内存溢出异常
- 弱引用:非必需对象。只能生存到下次垃圾收集发生之前。GC会回收掉被弱引用关联的对象
- 虚引用:对其生存时间完全不构成影响;在这个对象被回收时会收到一个系统通知
生存还是死亡(finalize())
- 对于可达性分析算法中不可达的对象,会被第一次标记并进行筛选,即执行finalize()方法;如果对象没有覆盖finalize()方法或者finalize()已经被虚拟机调用,则“没有必要执行”
- 有必要执行finalize()的对象加入F-Queue队列,并有JVM自动建立的低优先级的Finalizer线程执行finalize()方法
- 如果对象在finalize()成功拯救自己——与引用链上的任意对象建立关联,则在第二次标记是移出“即将回收”的集合
- finalize()方法只能调用一次
回收方法区
- 非必要:永生代的垃圾收集“性价比”低
- 回收内容
- 废弃常量:没有任何对象引用常量池中的该常量
- 无用的类
- 该类的所有实例都被回收
- 加载该类的ClassLoader被回收
- 该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法
垃圾收集算法
标记-清除算法(Mark-Sweep)
- 过程:标记要回收的对象,在标记完成后统一回收被标记的对象
- 不足
- 效率:标记和清除效率都很低
- 空间问题:产生大量不连续的内存碎片
复制算法
- 过程:将内存分为大小相等的两块,当用完一块内存后,将存活的对象复制到另一块上去,把用过的内存空间一次清理掉
- 优点:高效;缺点:内存缩小一半,代价高
- 一般通过这种算法回收新生代:
- 将内存分为1个较大的Eden空间和2个较小的Survivor空间,每次使用Eden和其中一个Survivor
- 回收时,将Eden和当前使用的Survivor中所有存活的对象复制到另一个Survivor空间上,然后清理使用过的两块区域
标记-整理算法
- 标记要回收的对象,将所有存活的对象向一段移动,清理掉端边界以外的内存
- 老年代垃圾收集机制
分代收集算法
- 新生代:复制算法
- 老生代:标记-整理或标记-清理算法
HotSpot的算法实现
枚举根节点
- 存在的问题:
- 方法区一般很大,逐个检查其中的引用,时间开销大
- GC停顿:为了保证一致性(在整个可达性分析过程中执行系统看起来像被冻结在某个时间点上),会导致GC进行时必须停顿所有Java执行线程(Stop the World)
- 虚拟机可以直接得知那些地方存放着对象引用:HotSpot中通过一组OopMap的数据结构实现
- 在类加载完成的时候,HotSpot就把对象内每个偏移量上的数据类型计算出来,GC在扫描时可以直接得知这些信息
安全点
- 问题:导致OopMap内容变化的指令很多,如果条指令都生成一个OopMap,需要大量的额外空间,导致GC的空间成本高
- 在“特定的位置”记录这些信息,即安全点
- 安全点的选择:
- 是否具有程序长时间执行的特征,如指令序列服用(方法调用、循环跳转、异常跳转等)
- GC发生时让所有线程都“跑”到最近的安全点上
- 抢先试中断:GC发生时,首先所有线程中断,将不在安全点上的线程恢复,让它跑到安全点上(基本不采用这种方法)
- 主动式中断:GC需要中断时,设置一个标志,各个线程执行时主动轮询这个标志,如果中断标志为真就自己中断挂起。标志点和安全点重合
安全区域
- 当线程处于Sleep或者Blocked状态,无法响应JVM的中断请求,即无法“走”到安全的地方中断挂起
- 安全区域:在一段代码之中,引用关系不会发生变化——扩展的安全点
- 当线程执行到Safe Region中的代码时,标识自己进入该区域,这样GC过程中就不用管这类线程;线程离开安全区域时,如果已完成根节点枚举,则继续执行,否则等待可以离开 Safe Region的信号
垃圾收集器
CMS收集器(Concurrent Mark Sweep)
- 获取最短回收停顿时间为目标的收集器
- 步骤
- 初始标记(停顿):GC Roots直接关联到的对象
- 并发标记:GC Roots Tracing
- 重新标记(停顿):修正并发标记期间内程序继续运行而导致标记变化的那部分对象的标记记录
- 并发清除
内存分配与回收策略
对象优先在Eden分配
Tip:
Minor GC:发生在新生代的垃圾收集懂
Major GC/Full GC:发生在老年代的GC,往往伴随至少一次Minor GC,比Minor GC慢10倍以上
大对象直接进入老年代
-XX:PretenureSizeThreshold
参数:大于这个参数的直接进入老年代
长期存活的对象将进入老年代
-XX:MaxTenuringThreshold
:设置年龄阈值(默认15)
动态对象年龄判定
- 如果Survivor空间中相同年龄所有对象的大小总和大于Survivor空间的一半,年龄大于或等于该年龄的对象直接进入老年代,不必等到MaxTenuringThreshold要求达到年龄
空间分配担保
- 在Minor GC前,虚拟机会先检查老年代最大可用的连续空间是否大于新生代所有对象总空间:如果成立,则Minor GC安全;如果不成立,如果HandlePromotionFailure为True,则检查老年代最大可用连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,则尝试有风险的Minor GC;否则进行Full GC