Java中的垃圾回收
Java垃圾回收是一个高级主题之一。Java GC知识有助于我们在优化应用程序的运行时性能方面进行微调。
Java中的垃圾回收
- In Java, the programmers don’t need to take care of destroying the objects that are out of use. The Garbage Collector takes care of it.
- Garbage Collector is a Daemon thread that keeps running in the background. Basically, it frees up the heap memory by destroying the unreachable objects.
- Unreachable objects are the ones that are no longer referenced by any part of the program.
- We can choose the garbage collector for our java program through JVM options, we will look into these in later section of this tutorial.
自动垃圾回收是如何运作的?
自动垃圾回收是一种查看堆内存、识别(也称为“标记”)不可达对象并进行压缩销毁的过程。这种方法的一个问题是随着对象数量的增加,垃圾回收时间也在增长,因为它需要遍历整个对象列表,查找不可达对象。然而,应用程序的经验分析表明,大多数对象的生命周期很短。这种行为被用来改善JVM的性能,采用的方法通常被称为分代垃圾回收。在这种方法中,堆空间被划分为年轻代、老年代和永久代等一些代。年轻代堆空间是新对象创建的地方。一旦填满,将进行次要垃圾回收(也称为次要GC)。这意味着该代中的所有无效对象都会被销毁。由于从图中可以看出,大多数对象都是无效的,所以这个过程很快。年轻代中幸存的对象会变老并最终移动到老年代。老年代被用于存储存活时间较长的对象。通常,为年轻代对象设置一个阈值,当达到该年龄时,对象会被移到老年代。最终,需要收集老年代。这个事件称为主要GC(主要垃圾回收)。由于它涉及所有活动对象,所以通常较慢。还有一种称为Full GC的操作,它清理整个堆,包括年轻代和老年代的空间。最后,在Java 7之前,还存在一种称为永久代(或Perm Gen)的空间,其中包含JVM需要描述应用程序中使用的类和方法的元数据。它在Java 8中被移除。
Java 垃圾回收器
JVM实际上提供了四种不同的垃圾回收器,它们都是分代的。每个回收器都有各自的优缺点。选择使用哪种垃圾回收器取决于我们自己,而且吞吐量和应用程序暂停时间可能存在巨大的差异。所有这些垃圾回收器将托管堆划分为不同的片段,根据古老的假设,堆中的大多数对象都是短寿命的,应该被快速回收。因此,这四种类型的垃圾回收器是:
串行垃圾回收
这是最简单的垃圾收集器,专为单线程系统和小堆大小设计。它在工作时会冻结所有应用程序。可以通过使用-XX:+UseSerialGC JVM选项来启用。
并行/吞吐量垃圾回收 /
这是JDK 8中JVM的默认收集器。顾名思义,它使用多线程来扫描堆空间并进行整理。这个收集器的缺点是在执行部分或完整的GC时会暂停应用程序线程。它最适合能够处理此类暂停并尝试优化收集器引起的CPU开销的应用程序。
CMS收集器
CMS收集器(”concurrent-mark-sweep”)算法使用多个线程(”concurrent”)扫描堆(”mark”)中未使用的可以回收的对象(”sweep”)。该收集器在两种情况下进入Stop-The-World(STW)模式:- 初始化根对象的起始标记时,即从线程入口点或静态变量可达的旧一代对象- 当应用程序在算法并发运行时改变了堆的状态,并迫使其返回并进行一些最后的处理,以确保正确标记了对象。此收集器可能会面临升级失败。如果要将某些年轻代的对象移动到旧一代,并且收集器没有足够的时间来在旧一代空间中腾出位置,则会发生升级失败。为了防止这种情况,我们可以为旧一代提供更多的堆大小,或为收集器提供更多的后台线程。
G1 收集器
- 年轻代专用阶段:此阶段仅包括年轻一代的对象,并将它们晋升到老一代。年轻代专用阶段与空间回收阶段之间的过渡是在老一代达到一定的阈值(即初始堆占用阈值)时开始的。此时,G1会安排一次初始标记的年轻代专用回收,而不是常规的年轻代专用回收。
初始标记:这种类型的回收会在常规的年轻代专用回收之外启动标记过程。并发标记确定老一代区域中的所有当前存活对象,以便在接下来的空间回收阶段保留这些对象。在标记尚未完全结束时,可能会进行常规的年轻代专用回收。标记完成后会有两次特殊的停顿:标记暂停和清理暂停。
标记暂停:此暂停最终完成标记本身,并执行全局引用处理和类卸载。在标记暂停和清理暂停之间,G1会并发计算活跃信息的摘要,并在清理暂停时最终确定并用于更新内部数据结构。
清理暂停:此暂停还会处理完全空的区域,并确定是否真的需要进行空间回收阶段。如果有空间回收阶段,年轻代专用阶段将以一次年轻代专用回收完成。
空间回收阶段:此阶段包括多次混合回收-除了回收年轻代区域外,还会疏散老一代区域的存活对象。空间回收阶段在G1确定疏散更多老一代区域不再值得的情况下结束。
可以使用-XX:+UseG1GC标志来启用G1垃圾收集器。这种策略减少了在后台线程完成对无法访问的对象进行扫描之前堆被耗尽的机会。此外,它还可以在运行时压缩堆,而CMS收集器只能在发生STW模式时进行压缩。在Java 8中,通过G1收集器提供了一种名为字符串去重的出色优化。我们知道,表示字符串的字符数组占用了大量的堆空间。现在,引入了一种新的优化,使G1收集器能够识别堆中多次重复出现的字符串,并将它们修改为指向同一个内部char[]数组,以避免不必要地在堆中存在多个相同的字符串副本。我们可以使用-XX:+UseStringDeduplication JVM参数来启用此优化。在JDK 9中,默认的垃圾收集器是G1。
Java 8 的永久代(PermGen)和元空间(Metaspace)。
如前所述,自Java 8开始,永久代空间已被移除。因此,JDK 8 HotSpot JVM现在使用本机内存来表示类的元数据,这被称为Metaspace。大部分类元数据的分配都是在本机内存中进行的。此外,还有一个新的标志MaxMetaspaceSize,用于限制用于类元数据的内存量。如果我们没有为此指定值,Metaspace将根据运行应用程序的需求在运行时重新调整大小。当类元数据使用达到MaxMetaspaceSize限制时,会触发Metaspace垃圾收集。过多的Metaspace垃圾收集可能是类、类加载器内存泄漏或应用程序大小不足的症状。关于java中的垃圾收集,就介绍到这里。希望你对我们在java中拥有的不同垃圾收集器有所了解。参考资料:Oracle文档,G1 GC。