@lkxed
https://linux.cn/article-14742-1.html
This commit is contained in:
Xingyu Wang 2022-06-22 09:48:48 +08:00
parent 88e1693c08
commit 65a3845dd3

View File

@ -3,17 +3,16 @@
[#]: author: "Jayashree Huttanagoudar https://opensource.com/users/jayashree-huttanagoudar"
[#]: collector: "lkxed"
[#]: translator: "lkxed"
[#]: reviewer: " "
[#]: publisher: " "
[#]: url: " "
[#]: reviewer: "wxy"
[#]: publisher: "wxy"
[#]: url: "https://linux.cn/article-14742-1.html"
JVM 垃圾回收的工作原理
======
对于程序员来说,掌握 Java 的内存管理机制并不是必须的,但它能够帮助你更好地理解 JVM 是如何处理程序中的变量和类实例的。
![咖啡豆][1]
![](https://img.linux.net.cn/data/attachment/album/202206/22/094238qvh45pv2jtpde9td.jpg)
图源Pixabay. CC0.
> 对于程序员来说,掌握 Java 的内存管理机制并不是必须的,但它能够帮助你更好地理解 JVM 是如何处理程序中的变量和类实例的。
Java 之所以能够如此流行,自动 <ruby>垃圾回收<rt>Garbage Collection</rt></ruby>GC功不可没它也是 Java 最重要的几个特性之一。在这篇文章中我将说明为什么垃圾回收如此重要。本文的主要内容为自动的分代垃圾回收、JVM 划分内存的依据,以及 JVM 垃圾回收的工作原理。
@ -21,10 +20,10 @@ Java 之所以能够如此流行,自动 <ruby>垃圾回收<rt>Garbage Collecti
Java 程序的内存空间被划分为以下四个区域:
1. 堆区Heap:对象实例就是在这个区域分配的。不过,当我们声明一个对象时,堆中不会有任何内存分配发生,只是在栈中创建了一个对象的引用而已。
2. 栈区Stack:方法、局部变量和类的实例变量就是在这个区域分配的。
3. 代码区Code:这个区域存放了程序的字节码。
4. 静态区Static:这个区域存放了程序的静态数据和静态方法。
1. <ruby>堆区<rt>Heap</rt></ruby>:对象实例就是在这个区域分配的。不过,当我们声明一个对象时,堆中不会发生任何内存分配,只是在栈中创建了一个对象的引用而已。
2. <ruby>栈区<rt>Stack</rt></ruby>:方法、局部变量和类的实例变量就是在这个区域分配的。
3. <ruby>代码区<rt>Code</rt></ruby>:这个区域存放了程序的字节码。
4. <ruby>静态区<rt>Static</rt></ruby>:这个区域存放了程序的静态数据和静态方法。
### 什么是自动垃圾回收?
@ -34,13 +33,13 @@ Java 程序的内存空间被划分为以下四个区域:
垃圾回收的基本步骤如下:
#### 1. 标记已使用和未使用的对象
#### 1标记已使用和未使用的对象
在这一步骤中,已使用和未使用的对象会被分别做上标记。这是一个及其耗时的过程,因为需要扫描内存中的所有对象,才能够确定它们是否正在被使用。
![标记已使用和未使用的对象][2]
#### 2. 扫描/删除对象
#### 2扫描/删除对象
有两种不同的扫描和删除算法:
@ -62,56 +61,56 @@ Java 程序的内存空间被划分为以下四个区域:
为了提升垃圾回收中的“标记清除”的效率JVM 将对内存划分成以下三个“代”:
* 年轻代
* 老年代
* 永久代
* <ruby>新生代<rt>Young Generation</rt></ruby>
* <ruby>老年代<rt>Old Generation</rt></ruby>
* <ruby>永久代<rt>Permanent Generation</rt></ruby>
![Hotspot 堆内存结构][5]
下面我将介绍每个“代”及其主要特征。
#### 年轻
#### 新生
所有创建不久的对象都存放在这里。年轻代被进一步分为以下两个区域:
所有创建不久的对象都存放在这里。新生代被进一步分为以下两个区域:
1. 伊甸区Eden:所有新创建的对象都在此处分配内存。
2. 幸存者区Survivor分为 S0 和 S1:经历过一次垃圾回收后,仍然存活的对象会被移动到两个幸存者区中的一个。
1. <ruby>伊甸区<rt>Eden</rt></ruby>:所有新创建的对象都在此处分配内存。
2. <ruby>幸存者区<rt>Survivor</rt></ruby>,分为 S0 和 S1:经历过一次垃圾回收后,仍然存活的对象会被移动到两个幸存者区中的一个。
![对象分配][6]
年轻代发生的分代垃圾回收被称为 “Minor GC”。Minor GC 过程中的每个阶段都是“<ruby>停止世界<rt>Stop The World</rt></ruby>STW这会导致其他应用程序暂停运行直到垃圾回收结束。这也是 Minor GC 更快的原因。
新生代发生的分代垃圾回收被称为 “<ruby>次要回收<rt>Minor GC</rt></ruby>LCTT 译注:也称为“<ruby>新生代回收<rt>Young GC</rt></ruby>”)。Minor GC 过程中的每个阶段都是“<ruby>停止世界<rt>Stop The World</rt></ruby>STW这会导致其他应用程序暂停运行直到垃圾回收结束。这也是次要回收更快的原因。
一句话总结:伊甸区存放了所有新创建的对象,当它的可用空间被耗尽,第一次垃圾回收就会被触发。
![填充伊甸区][7]
Minor GC:在该垃圾回收过程中,所有存活和死亡的对象都会被做上标记。其中,存活对象会被移动到 S0 幸存者区。当所有存活对象都被移动到了 S0未被引用的对象就会被删除。
次要回收:在该垃圾回收过程中,所有存活和死亡的对象都会被做上标记。其中,存活对象会被移动到 S0 幸存者区。当所有存活对象都被移动到了 S0未被引用的对象就会被删除。
![拷贝被引用的对象][8]
S0 中的对象年龄为 1因为它们挺过了一次 Minor GC。此时,伊甸区和 S1 都是空的。
S0 中的对象年龄为 1因为它们挺过了一次次要回收。此时,伊甸区和 S1 都是空的。
每当完成清理后,伊甸区就会再次接受新的存活对象。随着时间的推移,伊甸区和 S0 中的某些对象被宣判死亡(不再被引用),并且伊甸区的可用空间也再次耗尽(填满了),那么 Minor GC 又将再次被触发。
每当完成清理后,伊甸区就会再次接受新的存活对象。随着时间的推移,伊甸区和 S0 中的某些对象被宣判死亡(不再被引用),并且伊甸区的可用空间也再次耗尽(填满了),那么次要回收 又将再次被触发。
![对象年龄增长][9]
这一次,伊甸区和 S0 中的死亡和存活的对象会被做上标记。其中,伊甸区的存活对象会被移动到 S1并且年龄增加至 1。S0 中的存活对象也会被移动到 S1并且年龄增加至 2因为它们挺过了两次 Minor GC。此时伊甸区和 S0 又是空的了。每次 Minor GC 之后,伊甸区和两个幸存者区中的一个都会是空的。
这一次,伊甸区和 S0 中的死亡和存活的对象会被做上标记。其中,伊甸区的存活对象会被移动到 S1并且年龄增加至 1。S0 中的存活对象也会被移动到 S1并且年龄增加至 2因为它们挺过了两次次要回收)。此时,伊甸区和 S0 又是空的了。每次次要回收之后,伊甸区和两个幸存者区中的一个都会是空的。
新对象总是在伊甸区被创建,周而复始。当下一次垃圾回收发生时,伊甸区和 S1 都会被清理,它们中的存活对象会被移动到 S0 区。每次 Minor GC 之后这两个幸存者区S0 和 S1就会交换一次。
新对象总是在伊甸区被创建,周而复始。当下一次垃圾回收发生时,伊甸区和 S1 都会被清理,它们中的存活对象会被移动到 S0 区。每次次要回收之后这两个幸存者区S0 和 S1就会交换一次。
![额外年龄增长][10]
这个过程会一直进行下去,直到某个存活对象的年龄达到了某个阈值,然后它就会被移动到一个叫做“老年代”的地方,这是通过一个叫做“晋升”的过程来完成的。
使用 `-Xmn` 选项可以设置年轻代的大小。
使用 `-Xmn` 选项可以设置新生代的大小。
### 老年代
这个区域存放着那些挺过了许多次 Minor GC,并且达到了某个年龄阈值的对象。
这个区域存放着那些挺过了许多次次要回收,并且达到了某个年龄阈值的对象。
![晋升][11]
在上面这个示例图表中,晋升的年龄阈值为 8。在老年代发生的垃圾回收被称为 “Major GC”。
在上面这个示例图表中,晋升的年龄阈值为 8。在老年代发生的垃圾回收被称为 “<ruby>主要回收<rt>Major GC</rt></ruby>”。LCTT 译注:也被称为“<ruby>全回收<rt>Full GC</rt></ruby>”)
使用 `-Xms``-Xmx` 选项可以分别设置堆内存大小的初始值和最大值。LCTT 译注:结合上面的 `-Xmn` 选项,就可以间接设置老年代的大小了。)
@ -123,13 +122,13 @@ S0 中的对象年龄为 1因为它们挺过了一次 Minor GC。此时
#### 元空间
Java 8 引入了元空间,并用它替换了永久代。这么做的好处是自动调整大小,避免了 <ruby>内存不足<rt>OutOfMemory</rt></ruby>OOM错误。
Java 8 引入了<ruby>元空间<rt>Metaspace</rt></ruby>,并用它替换了永久代。这么做的好处是自动调整大小,避免了 <ruby>内存不足<rt>OutOfMemory</rt></ruby>OOM错误。
### 总结
本文讨论了各种不同的 JVM 内存“代”,以及它们是如何在分代垃圾回收算法中起作用的。对于程序员来说,掌握 Java 的内存管理机制并不是必须的,但它能够帮助你更好地理解 JVM 处理程序中的变量和类实例的方式。这种理解使你能够规划和排除代码故障,并理解特定平台固有的潜在限制。
正文配图来自Jayashree HuttanagoudarCC BY-SA 4.0
*正文配图来自Jayashree HuttanagoudarCC BY-SA 4.0*
--------------------------------------------------------------------------------
@ -138,7 +137,7 @@ via: https://opensource.com/article/22/6/garbage-collection-java-virtual-machine
作者:[Jayashree Huttanagoudar][a]
选题:[lkxed][b]
译者:[lkxed](https://github.com/lkxed)
校对:[校对者ID](https://github.com/校对者ID)
校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出