GC-Roots、对象间的引用级别、分代垃圾回收

简介: 一、GC-Roots对象主要是在堆上分配的,我们可以把它想象成一个池子,对象不停地创建,后台的垃圾回收进程不断地清理不再使用的对象。当内存回收的速度,赶不上对象创建的速度,这个对象池子就会产生溢出,也就是我们常说的 OOM。把不再使用的对象及时地从堆空间清理出去,是避免 OOM 有效的方法。那 JVM 是如何判断哪些对象应该被清理,哪些对象需要被继续使用呢?

一、GC-Roots
对象主要是在堆上分配的,我们可以把它想象成一个池子,对象不停地创建,后台的垃圾回收进程不断地清理不再使用的对象。当内存回收的速度,赶不上对象创建的速度,这个对象池子就会产生溢出,也就是我们常说的 OOM。

把不再使用的对象及时地从堆空间清理出去,是避免 OOM 有效的方法。那 JVM 是如何判断哪些对象应该被清理,哪些对象需要被继续使用呢?

**这里首先强调一个概念,这对理解垃圾回收的过程非常有帮助

垃圾回收,并不是找到不再使用的对象,然后将这些对象清除掉。它的过程正好相反,JVM 会找到正在使用的对象,对这些使用的对象进行标记和追溯,然后一股脑地把剩下的对象判定为垃圾,进行清理。
**
了解了这个概念,我们就可以看下一些基本的衍生分析:

GC 的速度,和堆内存活对象的多少有关,与堆内所有对象的数量无关;

GC 的速度与堆的大小无关,32GB 的堆和 4GB 的堆,只要存活对象是一样的,垃圾回收速度也会差不多;

垃圾回收不必每次都把垃圾清理得干干净净,最重要的是不要把正在使用的对象判定为垃圾。

那么,如何找到这些存活对象,也就是哪些对象是正在被使用的,就成了问题的核心。

大家可以想一下写代码的时候,如果想要保证一个 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 进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。软引用和弱引用在堆内缓存系统中使用非常频繁,可以在内存紧张时优先被回收掉。

虚引用
是一种形同虚设的引用,在现实场景中用得不是很多。

三、分代垃圾回收
上面我们提到,垃圾回收的速度,是和存活的对象数量有关系的,如果这些对象太多,JVM 再做标记和追溯的时候,就会很慢。

一般情况下,JVM 在做这些事情的时候,都会停止业务线程的所有工作,进入 SafePoint 状态,这也就是我们通常说的 Stop the World。所以,现在的垃圾回收器,有一个主要目标,就是减少 STW 的时间。

其中一种有效的方式,就是采用分代垃圾回收,减少单次回收区域的大小。这是因为,大部分对象,可以分为两类:

大部分对象的生命周期都很短

其他对象则很可能会存活很长时间

这个假设我们称之为弱代假设(weak generational hypothesis)。

分代垃圾回收器会在逻辑上,把堆空间分为两部分:年轻代(Young generation)和老年代(Old generation)。

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)就会加一,当对象的年龄达到一定阈值,就会被移动到老年代中。

分配担保

如果年轻代的空间不足,又有新的对象需要分配空间,就需要依赖其他内存(这里是老年代)进行分配担保,对象将直接在老年代创建。

大对象直接在老年代分配

超出某个阈值大小的对象,将直接在老年代分配,可以通过 -XX:PretenureSizeThreshold 配置这个阈值。

动态对象年龄判定

有的垃圾回收算法,并不要求 age 必须达到 15 才能晋升到老年代,它会使用一些动态的计算方法。比如 G1,通过 TargetSurvivorRatio 这个参数,动态更改对象提升的阈值。

老年代的空间一般比较大,回收的时间更长,当老年代的空间被占满了,将发生老年代垃圾回收。

目前,被广泛使用的是 G1 垃圾回收器。G1 的目标是用来干掉 CMS 的,它同样有年轻代和老年代的概念。不过,G1 把整个堆切成了很多份,把每一份当作一个小目标,部分上目标很容易达成。

G1 也是有 Eden 区和 Survivor 区的概念的,只不过它们在内存上不是连续的,而是由一小份一小份组成的。G1 在进行垃圾回收的时候,将会根据最大停顿时间(MaxGCPauseMillis)设置的值,动态地选取部分小堆区进行垃圾回收。

G1 的配置非常简单,我们只需要配置三个参数,一般就可以获取优异的性能:

① MaxGCPauseMillis 设置最大停顿的预定目标,G1 垃圾回收器会自动调整,选取特定的小堆区;

② G1HeapRegionSize 设置小堆区的大小;

③ InitiatingHeapOccupancyPercent 当整个堆内存使用达到一定比例(默认是45%),并发标记阶段就会被启动。

最后注意,不要把JVM内存模型 跟Java 的内存模型(JMM,JMM 指的是与多线程协作相关的主存与工作内存方面的内容)搞混

目录
相关文章
|
6月前
|
存储 监控 算法
垃圾回收器、垃圾回收算法、空间分配担保、JVM调优、GC回收对象的过程
垃圾回收器、垃圾回收算法、空间分配担保、JVM调优、GC回收对象的过程
|
1月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
68 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
4月前
|
存储 缓存 监控
Java面试题:在Java中,对象何时可以被垃圾回收?编程中,如何更好地做好垃圾回收处理?
Java面试题:在Java中,对象何时可以被垃圾回收?编程中,如何更好地做好垃圾回收处理?
69 0
|
5月前
|
算法 Java
Java垃圾回收(Garbage Collection,GC)是Java虚拟机(JVM)的一种自动内存管理机制,用于在运行时自动回收不再使用的对象所占的内存空间
【6月更文挑战第18天】Java的GC自动回收内存,包括标记清除(产生碎片)、复制(效率低)、标记整理(兼顾连续性与效率)和分代收集(区分新生代和老年代,用不同算法优化)等策略。现代JVM通常采用分代收集,以平衡性能和内存利用率。
71 3
|
4月前
|
存储 监控 算法
Java面试题:解释分代垃圾回收策略,并说明其优势
Java面试题:解释分代垃圾回收策略,并说明其优势
51 0
|
5月前
|
监控 算法 Java
掌握Java内存管理:对象生命周期与垃圾回收机制
本文旨在为读者提供一次深入的探索之旅,穿越Java虚拟机(JVM)的迷宫,揭示对象从诞生到消亡的奥秘。我们将一起揭开内存分配、存活判定以及回收策略等概念背后的神秘面纱,通过案例分析与实践技巧,让读者能够更加高效地运用Java语言,优化程序性能。
|
6月前
|
存储 缓存 监控
JVM工作原理与实战(二十四):堆的垃圾回收-对象引用
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了强引用、软引用、弱引用、虚引用、终结器引用等内容。
77 0
|
6月前
|
算法 Java UED
【JVM】分代收集算法:提升Java垃圾回收效率
【JVM】分代收集算法:提升Java垃圾回收效率
110 0
|
6月前
|
算法 Java 程序员
Python内存管理:请解释Python中的引用计数机制以及如何处理循环引用。描述一下Python是如何通过垃圾回收来释放不再使用的对象内存的。
Python内存管理:请解释Python中的引用计数机制以及如何处理循环引用。描述一下Python是如何通过垃圾回收来释放不再使用的对象内存的。
109 1
|
存储 算法 安全
面试~jvm(JVM内存结构、类加载、双亲委派机制、对象分配,了解垃圾回收)
面试~jvm(JVM内存结构、类加载、双亲委派机制、对象分配,了解垃圾回收)
84 0