JVM GC知识笔记
文章目录
对象存活性
引用计数法
- 算法:每个对象有一个引用计数器,每一次对象被引用,计数器+1,引用失效时,计数器-1,计数器为0的对象即未被引用,可以回收
- 问题:循环依赖问题,循环依赖的对象计数器都不为0
objA.instance=objB; objB.instance=objA;
可达性分析算法(常用)
- 算法:以一系列称为“GC Roots”对象作为起始点,从这些节点开始往下搜索,形成引用链,当一个对象到GC Roots对象不存在引用链(连通)时,即认为该对象不可达,可以回收
- GC Roots包括:
- 栈帧的本地变量表中引用的对象
- 方法区中静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法中JNI(Native方法)引用的对象
引用
- 强引用(new Object()):不会被回收
- 软引用(SoftRefrence): 抛出内存异常之前回收,回收后内存不足才会继续抛出内存移除异常
- 弱引用(WeakRefrence):GC工作时,无论内存是否足够,都会被回收
- 虚引用(PhantomRefrence):无任何引用效果,也无法获取实例,唯一作用是在这个对象呗GC回收时获取一个事件通知
finalize方法
- 是否需要执行finalize?
- 判断对象有覆盖finalize方法
- JVM尚未调用过finalize() ,即finalize方法只会被调用一次
- finalize方法非回收时立即执行,而是回收时先标记是否执行,如需执行,入队列F-Queue,稍后执行
方法区回收(永久代)
- 废弃常量
- 无用的类
- 所有对象实例都被回收
- 加载该类的Classloader被回收
- 该类对应的java.land.Class对象没有被任何地方引用
内存回收算法
标记-清除算法(Mark-Sweep)
- 算法:首先标记所有要回收的对象,在标记完成后统一回收所有被标记的对象
- 问题:
- 效率问题:标记和清除的两个规程效率都不高
- 内存碎片问题:标记和清除会产生大量不连续的内存碎片
复制算法
- 算法:将可用内存按容量划分大小相等的两块,每次只使用其中一块,当这一块内存用完了,就将存活的对象复制到另一块去,然后将第一块的内存空间一次性清理掉
- 代价:可用内存缩小为实际内存的一半
- 解决了效率和复制问题,主流JVM都使用该算法来回收新生代(Eden区+Survivor区)
- 由于新生代对象存活率低,所以Eden区和Survivor区的大小不是1:1,可以Eden区更大,同时,为了Survivor区的回收效率,可以维护两个Survivor区
标记-整理算法
- 算法:首先标记所有要回收的对象,然后让所有存活的对象向内存的一端移动,将存活端边界外的对象全部清理
- 适合老年代的对象回收(对象存活率高)
算法实现
Stopword问题:在进行可达性分析时,需要将jvm中所有线程都停止下来,以使分析过程中,引用关系还在发生变化 - OopMap 1. 类加载时,会对象内什么偏移量上是什么对象类型计算出来 2. JIT编译时会记录栈和寄存器哪些位置是引用 3. 以上信息只在特定位置(安全点)记录,已缩小保存OopMap的内存消耗 4. 程序执行并非在所有地方都可以停顿下来GC,只有在到达安全点时才停止
内存收集器
Serial收集器
- 单线程
- 新生代:复制算法,Stop the world
- 老生代:标记-整理算法,Stop the world
- 适合Client模式
ParNew收集器
- Serial收集器的多线程版本
- 当老生代使用CMS时,新生代只能使用Parnew或Serial
Parallel Scavenge收集器
- 复制算法,多线程并行
- 新生代使用
- 关注吞吐量可控(吞吐量优先) -XX:MacGCPauseMillis:配置GC最大暂停时间 -XX:GCTimeRatio:配置GC时间占比
Serial Old收集器
- 单线程,使用标记-整理算法
- 与Parallel Scavenge搭配使用(默认)
- 作为CMS的后备选择,在并发收集失败(Concurrent Mode Failure)时使用
- -XX:+UseParallelGC:Parallel Scavenge+Serial Old (Server模式下默认)
Parallel Old收集器
- Parallel Scavenge的老生代版本
- 多线程,使用标记-整理算法
- -XX:+UseParallelOldGC: Parallel Scavenge+Parallel Old
CMS收集器
- 以获取最短停顿时间为目标的收集器
- 标记-清除算法
- 初始标记,Stop the world,只标识GC Roots能直接关联的对象
- 并发标记:GC Roots Tracing标记,无须Stop the world,耗时长
- 重新标记:Stop the world,修正并发标记期间程序继续运行而导致的标记变动
- 并发清除:并发清理过程中产生的垃圾不能在当次GC时被清理(浮动垃圾)
-XX:UseConcMarkSweepGC:ParNew+CMS+Serial Old(备选方案)
问题
- CPU消耗
- 浮动垃圾问题可能导致来不及释放,需要当老年代未被完全填满时就进行收集(jdk1.5默认68%,jdk1.6默认92%)
- 如果CMS回收后内存无法满足应用要求,会引发“Concurrent Mode Failure”,需要触发后备预案(Serial Old)
- 标记-清除算法产生的内存碎片问题
G1收集器
特点
- 并行与并发
- 空间整合
- 基于标记-整理算法,从两个region来看基于复制算法
- 分区分代收集:将java堆分为多个大小相等的Region,分代是在分区基础上的,新生代、老生代对应多个Region(不需要连续,代之间再是物理隔离)
- 可预测的停顿:G1跟踪每个region垃圾回收后的价值大小,维护一个优先列表,每次跟进允许的收集时间,优先回收价值最大的region(Garbage-First名称的来由)
回收步骤
- 初始标记(与CMS类似)
- 并发标记(与CMS类似)
- 最终标记(与CMS类似)
- 筛选回收:按成本和回收价值排序,根据用户期望的GC停顿时间来制定和执行回收计划
内存分配和回收策略
- 对象优先在Eden分配对象
大对象直接进入老年代:应该避免临时大对象的产生,以避免对象在Eden区和两个Servivor区之间发生大量的内存复制,-XX:PretenureSizeThreshold:设置大于这个值得对象直接进入老年代
长期存活的对象进入老年代
- 对象经过一次Minor GC(Eden区回收)后依然存活,并能被Survivor区容纳,则进入Survivor,此时对象的年龄为1,每次Minor GC年龄加1,当超过一定年龄(默认15,-XX:MaxTenuringThreshold控制)则进入老年代
- 并不是永远要等到对象年龄达到MaxTenuringThreshold才进入老年代,当Survivor空间中相同年龄对象大小的综合大于Survivor空间的一般,年龄大于或等于改年龄的对象就可以进入老年代
文章作者 justin huang
上次更新 2017-05-07