Java中的垃圾回收

时间:2020-02-23 14:36:14  来源:igfitidea点击:

Java中的垃圾回收是高级主题之一。
Java GC知识有助于我们微调应用程序的运行时性能。

Java中的垃圾回收

  • 在Java中,程序员无需担心破坏不使用的对象。
    垃圾收集器会处理它。

  • 垃圾收集器是一个后台驻留程序的守护进程线程。
    基本上,它通过销毁无法访问的对象来释放堆内存。

  • 无法访问的对象是程序的任何部分不再引用的对象。

  • 我们可以通过JVM选项为我们的Java程序选择垃圾收集器,我们将在本教程的后面部分中研究这些垃圾收集器。

自动垃圾收集如何工作?

自动垃圾收集是查看堆内存,识别(也称为"标记")无法访问的对象并通过压缩将其破坏的过程。

这种方法的问题在于,随着对象数量的增加,垃圾收集时间会不断增加,因为它需要遍历对象的整个列表,以寻找不可达的对象。

但是,对应用程序的经验分析表明,大多数对象都是短暂的。

此行为用于提高JVM的性能,采用的方法通常称为"世代垃圾收集"。
在这种方法中,堆空间被分为几代,如年轻一代,老一代或者终身一代以及永久一代。

Young一代堆空间是创建所有新对象的新空间。
一旦被填满,就会进行次要垃圾收集(也称为Minor GC)。
这意味着,这一代中的所有死亡对象都被销毁了。
此过程之所以迅速,是因为从图中可以看出,大多数对象都是死亡的。
年轻一代中幸存的物体已经老化,并最终移交给了老一代。

老一代用于存储长期存在的对象。
通常,为年轻一代对象设置一个阈值,并且当达到该年龄时,该对象将移至老一代。
最终,需要收集旧的一代。
此事件称为主要GC(主要垃圾回收)。
通常,它要慢得多,因为它涉及所有活动对象。
此外,还有完整的GC,这意味着可以清洁整个堆堆(年轻一代和老一代空间)。

最后,直到Java 7,都有一个永久代(或者Perm Gen),其中包含JVM所需的元数据,用于描述应用程序中使用的类和方法。
在Java 8中已将其删除。

Java垃圾收集器

JVM实际上提供了四个不同的垃圾收集器,它们都是世代相传的。
每个人都有自己的优点和缺点。
使用哪种垃圾收集器的选择由我们决定,吞吐量和应用程序暂停可能会有很大差异。

所有这些都使用古老的假设,即堆中的大多数对象是短寿命的,应迅速回收,将托管堆分为不同的段。

因此,四种类型的垃圾收集器是:

串行GC

这是最简单的垃圾收集器,设计用于单线程系统和较小的堆大小。
它在工作时冻结所有应用程序。
可以使用-XX:+ UseSerialGCJVM选项打开。

平行/通量GC

这是JVM在JDK 8中的默认收集器。
顾名思义,它使用多个线程扫描堆空间并执行压缩。
该收集器的缺点是在执行次要或者完全GC时会暂停应用程序线程。
如果应用程序可以处理此类暂停,并尝试优化由收集器引起的CPU开销,则是最适合的。

CMS收集器

CMS收集器(" concurrent-mark-sweep")算法使用多个线程(" concurrent")在堆(" mark")中扫描可回收(" sweep")未使用的对象。

该收集器在两种情况下进入"世界停止"模式:-初始化根的初始标记时,即。
可以从线程入口点或者静态变量访问的旧对象-当算法同时运行时,应用程序更改了堆的状态,并迫使其返回并进行最后的修改以确保其正确时标有对象。

该收集器可能会面临升级失败。
如果要将年轻一代的一些对象移到老一代,并且收集器没有足够的时间在老一代空间中腾出空间,则会发生升级失败。
为了防止这种情况,我们可能会为旧一代提供更多的堆大小,或者为收集器提供更多的后台线程。

G1收集器

最后但并非最不重要的是Garbage-First收集器,其设计用于大于4GB的堆大小。
它将根据堆大小将堆大小划分为1MB到32Mb之间的区域。

有一个并发的全局标记阶段来确定整个堆中对象的活动性。
标记阶段完成后,G1知道哪些区域大部分为空。
它首先从这些区域收集无法到达的对象,这通常会产生大量的可用空间。
因此,G1首先收集这些区域(包含垃圾),因此名称为Garbage-First。
为了满足用户定义的暂停时间目标,G1还使用了暂停预测模型。
它根据指定的暂停时间目标选择要收集的区域数。

G1垃圾回收周期包括以下各阶段:

  • 仅限年轻阶段:此阶段仅包含年轻一代对象,并将其提升为老一代。
    当老一代被占用到一定的阈值时,即仅年轻阶段和空间恢复阶段之间的过渡开始。
    启动堆占用率阈值。
    此时,G1安排了一个Initial Mark仅限年轻人的收藏,而不是常规的仅限年轻人的收藏。

  • 初始标记:除了常规的仅年轻标记的集合之外,这种类型的集合还开始标记过程。
    并发标记确定了在下一个空间回收阶段中要保留的旧一代区域中的所有当前活动对象。
    标记尚未完全结束,但可能会定期出现仅年轻的收藏。
    标记结束时有两个特殊的停顿世界:"备注"和"清理"。

  • 备说明:此暂停将最终完成标记本身,并执行全局引用处理和类卸载。
    在"备注"和"清除"之间,G1同时计算活动信息的摘要,该摘要将最终确定并在"清除暂停"中用于更新内部数据结构。

  • 清理:此暂停还将占用完全空白的区域,并确定是否将实际进行空间回收阶段。
    如果进行空间回收阶段,则仅年轻阶段将以单个仅年轻集合完成。

  • 太空垦殖阶段:此阶段包括多个混合馆藏-除了年轻一代地区,还撤离了旧一代地区的活物。
    当G1确定撤离更多的旧一代地区不会产生值得努力的足够自由空间时,太空争夺阶段结束。

可以使用-XX:+ UseG1GC标志启用G1。

此策略减少了在后台线程完成对不可达对象的扫描之前耗尽堆的机会。
此外,它还可以在移动时压缩堆,CMS收集器只能在STW模式下执行此操作。

在Java 8中,G1收集器提供了一个漂亮的优化,称为字符串重复数据删除。
众所周知,代表字符串的字符数组占用了我们的堆空间。
进行了新的优化,使G1收集器可以识别在我们的堆中重复多次的字符串,并对其进行修改以指向同一内部char []数组,以避免不必要地将同一字符串的多个副本驻留在堆中。
我们可以使用-XX:+ UseStringDeduplicationJVM参数来启用此优化。

G1是JDK 9中的默认垃圾收集器。

Java 8 PermGen和元空间

如前所述,自Java 8以来已删除了永久生成空间。
因此,现在,JDK 8 HotSpot JVM使用本机内存来表示类元数据,即元空间。

类元数据的大多数分配都是在本机内存中进行的。
另外,还有一个新的标志MaxMetaspaceSize,以限制用于类元数据的内存量。
如果我们没有为此指定值,则Metaspace将在运行时根据正在运行的应用程序的需求重新调整大小。

当类元数据使用量达到MaxMetaspaceSize限制时,将触发元空间垃圾回收。
过多的Metaspace垃圾收集可能是类的症状,类加载器内存泄漏或者应用程序大小不足。