JVM 内存模型

堆分区

老年代新生代EdenFromSurvivorToSurvivorTenuredMetaspace

GC 发生在哪里?

JVM 内存区域中,程序计数器,JVM栈和本地方法栈时线程私有的,随着线程的创建而创建,销毁而销毁,每个栈帧分配多少内存都是已知的,因此这三个区域的内存分配和回收都具有确定性。

那么GC的重点就是方法区的内存,堆中是对象的回收,方法区是废弃常量和无用类的回收。

GC 过程理解

  1. Java 应用创建对象,通常分配在 Eden 区域,当其空间占用到达一定阈值,触发 Minor GC。依然被引用的对象存活下来,复制到 Survivor区域,没有被引用的对象被回收。
  2. 经过一次 Minor GC,Eden 空闲下来,直到再次触发 Minor GC。这时,另一个 Survivor 成为 to 区域,Eden 区域存活对象和 From 区域对象复制到 to 区域,并且存活年龄计数 +1.
  3. 第2步发生很多次,直到有对象年龄计数达到阈值,这时发生所谓的晋升(Promotion),超过阈值的对象晋升到老年代。
  4. 后面就是 老年代 GC,取决于选择的 GC 选项。

老年代 GC 叫做 Major GC,整个堆清理叫做 Full GC.

对象什么时候被回收?

当一个对象不在被引用,就代表改对象可以被回收。有两种算法可以判断对象是否可以被回收:

引用计数算法:每个对象有一个引用计数器,被引用+1,引用失效-1,引用=0代表可以被回收了。虽然该方法实现简单,但它存在对象间循环引用的问题。

可达性分析算法:GC Roots 是所有对象的根对象, JVM 加载时,会创建一些普通对象引用正常对象。这些对象作为正常对象的起始点,GC时,从 GC Roots 向下搜索,当一个对象到 GC Roots 没有任何引用链时,表示该对象可被回收了。HotSpot JVM采用的改算法。

强引用,软引用,弱引用,虚引用有什么区别?

不同的引用类型,区别在对象可达性状态和对GC的影响。

引用类型特点
强引用存在引用,永远不会被 GC
软引用通过 SoftReference 实现,当要 OOM 时才会回收
弱引用通过 WeakReference 实现,只要 GC 就会被回收
虚引用通过 PhantomReference 实现,唯一作用是被GC时受到一个系统通知
FinalReference<T>
Finalizer
FinalizerHistogram
PhantomReference<T>
Reference<T>
ReferenceQueue<T>
SoftReference<T>
WeakReference<T>

参考地址 https://mp.weixin.qq.com/s/8f29ZfGvZVPe0bO-FahokQ

如何回收对象?

JVM GC 遵循以下两个特性:

自动性:Java 提供了一个系统级线程来跟踪每一块分配出去的内存空间,当 JVM 处于空闲循环时,垃圾收集器线程会自动检查每一块分配出去的内存空间,然后自动回收每一块空闲的内存块。

不可预期性:对象回收的时期不确定。

GC 线程是 JVM 自动执行的,Java 程序无法自动执行。System.gc 只能建议进行 GC, 具体什么时候执行?仍然是不可预期。

GC 算法

JVM GCopen in new window

GC 算法 和具体 JVM 紧密相关,不同的JVM 提供的实现可能不一样。

常见的 GC 算法如下表:

标记-清除算法 Mark-Sweep

标记: 存活对象标记

清除: 回收不可达对象

图片

复制算法 Copying

对象创建区 复制到 空闲区,两个区交换

图片

标记-整理 Mark-Compact

标记: 存活对象标记

整理: 移动所有存活对象,按顺序排列

分代收集

组合回收, GC 主流回收算法,将不同声明周期的对象分配到堆中不同区域,采用不同的 GC 算法。

总结

GC 算法优点缺点适用场景
标记-清除(Mark-Sweep)不移动对象,简单高效效率低,内存碎片
复制(Copying)不产生内存碎片内存使用率低,频繁复制对象存活率低
标记-整理(Mark-Compact)综合前两种优点移动对象,成本较高对象存活率高
分代收集(Generational Collection)分区回收

不同内存区域的回收方式

年轻代

使用 Minor GC,采用复制算法。

  • Java 应用创建对象,通常分配在 Eden 区域,当其空间占用到达一定阈值,触发 Minor GC。依然被引用的对象存活下来,复制到 Survivor区域,没有被引用的对象被回收。
  • 经过一次 Minor GC,Eden 空闲下来,直到再次触发 Minor GC。这时,另一个 Survivor 成为 to 区域,Eden 区域存活对象和 From 区域对象复制到 to 区域,并且存活年龄计数 +1.
  • 第2步发生很多次,直到有对象年龄计数达到阈值,这时发生所谓的晋升(Promotion),超过阈值的对象晋升到老年代。

老年代

存放生命周期较长的对象,使用标记-清除算法或标记-整理算法。

回收器分类

回收器分类一图流:

连线为相互配合的GC

YoungOldSerialParNewParallel ScavengeSerial OldParallel OldCMSG1

年轻代垃圾回收器

回收器类型回收算法特点参数
Serial GC复制单线程, Client 模式默认回收器-XX:+UseSerialGC
ParNew GC复制新生代GC,Serial GC 的多线程版本 ,通常配合 老年带的 CMS GC 工作-XX:+UseConcMarkSweepGC -XX:+UseParNewGC
Parallel Scavenge GC复制多线程,高吞吐,JDK8 默认GC-xx:+UseParallelGC

老年代回收器

回收器类型回收算法特点参数
Serial Old GC标记-整理单线程-XX:+UseSerialGC
Parallel Old GC标记-整理JDK8 默认GC-xx: +UseParallelOldGC
CMS(Concueent Mark Sweep) GC标记-清除并发收集,低延迟,高吞吐,内存碎片-XX:+UseConcMarkSweepGC(默认会将-XX: +UseParNewGC打开)

G1GC

回收器类型回收算法特点参数
G1 GC标记-整理+复制高并发、低停顿、可预测停顿时长-XX:+UseG1GC -XX:MaxGCPauseMillis=200(最大停顿时间)

G1(Garbage First)采用了全新的分区算法。

JDK9 默认GC

查看默认GC

查看 JDK 默认GC配置

java -XX:+PrintCommandLineFlags -version

输出如下

-XX:InitialHeapSize=264301824 -XX:MaxHeapSize=4228829184 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGCation -XX:+UseParallelGC
java version "1.8.0_291"
Java(TM) SE Runtime Environment (build 1.8.0_291-b10)
Java HotSpot(TM) 64-Bit Server VM (build 25.291-b10, mixed mode)

可以看到 -XX:+UseParallelGC,JDK1.8默认采用的是ParallelGC(新生代),老年代默认就是Parallel old了。

参考文章

Last Updated:
Contributors: himcs