写点什么

第九周作业

用户头像
文智
关注
发布于: 2020 年 11 月 23 日

JVM 垃圾回收原理

JVM 内存模型

在 JVM 内存模型中,所有应用程序创建的对象大部分存放在堆中(方法区中也存储一些静态变量和全局常量),随着程序运行,创建的对象越来越多,而这些对象中,大部分都可能不会在被引用,如果这些对象不被回收,内存很快就会被耗尽,程序也将崩溃。

对象存活分析

一个对象是否需要被回收,首先需要判断对象的存货状态,也就是一个对象是否还被引用或关联,常见方法有引用计数法和可达性分析算法。

引用计数法在对象头中维护对象的引用数,引用数为 0 时说明该对象可以被回收。Java 中允许相互依赖,这种情况下,假设 A、B 相互引用,即使没有其他任何类引用 A 或 B,A、B 的引用数也是 1,从而 A、B 永远不会被回收,这就造成了内存泄漏。因此 Java 中没有采用这种方式,而是采用另一种算法,即可达性分析算法。

可达性分析算法

通过一系列的称为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连(用图论的话来说,就是从 GC Roots 到这个对象不可达)时,则证明此对象是不可用的。

可以作为 GC Roots 的对象

  1. 当前正在执行的方法里的局部变量和输入参数

  2. 活动线程(Active threads)

  3. 方法区中的类静态属性引用的对象和常量引用的对象

  4. JNI 引用

回收方法

  • 清除算法

将垃圾对象占据的内存清理掉,其实 JVM 并不会真的将这些垃圾内存进行清理,而

是将这些垃圾对象占用的内存空间标记为空闲,记录在一个空闲列表里,当应用程序需要创

建新对象的时候,就从空闲列表中找一段空闲内存分配给这个新对象。

  • 整理算法(压缩算法)

从堆空间的头部开始,将存活的对象拷贝放在一段连续的内存空间中,那么其余的空

间就是连续的空闲空间。

  • 复制算法

将堆空间分成两部分,只在其中一部分创建对象,当这个部分空间用完的时候,将标

记过的可用对象复制到另一个空间中。

分代垃圾回收

JVM 的分代垃圾回收算法,主要用到以上的压缩和复制算法,具体回收过程如下:

  1. 大对象初始化时直接进入老年代区,其他对象进入新生代的 Eden 区;

  2. Eden 区满时,系统会执行 Yong GC,Eden 区的存活对象被复制进存活区 S0,Eden 区被清空;下一轮 Yong GC 时,如果 S0 也满了,Eden 区和 S0 的存活对象被复制进存活区 S1,Eden 区和 S0 被清空;如此反复;

  3. 新生代中多轮 Yong GC 未被回收的对象会被转移至老年代,当老年代也没有空间时,就会执行 Full GC,即新生代和老年代同时垃圾回收;

垃圾回收器算法

串行垃圾回收器(SerialGC)

串行 GC 对年轻代使用 mark-copy(标记-复制) 算法,对老年代使用 mark-sweep-compact(标记-清除-整理)算法。串行 GC 是单线程的垃圾收集器,不能进行并行处理,所以都会触发全线暂停(STW),停止所有的应用线程。因此这种 GC 算法不能充分利用多核 CPU。不管有多少 CPU 内核,JVM 在垃圾收集时都只能使用单个核心。

并行垃圾回收器(Parallel GC)

年轻代和老年代的垃圾回收都会触发 STW 事件。在年轻代使用标记-复制(mark-copy)算法,在老年代使用标记-清除-整理(mark-sweepcompact)算法。

并行垃圾收集器适用于多核服务器,主要目标是增加吞吐量。因为对系统资源的有效使用,能达到更高的吞吐量:

  • 在 GC 期间,所有 CPU 内核都在并行清理垃圾,所以总暂停时间更短;

  • 在两次 GC 周期的间隔期,没有 GC 线程在运行,不会消耗任何系统资源。

并发回收器(CMS GC)

其对年轻代采用并行 STW 方式的 mark-copy (标记-复制)算法,对老年代主要使用并发 marksweep

(标记-清除)算法。

CMS GC 的设计目标是避免在老年代垃圾收集时出现长时间的卡顿,主要通过两种手段来达成此

目标:

  1. 不对老年代进行整理,而是使用空闲列表(free-lists)来管理内存空间的回收。

  2. 在 mark-and-sweep (标记-清除) 阶段的大部分工作和应用线程一起并发执行。

也就是说,在这些阶段并没有明显的应用线程暂停。但值得注意的是,它仍然和应用线程争抢

CPU 时间。默认情况下,CMS 使用的并发线程数等于 CPU 核心数的 1/4。

如果服务器是多核 CPU,并且主要调优目标是降低 GC 停顿导致的系统延迟,那么使用 CMS 是

个很明智的选择。进行老年代的并发回收时,可能会伴随着多次年轻代的 minor GC。

G1 回收器(G1 GC)

G1 的全称是 Garbage-First,意为垃圾优先,哪一块的垃圾最多就优先清理它。

G1 GC 最主要的设计目标是:将 STW 停顿的时间和分布,变成可预期且可配置的。事实上,G1 GC 是一款软实时垃圾收集器,可以为其设置某项特定的性能指标。为了达成可预期停顿时间的指标,G1 GC 有一些独特的实现。

首先,堆不再分成年轻代和老年代,而是划分为多个(通常是 2048 个)可以存放对象的小块堆区域(smaller heap regions)。每个小块,可能一会被定义成 Eden 区,一会被指定为 Survivor 区或者 Old 区。在逻辑上,所有的 Eden 区和 Survivor 区合起来就是年轻代,所有的 Old 区拼在一起那就是老年代。这样划分之后,使得 G1 不必每次都去收集整个堆空间,而是以增量的方式来进行处理: 每次只处理一部分内存块,称为此次 GC 的回收集(collection set)。每次 GC 暂停都会收集所有年轻代的内存块,但一般只包含部分老年代的内存块。G1 在并发阶段估算每个小堆块存活对象的总数。构建回收集的原则是:垃圾最多的小块会被优先收集。这也是 G1 名称的由来。

G1 GC 的另一个特点是可以通过 MaxGCPauseMillis 参数控制暂停时间。

垃圾回收器的选择

Java1.8 之前,默认使用串行 GC,1.8 及以后默认使用并行 GC。

不同 GC 比较

  • 串行 GC 暂停时间最长,而且暂停时间随堆内存增大而增大,因为当内存可用空间较大时,GC 触发的频率会降低,但每次需要回收的对象会增大,因为分配的内存越大,每次需要回收的对象数越多;

  • 并行 GC 默认 GC 线程数为系统核心数,暂停时间较串行 GC 短,暂停时间也会随堆内存增大而增大;

  • G1GC 暂停时间最短,而且可以通过 MaxGCPauseMillis 人为控制;

选择 GC 方式

  • 可用堆内存较小时,不同 GC 方式的差别不大,可以尝试不同 GC 方式,查看 GC 消耗时间,依据应用需求选择,如对吞吐量要求更高,可以选择并行 GC,如果对暂停时间要求比较高而并行 GC 无法达到要求,则选用 G1GC 并通过 MaxGCPauseMillis 控制暂停时间

  • 堆内存较大时,并行 GC 暂停时间会增大,可以优先考虑 G1GC


发布于: 2020 年 11 月 23 日阅读数: 30
用户头像

文智

关注

还未添加个人签名 2018.11.29 加入

还未添加个人简介

评论

发布
暂无评论
第九周作业