java大对象_Java中的大对象怎么了?

通过设计,G1垃圾收集器通过将堆划分为固定数量的相同大小的区域来管理堆。默认情况下,最大区域数为2048,并且区域大小与最大堆大小相对应,如下所示:堆大小<4GB:2MB,<8GB:4MB,<16GB:8MB,依此类推。通常,将对象分配到给定区域中,直到其满为止,然后在某个时候,GC通过从该区域撤离所有活动对象来释放整个区域。

80582b54cecc15b405f7dc4584f08486.png

但是,如果对象(通常是数组)大于区域大小的一半,则所有这些更改都会发生。此类对象在G1术语中称为Humongous,其处理方式如下:

大型对象是直接在旧版本中分配的(请注意,在JDK 11和更高版本中,可能会或可能不会)。通过串联几个连续的区域,为每个这样的对象创建一个单独的巨大区域。其中一些区域可能需要先进行GC。每个大型区域只能容纳一个大型物体,而不能容纳其他物体。也就是说,巨大对象的末端和巨大区域的末端之间的空间(在最坏的情况下可能接近正常区域大小的一半)(至少在JDK 8-11中是这样,但是可能会在最新的JDK版本中解决)

55f40151b1f083094f91cc8773886ddf.png

可以从Oracle网站上获得更多详细信息,但是从上面的描述中应该很清楚,从性能的角度来看,庞大的对象是不好的,因为

如果在老一代中进行分配,则即使它们寿命短,也无法快速对其进行GC(老一代的收集频率比年轻一代要低,并且花费更多时间)从多个正常区域中创建一个巨大区域可能需要很短的时间如果堆上有许多巨大的对象,则由于巨大区域中未使用的“间隙”,可能导致堆碎片。

9872179a5b71a7094cd54cbf65704762.png

根据Humongous对象的数量,大小和分配频率,后果可能会有所不同。相对温和的结果是增加了GC暂停时间。在最坏的情况下,

OutOfMemoryError当使用的堆部分比整个堆小得多时,严重的堆碎片会导致JVM崩溃。这是一个简短的应用程序,可以说明此问题:

b6e8b3b038d65a6405c3a0f0979b75f0.png

该应用程序将运行到耗尽堆并崩溃为止 OutOfMemoryError。它打印的最后一行显示了它能够分配多少个1MB阵列。我在具有3900MB堆大小的JDK 8和JDK 11上运行了此代码。使用并发标记扫描(CMS)GC(不会将堆划分成小区域)时,将分配3830个数组,即3830 MB。但是,使用G1 GC时,

6fcb44e6382382af5afc193a94f2a27e.png

分配的空间减少了两倍(1948个数组)。换句话说,使用CMS,几乎可以使用整个堆,而使用G1 GC,该应用程序实际上只能使用大约一半的堆!如果更改此代码以模拟更现实的情况(大小数组交替分配),结果将不会更好:该应用程序成功分配了大约2600MB的内存,但分配的数量却更少了-1310而不是1948 -个1MB阵列。

e1ee7e0492f9614d724b381b6e901c54.png

在分析OOM之后生成的堆转储时(如果-XX:+HeapDumpOnOutOfMemoryError启用了JVM标志),这种情况可能会引起混乱:这样的转储可能比堆大小小得多。这是违反直觉的:如果转储很小,则意味着堆没有得到充分利用,即堆中有可用空间。但是,如果有可用空间,那么为什么JVM声称它耗尽了堆空间?

a29c5917bfdae35badea0594c2fd7244.png

答案是:堆中有(很多)可用空间,但是新对象分配无法访问它。即使某些巨大的对象是垃圾,该GC也会进一步加剧,因为未知原因,GC可能(如我们多次观察)无法启动将释放这些对象并释放空间的Full GC。

诊断大量对象分配

最容易发现大型对象分配的地方是应用程序的GC日志。只是将其grep表示为“巨大”,您可能会看到类似以下内容:

584aa4a8e631f76358386e6b9be0ada7.png

如果您观察到频繁的Humongous对象分配,和/或与之相关的长时间GC暂停,则下一步是确定谁分配/管理此类对象。例如,可以使用JXRay工具通过堆转储分析来完成此操作。该工具具有“检查”功能,可查找所有大小为1MB及更大的对象。对于每个这样的对象,它显示了返回到某个GC根目录的最短路径(参考链)。具有相同路径的对象被聚集在一起,这使得在管理许多问题对象的代码中轻松识别“热点”。

cdbaa52a060af5274e843456933c3e4f.png

对于大型对象检测,强烈建议进行完整的堆转储(即,将所有对象(包括垃圾)而不是仅包含活动对象的堆都转储)。那是因为某些Humongous对象(例如,序列化和发送消息时使用的临时数组)可能是短暂的,即很快变成垃圾,而不会出现在活动堆转储中。但是,垃圾对象的问题是,根据定义,实时数据结构中没有对它们的引用。

4b8a402141463ec450b66115a5268855.png

因此,在分析完整的堆转储时,通常很难确定曾经创建和管理过谁(应用程序代码的哪一部分)。如果不清楚某个“大型对象”是从哪里来的,则可能需要从同一个应用程序中进行几次完整的转储,然后对其进行分析或选择最大的转储,更有可能同时容纳垃圾和生活中的巨大物体。这是一份有关Humongous对象的JXRay报告的摘录:

abd9ba9a57ae1688affc3dbc04b85523.png

减轻大型对象的问题

有多种方法可以解决或至少缓解此问题:调整GC,更改GC类型,解决根本原因或升级到较新的JDK。

在这种情况下,调整GC意味着增加堆或增加区域大小,-XX:G1HeapRegionSize以使以前的Humongous对象不再是Humongous对象,而是遵循常规分配路径。

f0717f8762f2186efa3cdcf7c7c3d654.png

但是,后者会减少区域数量,这可能会对GC性能产生负面影响。这还意味着将GC选项与当前工作负载结合在一起(这可能会在将来发生变化并打破您当前的假设)。但是,在某些情况下,这是继续进行的唯一方法。

2b8d8de95b64524f9abeeb4ccff4682a.png

解决此问题的更基本方法是通过-XX:+UseParNewGC XX:+UseConcMarkSweepGC标志切换到较旧的并发标记扫描(CMS)垃圾收集器(除非您使用不赞成使用该收集器的最新JDK版本之一)。CMS不会将堆分成多个小区域,因此处理几个MB的对象没有问题(尽管每个垃圾收集器可能都很难为更大的对象释放空间,例如占用了堆的10%) 。

81e1bddd7842798f166f9820409c3472.png

实际上,在相对较旧的Java版本中,CMS的总体性能甚至可能比G1更好,至少如果应用程序创建的大多数对象都属于两类:非常短寿命和非常长寿命。

解决根本原因意味着以某种方式更改代码以停止生成Humongous对象。例如,有时您可能会发现Humongous对象是太大而未充分利用的缓冲区,因此可以安全地减小它们的大小。

491234888953ed49734793602e9fa8c7.png

在其他情况下,您可能需要对代码进行更根本的更改,或者考虑用另一个替换有问题的第三方库。

最后,从版本11开始,在最新的JDK中,Humongous对象的问题可能不太严重。在这些JDK中,此类对象是在Young Gen中分配的,因此寿命较短的Humongous对象应更快地收集。

03a9134d85c596b3048448f93ae572ab.png

在其他方面,也可能会更好地处理它们,从而导致较短的与humunous相关的GC暂停,并且可能在Humunous地区也能够容纳正常对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值