程序员必备:JVM核心知识点总结( 三 )

  • 垃圾回收不必每次都把垃圾清理得干干净净,最重要的是不要把正在使用的对象判定为垃圾 。
  • 那么,如何找到这些存活对象,也就是哪些对象是正在被使用的,就成了问题的核心 。
    大家可以想一下写代码的时候,如果想要保证一个 HashMap 能够被持续使用,可以把它声明成静态变量,这样就不会被垃圾回收器回收掉 。我们把这些正在使用的引用的入口,叫作GC Roots 。
    这种使用 tracing 方式寻找存活对象的方法,还有一个好听的名字,叫作可达性分析法 。
    概括来讲,GC Roots 包括:
    • Java 线程中,当前所有正在被调用的方法的引用类型参数、局部变量、临时值等 。也就是与我们栈帧相关的各种引用;
    • 所有当前被加载的 Java 类;
    • Java 类的引用类型静态变量;
    • 运行时常量池里的引用类型常量(String 或 Class 类型);
    • JVM 内部数据结构的一些引用,比如 sun.jvm.hotspot.memory.Universe 类;
    • 用于同步的监控对象,比如调用了对象的 wait() 方法;
    • JNI handles,包括 global handles 和 local handles 。
    对于这个知识点,不要死记硬背,可以对比着 JVM 内存区域划分那张图去看,入口大约有三个:线程、静态变量和 JNI 引用 。
    强、软、弱、虚引用那么,通过 GC Roots 能够追溯到的对象,就一定不会被垃圾回收吗?这要看情况 。
    Java 对象与对象之间的引用,存在着四种不同的引用级别,强度从高到低依次是:强引用、软引用、弱引用、虚引用 。
    • 强应用 默认的对象关系是强引用,也就是我们默认的对象创建方式 。这种引用属于最普通最强硬的一种存在,只有在和 GC Roots 断绝关系时,才会被消灭掉 。
    • 软引用 用于维护一些可有可无的对象 。在内存足够的时候,软引用对象不会被回收;只有在内存不足时,系统则会回收软引用对象;如果回收了软引用对象之后,仍然没有足够的内存,才会抛出内存溢出异常 。
    • 弱引用 级别就更低一些,当 JVM 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象 。软引用和弱引用在堆内缓存系统中使用非常频繁,可以在内存紧张时优先被回收掉 。
    • 虚引用 是一种形同虚设的引用,在现实场景中用得不是很多 。这里有一个冷门的知识点:Java 9.0 以后新加入了 Cleaner 类,用来替代 Object 类的 finalizer 方法,这就是虚引用的一种应用场景 。
    分代垃圾回收上面我们提到,垃圾回收的速度,是和存活的对象数量有关系的,如果这些对象太多,JVM 再做标记和追溯的时候,就会很慢 。
    一般情况下,JVM 在做这些事情的时候,都会停止业务线程的所有工作,进入 SafePoint 状态,这也就是我们通常说的 Stop the World 。所以,现在的垃圾回收器,有一个主要目标,就是减少 STW 的时间 。
    其中一种有效的方式,就是采用分代垃圾回收,减少单次回收区域的大小 。这是因为,大部分对象,可以分为两类:
    • 大部分对象的生命周期都很短
    • 其他对象则很可能会存活很长时间
    这个假设我们称之为弱代假设(weak generational hypothesis) 。
    如下图,分代垃圾回收器会在逻辑上,把堆空间分为两部分:年轻代(Young generation)和老年代(Old generation) 。
    程序员必备:JVM核心知识点总结

    文章插图
     
    1.年轻代年轻代中又分为一个伊甸园空间(Eden),两个幸存者空间(Survivor) 。对象会首先在年轻代中的 Eden 区进行分配,当 Eden 区分配满的时候,就会触发年轻代的 GC 。
    此时,存活的对象会被移动到其中一个 Survivor 分区(以下简称 from);年轻代再次发生垃圾回收,存活对象,包括 from 区中的存活对象,会被移动到 to 区 。所以,from 和 to 两个区域,总有一个是空的 。
    Eden、from、to 的默认比例是 8:1:1,所以只会造成 10% 的空间浪费 。这个比例,是由参数 -XX:SurvivorRatio 进行配置的(默认为 8) 。
    2.老年代对垃圾回收的优化,就是要让对象尽快在年轻代就回收掉,减少到老年代的对象 。那么对象是如何进入老年代的呢?它主要有以下四种方式 。
    • 正常提升(Promotion)
    上面提到了年轻代的垃圾回收,如果对象能够熬过年轻代垃圾回收,它的年龄(age)就会加一,当对象的年龄达到一定阈值,就会被移动到老年代中 。


    推荐阅读