垃圾收集在 Java 中是如何工作的?
我已经阅读了很多关于 Java 垃圾收集的文章,其中一些太复杂而难以理解,其中一些没有包含足够的信息来理解 Java 中的垃圾收集。 我们可以用简单的话将其称为关于垃圾收集的文章,这将很容易理解并且具有足够的信息来理解垃圾收集在 Java 中的工作原理。 垃圾收集通过使用多种 GC 算法(如 Mark 和 Sweep、G1 等)来工作。Java 中有不同种类的垃圾收集器可以收集堆内存的不同区域,就像 Java 中的串行、并行和并发垃圾收集器一样。
JDK 1.7 中还引入了一个名为 G1(垃圾优先)的新收集器。 了解 GC 的第一步是了解对象何时有资格进行垃圾回收?
由于 JVM 提供内存管理,Java 开发人员只关心创建一个对象,他们不关心清理,这是由垃圾收集器完成的,但它只能收集没有实时强引用或无法从任何对象访问的对象。
如果一个对象应该被收集但由于无意的强引用而仍然存在于内存中,那么它在 Java 中被称为内存泄漏。 Java Web 应用程序中的 ThreadLocal 变量很容易导致内存泄漏。
1. Java 垃圾回收的要点
在继续之前让我们回顾一下关于 Java 垃圾收集的几个要点。
- 在 Java 中,对象是在堆上创建的,而不管它们的作用域,如局部变量或成员变量。 而值得注意的是,类变量或静态成员是在Java内存空间的方法区中创建的,堆和方法区都是不同线程共享的。
- 垃圾收集是Java虚拟机提供的一种机制,用于从符合垃圾收集条件的对象中回收堆空间。
- 垃圾收集使 Java 程序员从内存管理中解放出来,而内存管理是 C++ 编程的重要组成部分,并让他们有更多时间专注于业务逻辑。
- Java中的垃圾收集由一个叫做垃圾收集器的守护线程来承载。
- 在从内存中删除一个对象之前,垃圾收集线程调用该对象的 finalize() 方法,并提供执行所需的任何类型清理的机会。
- 作为Java程序员,你不能在Java中强制垃圾回收; 它只会在 JVM 认为它需要基于 Java 堆大小的垃圾收集时触发。
-
System.gc()
和Runtime.gc()
等方法用于向 JVM 发送垃圾回收请求,但不能保证垃圾回收一定会发生。 -
如果Heap中没有创建新对象的内存空间Java虚拟机抛出OutOfMemoryError或
java.lang.OutOfMemoryError
堆空间 - J2SE 5(Java 2 Standard Edition) 添加了一个称为 Ergonomics 的新特性 ergonomics 的目标是通过最少的命令行调优从 JVM 提供良好的性能。
2. 对象什么时候可以进行垃圾收集?
如果对象无法从任何活动线程或任何静态引用访问,则该对象有资格进行垃圾收集或 GC。 换句话说,我们可以说如果所有引用都为空,则对象有资格进行垃圾回收。 循环依赖不计入引用,因此如果对象 A 具有对对象 B 的引用并且对象 B 具有对对象 A 的引用并且它们没有任何其他实时引用,则对象 A 和 B 都将符合垃圾收集的条件。
通常,在以下情况下,一个对象有资格在 Java 中进行垃圾回收:
-
对该对象的所有引用都显式设置为空,例如
object = null
- 该对象是在一个块内创建的,一旦控制退出该块,引用就会超出范围。
- 如果一个对象持有对另一个对象的引用,并且当我们将容器对象的引用设置为 null 时,父对象设置为 null,子对象或包含的对象自动符合垃圾收集条件。
- 如果一个对象仅通过 WeakHashMap 存在弱引用,它将有资格进行垃圾收集。
3. Java 中用于垃圾收集的堆生成
Java对象是在Heap中创建的,为了Java中的垃圾回收,Heap分为三个部分,分别称为Young generation,Tenured或Old Generation,以及heap的Perm Area。 New Generation 进一步分为 Eden 空间、Survivor 1 和 Survivor 2 空间三个部分。
当一个对象首次在堆中创建时,它会在 Eden 空间内的新一代中创建,并且在随后的次要垃圾回收之后,如果一个对象存活下来,它会被移动到幸存者 1,然后移动到幸存者 2,然后主要垃圾收集将该对象移动到老年代或永久代 .
永久生成堆或堆的永久区域有些特殊,它用于存储与 JVM 中的类和方法相关的元数据,它还托管 JVM 提供的字符串池,如我们之前字符串文章中所讨论的,为什么字符串在 Java 中是不可变的。
关于 Java 中的垃圾收集是否发生在 Java 堆的 perm 区域有很多意见,据我所知,这是依赖于 JVM 的事情,至少在 Sun 的 JVM 实现中发生。 我们也可以通过创建数百万个 String 并观察垃圾收集或 OutOfMemoryError 来尝试此操作。
4. Java 中垃圾收集器的类型? 并发与串行垃圾收集器
Java Runtime (J2SE 5) 在 Java 中提供了多种类型的垃圾回收,我们可以根据应用程序的性能需求进行选择。 Java 5 添加了三个额外的垃圾收集器,串行垃圾收集器除外。 每个都是一个世代垃圾收集器,已被实施以增加应用程序的吞吐量或减少垃圾收集暂停时间。
-
Throughput Garbage Collector:这个Java中的垃圾收集器使用了并行版本的新生代收集器。 如果 -XX:+UseParallelGC 选项通过 JVM 命令行选项传递给运行时,则使用它。 tenured generation 收集器与串行收集器相同。
-
Concurrent low pause Collector:如果在命令行传递-Xingc或-XX:+UseConcMarkSweepGC,则使用该Collector。 这也称为并发标记清除垃圾收集器。 并发收集器用于收集老年代,并在应用程序执行的同时进行大部分收集。 应用程序在收集期间会暂停一小段时间。
年轻代复制收集器的并行版本与并发收集器一起使用。 Concurrent Mark Sweep Garbage collector是java中使用最广泛的垃圾收集器,它使用一种算法在垃圾收集触发时首先标记需要收集的对象。
-
增量(有时称为训练)低暂停收集器:仅当在命令行上传递
-XX:+UseTrainGC
时才使用此收集器。 这个垃圾收集器自 java 1.4.2 以来没有改变,目前没有在积极开发中。 未来的版本将不支持它,因此请避免使用它,请参阅 1.4.2 GC 调整文档以获取有关此收集器的信息。
需要注意的重要一点是 -XX:+UseParallelGC
不应与 -XX:+UseConcMarkSweepGC
一起使用。 从 1.4.2 版本开始的 J2SE 平台中的参数解析应该只允许垃圾收集器的命令行选项的合法组合,但早期版本可能无法发现或检测所有非法组合,并且非法组合的结果是不可预测的。 不建议在 java 中使用这个垃圾收集器。
5. Java中垃圾收集的JVM参数
垃圾收集调整是一项长期的工作,需要大量的应用程序分析和耐心才能正确完成。我们需要通过分析和查找导致完全 GC 的原因来提高 Java 应用程序的性能,我发现垃圾收集调整在很大程度上取决于应用程序配置文件, 应用程序有什么样的对象,它们的平均寿命是多少等等。
例如,如果一个应用程序有太多短暂的对象,那么让 Eden 空间足够宽或更大将减少次要集合的数量。 我们还可以使用 JVM 参数控制年轻代和老年代的大小,例如设置 -XX:NewRatio=3
意味着年轻代和老年代的比例为 1:3,我们在调整这一代的大小时必须小心。
使新生代变大将减小老年代的大小,这将迫使 Major 收集更频繁地发生,这会在这段时间内暂停应用程序线程,从而导致吞吐量下降或减少。 参数 NewSize 和 MaxNewSize 用于从下方和上方指定年轻代的大小。 将这些设置为彼此相等可以修复年轻一代。
在我看来,在进行垃圾收集调整之前,必须详细了解 Java 中垃圾收集的工作原理。
6. Java 中的 Full GC 和并发垃圾回收
Java 中的并发垃圾收集器使用单个垃圾收集器线程,该线程与应用程序线程并发运行,目的是在老年代变满之前完成收集。 在正常操作中,并发垃圾收集器能够在应用程序线程仍在运行的情况下完成其大部分工作,因此应用程序线程只会看到短暂的暂停。
作为回退,如果并发垃圾收集器无法在老年代填满之前完成,则应用程序将暂停并在所有应用程序线程停止的情况下完成收集。
这种在应用程序停止的情况下进行的收集称为完全垃圾收集或完全 GC,并且是需要对并发收集参数进行一些调整的标志。
始终尽量避免或尽量减少完全垃圾收集或 Full GC,因为它会影响 Java 应用程序的性能。
7.Java垃圾收集总结
-
为了垃圾回收,Java Heap分为三代。 这些是年轻一代,终身或老一代,以及彼尔姆地区。
-
新对象由年轻代创建,随后移动到老年代。
-
字符串池在 Heap 的 PermGen 区域创建,垃圾收集可以发生在 perm 空间但取决于 JVM 到 JVM。 顺便说一下,从JDK 1.7 更新开始,String 池被移到了创建对象的堆区。
-
次要垃圾收集用于将对象从 Eden 空间移动到幸存者 1 和幸存者 2 空间,主要收集用于将对象从年轻代移动到老年代。
-
每当发生主要垃圾收集时,应用程序线程都会在此期间停止,这将降低应用程序的性能和吞吐量。
-
在 Java 6 的垃圾收集中应用的性能改进很少,我们通常使用 JRE 1.6.20 来运行我们的应用程序。
-
JVM 命令行选项 -Xmx 和 -Xms 用于设置Java 堆的起始大小和最大大小。 根据我的经验,此参数的理想比例是 1:1 或 1:1.5,例如,您可以将 –Xmx 和 –Xms 都设置为 1GB,或者将 –Xms 设置为 1.2 GB 和 1.8 GB。
-
在 Java 中没有手动进行垃圾收集的方法,但是我们可以使用各种引用类,如 WeakReference 或 SoftReference 来协助垃圾收集器。
这就是关于 Java 中的垃圾收集的全部内容。 在本篇文章中,我们将学习如何将堆划分为不同的区域,如 Eden、survivor spaces 和 perm gen space。 当没有强引用指向某个对象或无法从任何线程访问该对象时,该对象就有资格进行垃圾回收。
当垃圾收集器意识到需要进行垃圾收集时,它会触发小收集,有时还会停止大收集。 这一切都是自动的,因为我们不能在 Java 中强制进行垃圾回收。
相关文章
在 Java 中获取文件大小
发布时间:2023/05/01 浏览次数:139 分类:Java
-
Java 提供了不同的方法来获取文件的字节大小。 本教程演示了在 Java 中获取文件大小的不同方法。使用 Java IO 的文件类获取文件大小 Java IO 包的 File 类提供了以字节为单位获取文件大小的功能。
Java 中的文件分隔符
发布时间:2023/05/01 浏览次数:108 分类:Java
-
本篇文章介绍了 Java 中的文件分隔符。Java 中的文件分隔符 文件分隔符是用来分隔目录的字符; 例如,Unix 使用 /,Windows 使用 \ 作为文件分隔符。
Java 中的文件过滤器
发布时间:2023/05/01 浏览次数:193 分类:Java
-
本篇文章介绍如何在 Java 中使用 FileFilter。FileFilter 用于过滤具有特定扩展名的文件。 Java内置包IO和Apache Commons IO为FileFilter提供了类和接口来进行文件过滤操作。
Java 获取 ISO 8601 格式的当前时间戳
发布时间:2023/05/01 浏览次数:132 分类:Java
-
本篇文章介绍了 ISO 8601 日期格式、其重要性及其在 Java 中的使用。 它还列出了一些优点来强调为什么应该使用 ISO 格式来表示日期。
在 Java 中获取数组的子集
发布时间:2023/05/01 浏览次数:142 分类:Java
-
本篇文章介绍了几种在 Java 中获取数组子集的方法。使用 Arrays.copyOf() 方法获取数组的子集 使用 Arrays.copyOfRange() 方法获取数组的子集
用 Java 填充二维数组
发布时间:2023/05/01 浏览次数:110 分类:Java
-
二维数组是基于表结构的,即行和列,填充二维数组不能通过简单的添加到数组操作来完成。 本篇文章介绍如何在 Java 中填充二维数组。
计算 Java 数组中的重复元素
发布时间:2023/05/01 浏览次数:202 分类:Java
-
本篇文章介绍Java计算数组中重复元素的方法。计算 Java 数组中的重复元素。我们可以创建一个程序来计算数组中的重复元素。 该数组可以是未排序的,也可以是已排序的。
Java 中 List 和 Arraylist 的区别
发布时间:2023/05/01 浏览次数:90 分类:Java
-
表示为单个单元的一组单个对象称为集合。 在 Java 中,Collection 是一个具有多个已定义接口和类的框架,用于将一组对象表示为一个单元。 它允许我们操纵