目录

「004」 JVM 内存结构

https://leslie-cloud.oss-cn-beijing.aliyuncs.com/2021/03/826b5c76f045.png

定义

PC、Stack_VM 和 Stack_Local 都是线程隔离的,随着方法或线程的产生而产生,方法或线程的结束而结束,因此不需要过多考虑垃圾收集的问题。需要考虑的主要是方法区

垃圾

垃圾就是无效对象,当一个对象不被任何变量引用时,它就是无效对象。

判定无效对象的方法有:

引用计数法

为每个对象维护一个计数变量,每增加一个引用计数变量加一,每减少一个引用计数变量减一。对象被创建时引用计数变量为 1,当该引用消亡时计数变量变为 0,此时判定其为无效对象。

引用计数法无法解决对象循环引用的问题。

可达性分析法

从 GC Roots 开始建立一张有向图,根结点与对象之间存在一条可达路径的为有效对象,否则为无效对象。

GC Roots 可以为:

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

引用

在 Java/1.2 之前,对象只有两种状态:引用和未被引用,在垃圾收集时只需要做一件事情:挥手未被引用的对象。对于「当内存足够时,不回收该对象,否则回收该对象」的场景,就不能很好地进行描述了。

在 Java/1.2 之后,Java 对引用概念进行了扩充和修改,将引用分为了四种类型:强引用(Strong Reference),软引用(Soft Reference),弱引用(Weak Reference)和虚引用(Phantom Reference)。

强引用

强引用是最普遍的一种引用,创建对象并初始化就是强引用。

1
2
Object o = new Object();
o = null;

只要强引用存在,垃圾收集器就永远不会回收该对象。

软引用

软引用使用 SoftReference 进行包装,使用时用 get 方法获取。

1
2
SoftReference<Integer> integerSoftReference = new SoftReference(new Integer(3));
Integer x = integerSoftReference.get();

只有内存不足时,垃圾收集器才会回收软引用对象。

弱引用

弱引用使用 WeakReference 进行包装,使用时用 get 方法获取。

1
2
WeakReference<Integer> integerWeakReference = new WeakReference(new Integer(3));
Integer x = integerWeakReference.get();

只要发生 GC,垃圾收集器就会回收弱引用对象。

虚引用

虚引用使用 PhantomReference 进行包装,使用时用 get 方法获取。

1
2
PhantomReference<Integer> integerPhantomReference = new PhantomReference(new Integer(3));
Integer x = integerPhantomReference.get();

无法利用虚引用获取对一个对象的真实引用。

在垃圾收集时,若一个对象被虚引用,在回收前,Java 会将其加入与之关联的 ReferenceQueue 中,一般用于管理内存。

垃圾收集

回收堆中无效对象

一般来说,当被可达性分析判定为无效对象后,在下一次垃圾收集的过程中,该对象就会被回收,但也存在一些特例,那就是在 finalize 方法中重生。

回收无效对象时,先判定对象是否执行 finalize 方法,如果不执行或已执行过,该对象被回收。如果应当执行,对象将被放进一个优先度较低的队列中依次执行 finalize 方法,一旦方法中this 引用赋给了另外一个引用,对象将重生。

在一个对象的生命周期中,finalize 方法只执行一次。

回收方法区内存

方法区用于存放类信息、常量、静态变量等生命周期较长的数据,每次回收都会剩下大量存活的数据。方法区主要回收废弃常量无用的类

回收废弃常量

废弃常量是指没有被任何变量引用的常量。

1
2
String s = "Mike";
s = "Jack";	// "Mike" 即废弃常量

回收无用的类

无用的类是指:

  • 类的所有对象都被清除
  • 类的 ClassLoader 对象被回收
  • 该类的 java.lang.Class 对象没有被引用

此时该类应当被回收。

垃圾收集算法

三色标记

在遍历对象图的过程中,把访问都的对象按照"是否访问过"这个条件标记成以下三种颜色:

白色:表示对象尚未被垃圾收集器访问过。显然,在可达性分析刚刚开始的阶段,所有的对象都是白色的,若在分析结束的阶段,仍然是白色的对象,即代表不可达。

黑色:表示对象已经被垃圾收集器访问过,且这个对象的所有引用都已经扫描过。黑色的对象代表已经扫描过,它是安全存活的,如果有其它的对象引用指向了黑色对象,无须重新扫描一遍。黑色对象不可能直接(不经过灰色对象)指向某个白色对象。

灰色:表示对象已经被垃圾收集器访问过,但这个对象至少存在一个引用还没有被扫描过。

标记-清除算法

首先遍历所有的 GC Roots 元素,进行可达性分析,标记出存活对象(三色标记后的黑色对象),再对无效对象予以清除。

复制算法

常用于新生代。将新生代分为三部分:Eden、S0、S1(8:1:1),当有对象存入时将其置于 Eden 和其中一个 S 区域,在每次 Minor GC 后将存活的对象复制到另一个 S 区域。如果另一个 S 区域无法容纳所有的存活对象,将触发分配担保机制,存活的对象直接进入老年代。

标记-整理算法

常用于老年代。类似的,首先遍历所有的 GC Roots 元素,进行可达性分析,标记出**存活对象(**三色标记后的黑色对象),按照内存地址高低移动存活对象,然后再将末端内存地址之后的对象全部清除。

垃圾收集器

新生代垃圾收集器

https://leslie-cloud.oss-cn-beijing.aliyuncs.com/2021/03/436f444bf666.png

Serial & ParNew 垃圾收集器

Serial 和 ParNew 垃圾收集器区别在于前者使用单线程进行垃圾收集,后者使用多线程进行垃圾收集,二者在垃圾收集的过程中均需要暂停暂停应用程序线程。

Parallel Scavenge垃圾收集器

Parallel 和 ParNew 类似,同样利用多线程进行垃圾收集,区别在于 ParNew 追求低停顿,适合交互性应用,而 Parallel 追求高吞吐,适合后台计算。

老年代垃圾收集器

Serial Old & Parallel Old 垃圾收集器

分别对应着新生代的 Serial 和 Parallel Scavenge,区别在于垃圾回收策略不一样,新生代采用复制策略,老年代采用标记-清除策略。

CMS 垃圾收集器

https://leslie-cloud.oss-cn-beijing.aliyuncs.com/2021/03/1ac4f51fc631.png

CMS (Concurrent Mark Sweep)垃圾收集器追求低停顿,适合交互性应用,与其他垃圾收集器最大的区别在于,CMS 进行垃圾回收时用户线程和 CMS 线程并行运行,所以用户不会感觉到太大的卡顿。

运行过程如下:

  • 初始标记:用户线程停止,单线程标记出所有 GC Roots 结点
  • 并发标记:使用可达性分析法,多线程标记出所有废弃对象
  • 重新标记:用户线程停止,多线程标记出并发标记过程中新出现的废弃对象
  • 并发清除:单线程清除废弃对象
  • 并发重置:重置 CMS 垃圾收集器状态,等待下一次调用

内存分配

https://leslie-cloud.oss-cn-beijing.aliyuncs.com/2021/03/cc458fb79462.png

  1. 新对象优先分配在新生代的 Eden 区域;

  2. 大对象优先分配在老年代;

  3. 存活时间长的对象晋升到老年代;

    设置年龄字段,每次 Minor GC 后自动 +1,超过设定的阈值 MaxTenuringThreshold 后晋升

    动态年龄机制:当前新生代 GC 后存活对象中,相同年龄所有对象大小的总和大于 Survivor 空间的一半,年龄 >= 该年龄的对象就可以直接进入老年代,无须等到 MaxTenuringThreshold 中要求的年龄。

  4. 空间分配担保。

    由于新生代复制算法的存在,每次 Minor GC 前会检查老年代的最大连续空间大小,若老年代的最大连续空间大小大于新生代所有对象总大小或历次晋升对象平均大小,继续执行 Minor GC,否则,执行 Full GC。