JVM学习之旅(三)

写在前面的话

本章主要进行的是垃圾回收器与分配策略,之前一直听说的young GC和full GC这些问题迎刃而解,挺好的一章,学到不少

垃圾收集器与内存分配策略

概述

随线程而生而灭

  • 程序计数器
  • 虚拟机栈
  • 本地方法栈

垃圾回收

对象生存判断

引用计数法

方法

  • 给对象添加计数器
  • 每当有引用则+1,释放则-1

问题

不能解决循环引用

可达性分析算法

方法

  • 设置GC ROOTS作为起始点
  • 从这些节点向下的路径被称为引用链
  • 当一个对象到GC ROOTS没有任何引用链接,则对象不可用

GC ROOTS

  • 虚拟机栈
  • 方法区中类静态属性引用对象
  • 方法区中常量引用对象
  • 本地方法栈中引用的对象

再谈引用

JAVA对引用进行了扩充

  • 强引用:代码普遍存在,类似Object obj = new Object(),只要强引用存在,则永远不会GC
  • 软引用:描述一些还有用但并非必需的对象,SoftReference类;系统将要发生内存溢出异常之前,会把这些对象列近回收范围之中进行第二次回收
  • 弱引用:描述非必须对象,WeakReference;只能生存到下一次垃圾收集发生之前
  • 虚引用:最弱的引用关系,是否由虚引用完全不会对生存时间造成印象,也无法通过虚引用来取得一个对象实例。PhantomReference

生存还是死亡

两阶段死亡

  • 对象在可达性分析中无GC ROOT关联,被第一次标记
  • 当finalize()有必要执行,则将对象放入到F-Queue的队列中
  • GC对F-Queue队列进行标记,只要对象能构建起与GC ROOT的关联,就会被拯救

P.S. finalize只会被执行一次,只能被拯救一次

回收方法区

主要回收两部分

  • 废弃常量:与回收无用的对象很类似
  • 无用的类:
    • 该类所有实例都已经被回收
    • 加载该类的ClassLoader已经被回收
    • 该类对应的Java.lang.Class对象没有在任何地方被引用,无法再任何地方通过反射访问该类的方法

垃圾回收算法

标记-清除算法

方法

  • 标记:标记出所有需要回收的对象
  • 清除:统一回收所有被标记的对象

优劣

  • 效率:标记和清除的效率都不高
  • 空间:会产生大量不连续的内存碎片

复制算法

在标记清除的基础之上:

  • 将内存划分为大小相等的两块
  • 当一块使用完,就将另一块复制过去,再做清除

优劣

  • 实现简单,运行高效
  • 内存会缩小为原来的一半

现在商业化虚拟机时用这种算法来回收新生代

标记-整理算法

流程与标记清除一样,但是后续操作是让所有存活对象都向一端移动,然后清理掉端边界以外的内存

分代收集算法

根据对象存活的周期将内存划分为新生代和老年代,不同的代使用最适当的收集算法

  • 新生代:每次GC都会有大批对象死去,选用复制算法
  • 老年代:对象存活率高,必须使用标记-清理或者标记-整理

HotSpot算法实现

枚举根节点

  • GC停顿:这项分析工作必须在一个能确保一致性的快照中进行(不允许出现分析过程中对象引用关系还在变化,STOP-THE-WORLD)
  • 准确式GC:通过OopMap的数据结构知道哪些地方存放着对象引用

安全点

  • 只在特定的位置才会进行OopMap,被称为安全点,以程序是否具有让程序长时间执行的特征
  • 让所有线程都“跑”到最近的安全点上再停顿下来
    • 抢先式中断:先把所有线程中断,如发现不在安全点上,则唤醒线程让之执行到安全点上
    • 主动式中断:简单设置标志,所有线程执行时主动轮询标志,发现中断为真则自己中断线程

安全区域

解决程序不执行时的GC问题(Sleep或者Block)

  • 在一段代码片段中,引用关系不会发生变化,在这个区域的任何地方开始GC都是安全的

当线程执行到时,先标记自己进入Safe Region;离开安全区域前需要检查系统是否完成了GC,否则要等待或者收到可以离开信号

垃圾收集器

jvmgc

理解GC日志

1
33.125:[GC [DefNew: 3324K->152K(3712K), 0.0025925 secs] 3324K->152K(11904K), 0.0031680 secs]
  • 33.125: 代表GC发生的时间,JAVA虚拟机启动以来经过的秒数
  • [GC / Full GC:有Full代表发生了Stop-The-World
  • [DefNew:代表GC发生的区域
  • 3324k-152k(3712k):GC前该内存区域已使用容量->GC后该内存区域已使用容量(该内存区域总容量)
  • 3324K->152k(11904k):GC前JAVA堆已使用容量->GC后JAVA堆已使用容量(JAVA堆总容量)
  • 0.0025925 secs 该区域内存GC时间

内存分配与回收策略

对象有限在Eden分配

大多数情况下,对象将在Eden区进行分配,如果空间不过,则会进行一次Young gc

大对象直接进入老年代

虚拟机提供了一个 -XX:PretenureSizeThreshold参数,大于这个的对象则会进入老年代

长期存活对象进入老年代

通过-XX:MaxTenuringThreshold来设置,经历过一次young GC存活并且能被Survivor容纳,就会yi’dao移到Survivor空间,并且年龄+1

动态对象年龄判定

如果在Survivor空间中相同年龄的所有对象大小的综合大于Survivor空间的一半,则年龄大于或等于该年龄对象就可以直接进入老年代

空间分配担保

  • 老年代最大可用连续空间是否大于新生代所有对象总空间
  • 大于则young GC是安全的
  • 小于则查看HandlePromotionFailure设置值是否允许失败
  • 如果允许,则查看最大连续空间是否大于晋升平均大小
  • 如果大于,ze则尝试young GC
  • 否则,进行Full GC