JVM优化经验总结

小夏 教育 更新 2024-02-08

通过示例和相应的输出说明,您将对 JVM 优化有一个初步的了解。

j**a虚拟机有自己完整的硬件架构,如处理器、堆栈、寄存器等,也有相应的指令系统。 JVM 屏蔽了有关特定操作系统平台的信息,因此只需生成在 JVM 上运行的目标字节码,JA 程序就可以在各种平台上运行,而无需修改。 当 j**a 虚拟机执行字节码时,它实际上最终会将字节码解释为在特定平台上执行机器指令。

注意:本文仅重点介绍 JDK7 和 Hotspot J**A VM,不重点介绍 JDK8 和其他 J**A VM 中引入的新 JVM 功能。

让我们从示例开始本文。 假设你是一个普通的j**a对象,你出生在伊甸园区,伊甸园区有很多和你长相相似的小弟弟妹妹,你可以把伊甸园区当成幼儿园,大家在这个幼儿园里玩了很久。 伊甸园区不能无休止地呆在里面,所以等你长大一点,你就会被送到学校,从小学到高中都叫幸存者区。

刚开始,你在幸存者区划分了从区,到了大四的时候,你进入了幸存者区的到区,由于中间学习成绩不稳定,你经常来回折腾。 当你18岁的时候,你已经高中毕业了,是时候在社会上闯出一片天了。所以你去找老一代,老一代有很多人。 在上一辈,你活了20年(每个GC加一岁),最后你死了,而GC**根本没有提,你遇到了一个老一辈的同学,他的名字叫爱德华(光之城的帅气吸血鬼),他和他的家人永远不会死,然后他们生活在永恒时代。

在上一篇文章“JVM Junkter 的工作原理和用例简介”中,我们介绍了年轻一代、老年人和不朽一代,本文主要讨论如何利用这些领域为系统性能提供更好的帮助。 本文不重复这些概念,让我们直奔主题。 众所周知,由于全GC的成本远高于次要GC,因此在某些情况下,有必要尽可能地将对象分配给年轻一代,这在很多情况下都是明智的选择。 虽然在大多数情况下,JVM 会尝试在 eden zone 中分配对象,但由于空间限制和其他问题,很可能不得不提前将一些较年轻的对象压缩到老一代。 因此,在调整JVM参数时,可以为应用分配合理的年轻一代空间,最大程度避免新对象直接进入老一代的发生。 如清单 1** 所示,尝试分配 4MB 的内存空间并观察其内存使用情况。

清单 1相同大小的内存分配。

public class putineden }
使用 jvm 参数 -xx:+printgcdetails -xmx20m -xms20m 运行清单 1,输出如清单 2 所示。

清单 2清单 1 运行输出。

[gc [defnew: 5504k->640k(6144k), 0.0114236 secs] 5504k->5352k(19840k),0.0114595 secs] [times: user=0.02 sys=0.00, real=0.02 secs][gc [defnew: 6144k->640k(6144k), 0.0131261 secs] 10856k->10782k(19840k),0.0131612 secs] [times: user=0.02 sys=0.00, real=0.02 secs][gc [defnew: 6144k->6144k(6144k), 0.0000170 secs][tenured: 10142k->13695k(13696k),0.1069249 secs] 16286k->15966k(19840k), perm : 376k->376k(12288k)],0.1070058 secs] [times: user=0.03 sys=0.00, real=0.11 secs][full gc [tenured: 13695k->13695k(13696k), 0.0302067 secs] 19839k->19595k(19840k),[perm : 376k->376k(12288k)],0.0302635 secs] [times: user=0.03 sys=0.00, real=0.03 secs][full gc [tenured: 13695k->13695k(13696k), 0.0311986 secs] 19839k->19839k(19840k),[perm : 376k->376k(12288k)],0.0312515 secs] [times: user=0.03 sys=0.00, real=0.03 secs][full gc [tenured: 13695k->13695k(13696k), 0.0358821 secs] 19839k->19825k(19840k),[perm : 376k->371k(12288k)],0.0359315 secs] [times: user=0.05 sys=0.00, real=0.05 secs][full gc [tenured: 13695k->13695k(13696k), 0.0283080 secs] 19839k->19839k(19840k),[perm : 371k->371k(12288k)],0.0283723 secs] [times: user=0.02 sys=0.00, real=0.01 secs][full gc [tenured: 13695k->13695k(13696k), 0.0284469 secs] 19839k->19839k(19840k),[perm : 371k->371k(12288k)],0.0284990 secs] [times: user=0.03 sys=0.00, real=0.03 secs][full gc [tenured: 13695k->13695k(13696k), 0.0283005 secs] 19839k->19839k(19840k),[perm : 371k->371k(12288k)],0.0283475 secs] [times: user=0.03 sys=0.00, real=0.03 secs][full gc [tenured: 13695k->13695k(13696k), 0.0287757 secs] 19839k->19839k(19840k),[perm : 371k->371k(12288k)],0.0288294 secs] [times: user=0.03 sys=0.00, real=0.03 secs][full gc [tenured: 13695k->13695k(13696k), 0.0288219 secs] 19839k->19839k(19840k),[perm : 371k->371k(12288k)],0.0288709 secs] [times: user=0.03 sys=0.00, real=0.03 secs][full gc [tenured: 13695k->13695k(13696k), 0.0293071 secs] 19839k->19839k(19840k),[perm : 371k->371k(12288k)],0.0293607 secs] [times: user=0.03 sys=0.00, real=0.03 secs][full gc [tenured: 13695k->13695k(13696k), 0.0356141 secs] 19839k->19838k(19840k),[perm : 371k->371k(12288k)],0.0356654 secs] [times: user=0.01 sys=0.00, real=0.03 secs]heapdef new generation total 6144k, used 6143k [0x35c10000, 0x362b0000, 0x362b0000)eden space 5504k, 100% used [0x35c10000, 0x36170000, 0x36170000)from space 640k, 99% used [0x36170000, 0x3620fc80, 0x36210000)to space 640k, 0% used [0x36210000, 0x36210000, 0x362b0000)tenured generation total 13696k, used 13695k [0x362b0000, 0x37010000, 0x37010000)the space 13696k, 99% used [0x362b0000, 0x3700fff8, 0x37010000, 0x37010000)compacting perm gen total 12288k, used 371k [0x37010000, 0x37c10000, 0x3b010000)the space 12288k, 3% used [0x37010000, 0x3706cd20, 0x3706ce00, 0x37c10000)ro space 10240k, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)rw space 12288k, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
清单 2 中所示的日志输出显示,年轻一代的 Eden 大小约为 5MB。 使用 jvm 参数 -xx:+printgcDetails -xmx20m -xms20m-xmn6m 分配足够的年轻一代空间来运行清单 1,输出如清单 3 所示。

清单 3清单 1 在增加 eden 大小后运行输出。

[gc [defnew: 4992k->576k(5568k), 0.0116036 secs] 4992k->4829k(19904k),0.0116439 secs] [times: user=0.02 sys=0.00, real=0.02 secs][gc [defnew: 5568k->576k(5568k), 0.0130929 secs] 9821k->9653k(19904k),0.0131336 secs] [times: user=0.02 sys=0.00, real=0.02 secs][gc [defnew: 5568k->575k(5568k), 0.0154148 secs] 14645k->14500k(19904k),0.0154531 secs] [times: user=0.00 sys=0.01, real=0.01 secs][gc [defnew: 5567k->5567k(5568k), 0.0000197 secs][tenured: 13924k->14335k(14336k),0.0330724 secs] 19492k->19265k(19904k), perm : 376k->376k(12288k)],0.0331624 secs] [times: user=0.03 sys=0.00, real=0.03 secs][full gc [tenured: 14335k->14335k(14336k), 0.0292459 secs] 19903k->19902k(19904k),[perm : 376k->376k(12288k)],0.0293000 secs] [times: user=0.03 sys=0.00, real=0.03 secs][full gc [tenured: 14335k->14335k(14336k), 0.0278675 secs] 19903k->19903k(19904k),[perm : 376k->376k(12288k)],0.0279215 secs] [times: user=0.03 sys=0.00, real=0.03 secs][full gc [tenured: 14335k->14335k(14336k), 0.0348408 secs] 19903k->19889k(19904k),[perm : 376k->371k(12288k)],0.0348945 secs] [times: user=0.05 sys=0.00, real=0.05 secs][full gc [tenured: 14335k->14335k(14336k), 0.0299813 secs] 19903k->19903k(19904k),[perm : 371k->371k(12288k)],0.0300349 secs] [times: user=0.01 sys=0.00, real=0.02 secs][full gc [tenured: 14335k->14335k(14336k), 0.0298178 secs] 19903k->19903k(19904k),[perm : 371k->371k(12288k)],0.0298688 secs] [times: user=0.03 sys=0.00, real=0.03 secs]exception in thread "main" j**a.lang.outofmemoryerror: j**a heap space[full gc [tenured:14335k->14335k(14336k), 0.0294953 secs] 19903k->19903k(19904k),[perm : 371k->371k(12288k)],0.0295474 secs] [times: user=0.03 sys=0.00, real=0.03 secs][full gc [tenured: 14335k->14335k(14336k), 0.0287742 secs] 19903k->19903k(19904k),[perm : 371k->371k(12288k)],0.0288239 secs] [times: user=0.03 sys=0.00, real=0.03 secs][full gc [tenuredat gctimetest.main(gctimetest.j**a:16): 14335k->14335k(14336k), 0.0287102 secs] 19903k->19903k(19904k),[perm : 371k->371k(12288k)],0.0287627 secs] [times: user=0.03 sys=0.00, real=0.03 secs]heapdef new generation total 5568k, used 5567k [0x35c10000, 0x36210000, 0x36210000)eden space 4992k, 100% used [0x35c10000, 0x360f0000, 0x360f0000)from space 576k, 99% used [0x36180000, 0x3620ffe8, 0x36210000)to space 576k, 0% used [0x360f0000, 0x360f0000, 0x36180000)tenured generation total 14336k, used 14335k [0x36210000, 0x37010000, 0x37010000)the space 14336k, 99% used [0x36210000, 0x3700ffd8, 0x37010000, 0x37010000)compacting perm gen total 12288k, used 371k [0x37010000, 0x37c10000, 0x3b010000)the space 12288k, 3% used [0x37010000, 0x3706ce28, 0x3706d000, 0x37c10000)ro space 10240k, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)rw space 12288k, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
通过对清单2和清单3的比较,可以发现,通过设置大量年轻一代来保留新对象,设置合理的幸存者区域,并提供幸存者区域的利用率,可以将年轻对象存储在年轻一代中。 一般来说,幸存者区空间不足,或者当达到50%的入住率时,将导致受试者进入老年(无论其年龄大小)。 清单 4 创建了 3 个对象,每个对象都有一定的内存空间。

清单 4不同大小的内存分配。

public class putineden2 }
使用参数 -xx:+printgcDetails -xmx1000m -xms500m -xmn100m -xx:survivorratio=8 运行清单 4,输出如清单 5 所示。

清单 5清单 4 运行输出。

heapdef new generation total 92160k, used 11878k [0x0f010000, 0x15410000, 0x15410000)eden space 81920k, 2% used [0x0f010000, 0x0f1a9a20, 0x14010000)from space 10240k, 99% used [0x14a10000, 0x1540fff8, 0x15410000)to space 10240k, 0% used [0x14010000, 0x14010000, 0x14a10000)tenured generation total 409600k, used 86434k [0x15410000, 0x2e410000, 0x4d810000)the space 409600k, 21% used [0x15410000, 0x1a878b18, 0x1a878c00, 0x2e410000)compacting perm gen total 12288k, used 2062k [0x4d810000, 0x4e410000, 0x51810000)the space 12288k, 16% used [0x4d810000, 0x4da13b18, 0x4da13c00, 0x4e410000)no shared spaces configured.
清单 5 的对数输出显示,年轻一代被分配了 8m,老一代也被分配了 8m。 我们可以尝试添加 -xx:targetsurvivorratio=90 参数,这样可以提高 from zone 的利用率,这样当 from zone 被使用到 90% 时,然后将对象发送给老一代,并运行 Manifest 4 ** 输出如清单 6 所示。

清单 6修改运行参数后的清单 4 输出。

heapdef new generation total 9216k, used 9215k [0x35c10000, 0x36610000, 0x36610000)eden space 8192k, 100% used [0x35c10000, 0x36410000, 0x36410000)from space 1024k, 99% used [0x36510000, 0x3660fc50, 0x36610000)to space 1024k, 0% used [0x36410000, 0x36410000, 0x36510000)tenured generation total 10240k, used 10239k [0x36610000, 0x37010000, 0x37010000)the space 10240k, 99% used [0x36610000, 0x3700ff70, 0x37010000, 0x37010000)compacting perm gen total 12288k, used 371k [0x37010000, 0x37c10000, 0x3b010000)the space 12288k, 3% used [0x37010000, 0x3706cd90, 0x3706ce00, 0x37c10000)ro space 10240k, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)rw space 12288k, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
如果存活率设置为 2,则 B1 对象在年轻一代中预先存在。 输出如清单 7 所示。

清单 7再次修改运行参数后,将显示清单 4 的输出。

heapdef new generation total 7680k, used 7679k [0x35c10000, 0x36610000, 0x36610000)eden space 5120k, 100% used [0x35c10000, 0x36110000, 0x36110000)from space 2560k, 99% used [0x36110000, 0x3638fff0, 0x36390000)to space 2560k, 0% used [0x36390000, 0x36390000, 0x36610000)tenured generation total 10240k, used 10239k [0x36610000, 0x37010000, 0x37010000)the space 10240k, 99% used [0x36610000, 0x3700fff0, 0x37010000, 0x37010000)compacting perm gen total 12288k, used 371k [0x37010000, 0x37c10000, 0x3b010000)the space 12288k, 3% used [0x37010000, 0x3706ce28, 0x3706d000, 0x37c10000)ro space 10240k, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)rw space 12288k, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
在大多数情况下,我们选择将对象分配给年轻一代。 但是,对于大型内存密集型对象,情况可能并非如此。 因为年轻一代中大型物体的存在,很可能会扰乱年轻一代的GC,破坏年轻一代原有的物体结构。 因为尝试将大型对象分配给年轻一代可能会导致空间不足,因此 JVM 必须将年轻一代中的年轻对象移动到老一代,以便有足够的空间来容纳大型对象。 由于大型物体占用大量空间,因此可能需要将大量小的幼小物体移动到老一辈,这对GC是相当不利的。

由于这些原因,可以将大型对象直接分配给老一辈,并保持年轻一代对象结构的完整性,从而提高GC的效率。 如果一个大对象也是一个短暂的对象,假设这种情况经常发生,这对 GC 来说将是一场灾难。 本来应该用来存储永久物品的老一代,却塞满了短命的物品,这也意味着堆空间被洗牌,打乱了世代记忆的基本理念。

因此,在软件开发过程中,应尽可能避免使用寿命较短的大对象。 您可以使用 -xx:petenuresizethreshold 参数来设置大型对象的阈值,使其直接转到老一代。 当对象的大小超过此值时,将直接在旧一代中分配。 参数 -xx:petenuresizethreshold 仅对串口采集器和年轻一代并行采集器有效,并行**采集器无法识别该参数。

清单 8创建一个大型对象。

public class bigobj2old }
使用 jvm 参数 -xx:+printgcdetails xmx20m xms20mb 运行,将得到清单 9 所示的日志输出。

清单 9清单 8 运行输出。

heapdef new generation total 6144k, used 1378k [0x35c10000, 0x362b0000, 0x362b0000)eden space 5504k, 25% used [0x35c10000, 0x35d689e8, 0x36170000)from space 640k, 0% used [0x36170000, 0x36170000, 0x36210000)to space 640k, 0% used [0x36210000, 0x36210000, 0x362b0000)tenured generation total 13696k, used 0k [0x362b0000, 0x37010000, 0x37010000)the space 13696k, 0% used [0x362b0000, 0x362b0000, 0x362b0200, 0x37010000)compacting perm gen total 12288k, used 374k [0x37010000, 0x37c10000, 0x3b010000)the space 12288k, 3% used [0x37010000, 0x3706dac8, 0x3706dc00, 0x37c10000)ro space 10240k, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)rw space 12288k, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
可以看出,该对象分配给了年轻一代,占据了25%的空间。 如果要将大于 1MB 的对象直接分配给老年人,请设置 -xx:petenuresizethreshold=1000000,程序运行后,输出将显示在清单 10 中。

清单 10修改运行参数后的清单 8 输出。

heapdef new generation total 6144k, used 354k [0x35c10000, 0x362b0000, 0x362b0000)eden space 5504k, 6% used [0x35c10000, 0x35c689d8, 0x36170000)from space 640k, 0% used [0x36170000, 0x36170000, 0x36210000)to space 640k, 0% used [0x36210000, 0x36210000, 0x362b0000)tenured generation total 13696k, used 1024k [0x362b0000, 0x37010000, 0x37010000)the space 13696k, 7% used [0x362b0000, 0x363b0010, 0x363b0200, 0x37010000)compacting perm gen total 12288k, used 374k [0x37010000, 0x37c10000, 0x3b010000)the space 12288k, 3% used [0x37010000, 0x3706dac8, 0x3706dc00, 0x37c10000)ro space 10240k, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)rw space 12288k, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
在清单 10 中,您可以看到,当 1MB 已满时,它进入了高级一代。

堆中的每个对象都有自己的期限。 一般来说,年轻的对象存储在年轻一代中,而旧的对象存储在老一代中。 为此,虚拟机会为每个对象保留一个期限。 如果对象在伊甸园区域,并且在GC后还活着,则将其移动到幸存者区域,并将对象的年龄加1。 之后,如果受试者在每次 GC 经历中幸存下来,则年龄会额外增加 1。 当主体的年龄达到门槛时,它就进入了老年一代,成为老年客体。 可以使用参数 -xx:maxtenuringthreshold 设置此阈值的最大值,默认值为 15。

虽然 -xx:maxtenuringthreshold 的值可能为 15 或更大,但这并不意味着新对象必须达到这个年龄才能进入老一代。 实际上,对象实际进入老一代的期限是由虚拟机在运行时根据内存使用情况动态计算的,此参数指定阈值期限的最大值。 也就是说,晋升的老一辈的实际年龄等于动态计算的年龄和 -xx:maxtenuringthreshold 中的较小者。 清单 11 显示了 3 个对象声明了多个内存。

清单 11请求内存。

public class maxtenuringthreshold }
参数设置为:-xx:+printgcdetails -xmx20m -xms20m -xmn10m -xx:survivorratio=2

运行清单 11,输出如清单 12 所示。

清单 12清单 11 运行输出。

[gc [defnew: 2986k->690k(7680k), 0.0246816 secs] 2986k->2738k(17920k),0.0247226 secs] [times: user=0.00 sys=0.02, real=0.03 secs][gc [defnew: 4786k->690k(7680k), 0.0016073 secs] 6834k->2738k(17920k),0.0016436 secs] [times: user=0.00 sys=0.00, real=0.00 secs]heapdef new generation total 7680k, used 4888k [0x35c10000, 0x36610000, 0x36610000)eden space 5120k, 82% used [0x35c10000, 0x36029a18, 0x36110000)from space 2560k, 26% used [0x36110000, 0x361bc950, 0x36390000)to space 2560k, 0% used [0x36390000, 0x36390000, 0x36610000)tenured generation total 10240k, used 2048k [0x36610000, 0x37010000, 0x37010000)the space 10240k, 20% used [0x36610000, 0x36810010, 0x36810200, 0x37010000)compacting perm gen total 12288k, used 374k [0x37010000, 0x37c10000, 0x3b010000)the space 12288k, 3% used [0x37010000, 0x3706db50, 0x3706dc00, 0x37c10000)ro space 10240k, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)rw space 12288k, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
将参数更改为 -xx:+printgcdetails -xmx20m -xms20m -xmn10m -xx:survivorratio=2 -xx:maxtenuringthreshold=1,运行清单 11 **,输出如清单 13 所示。

清单 13修改运行参数后的 Manifest 11 输出。

[gc [defnew: 2986k->690k(7680k), 0.0047778 secs] 2986k->2738k(17920k),0.0048161 secs] [times: user=0.00 sys=0.00, real=0.00 secs][gc [defnew: 4888k->0k(7680k), 0.0016271 secs] 6936k->2738k(17920k),0.0016630 secs] [times: user=0.00 sys=0.00, real=0.00 secs]heapdef new generation total 7680k, used 4198k [0x35c10000, 0x36610000, 0x36610000)eden space 5120k, 82% used [0x35c10000, 0x36029a18, 0x36110000)from space 2560k, 0% used [0x36110000, 0x36110088, 0x36390000)to space 2560k, 0% used [0x36390000, 0x36390000, 0x36610000)tenured generation total 10240k, used 2738k [0x36610000, 0x37010000, 0x37010000)the space 10240k, 26% used [0x36610000, 0x368bc890, 0x368bca00, 0x37010000)compacting perm gen total 12288k, used 374k [0x37010000, 0x37c10000, 0x3b010000)the space 12288k, 3% used [0x37010000, 0x3706db50, 0x3706dc00, 0x37c10000)ro space 10240k, 51% used [0x3b010000, 0x3b543000, 0x3b543000, 0x3ba10000)rw space 12288k, 55% used [0x3ba10000, 0x3c0ae4f8, 0x3c0ae600, 0x3c610000)
如清单 13 所示,在程序首次运行时,程序完成后,b1 对象仍存储在年轻一代中。 在第二次运行之前,我们将项目晋升为长老的年龄降低到 1。 也就是说,所有通过GC一次的对象都可以直接转到老一代。 程序运行后,可以发现 B1 对象已经分配给了老一代。 如果你想让对象尽可能长时间地留在年轻一代,你可以设置一个大的阈值。

通常,稳定的堆大小对垃圾有好处**。 获得稳定堆大小的方法是使 -xms 和 -xmx 的大小相同,即最大堆与最小堆(初始堆)相同。 如果以这种方式设置,理论上堆大小在运行时是恒定的,稳定的堆空间可以减少 GC 的数量。 因此,许多服务器端应用程序将最大和最小堆设置为相同的值。

但是,不稳定的堆并非毫无用处。 稳定的堆大小可以减少 GC 的数量,但也会增加每个 GC 的时间。 将堆大小放在区域**中可以通过压缩堆空间来加快单个 GC 的速度,以便 GC 可以在系统不需要使用大内存时处理较小的堆。 考虑到这一点,JVM 还提供了两个用于压缩和扩展堆空间的参数。

xx:minheapfreeratio 参数用于设置堆空间的最小空闲率,默认值为 40。 当堆空间的可用内存小于此值时,JVM 会扩展堆空间。

xx:maxheapfreeratio 参数用于设置堆空间的最大空闲比率,默认值为 70。 当堆空间的可用内存大于此值时,将压缩堆空间以获得更小的堆。

当 -xmx 和 -xms 相等时,-xx:minheapfreeratio 和 -xx:maxheapfreeratio 参数无效。

清单 14堆大小设置。

import j**a.util.vector;public class heapsize thread.sleep(1);}
清单 14 显示了测试 -xx:minheapfreeratio 和 -xx:maxheapfreeratio 的效果,当运行参数为 -xx:+printgcdetails -xms10m -xmx40m -xx:minheapfreeratio=40 -xx:maxheapfreeratio =50 时,输出如清单 15 所示。

清单 15修改运行参数后的清单 14 输出。

[gc [defnew: 2418k->178k(3072k), 0.0034827 secs] 2418k->2226k(9920k),0.0035249 secs] [times: user=0.00 sys=0.00, real=0.03 secs][gc [defnew: 2312k->0k(3072k), 0.0028263 secs] 4360k->4274k(9920k),0.0029905 secs] [times: user=0.00 sys=0.00, real=0.03 secs][gc [defnew: 2068k->0k(3072k), 0.0024363 secs] 6342k->6322k(9920k),0.0024836 secs] [times: user=0.00 sys=0.00, real=0.03 secs][gc [defnew: 2061k->0k(3072k), 0.0017376 secs][tenured: 8370k->8370k(8904k),0.1392692 secs] 8384k->8370k(11976k), perm : 374k->374k(12288k)],0.1411363 secs] [times: user=0.00 sys=0.02, real=0.16 secs][gc [defnew: 5138k->0k(6336k), 0.0038237 secs] 13508k->13490k(20288k),0.0038632 secs] [times: user=0.00 sys=0.00, real=0.03 secs]
使用参数 -xx:+printgcdetails -xms40m -xmx40m -xx:minheapfreeratio=40 -xx:maxheapfreeratio=50,输出如清单 16 所示。

清单 16再次修改运行参数后的清单 14 输出。

[gc [defnew: 10678k->178k(12288k), 0.0019448 secs] 10678k->178k(39616k),0.0019851 secs] [times: user=0.00 sys=0.00, real=0.03 secs][gc [defnew: 10751k->178k(12288k), 0.0010295 secs] 10751k->178k(39616k),0.0010697 secs] [times: user=0.00 sys=0.00, real=0.02 secs][gc [defnew: 10493k->178k(12288k), 0.0008301 secs] 10493k->178k(39616k),0.0008672 secs] [times: user=0.00 sys=0.00, real=0.02 secs][gc [defnew: 10467k->178k(12288k), 0.0008522 secs] 10467k->178k(39616k),0.0008905 secs] [times: user=0.00 sys=0.00, real=0.02 secs][gc [defnew: 10450k->178k(12288k), 0.0008964 secs] 10450k->178k(39616k),0.0009339 secs] [times: user=0.00 sys=0.00, real=0.01 secs][gc [defnew: 10439k->178k(12288k), 0.0009876 secs] 10439k->178k(39616k),0.0010279 secs] [times: user=0.00 sys=0.00, real=0.02 secs]
从清单 16 中可以看出,堆空间中的垃圾在固定范围内是稳定的。 在稳定堆中,堆大小始终相同,每次使用 GC 时,都会协调一个 40MB 的空间。 因此,虽然减少了 GC 的数量,但单个 GC 的速度不如堆的速度。

吞吐量优先方法将最大限度地减少系统执行垃圾所需的总时间,因此请考虑关注系统吞吐量的并行收集器。 在高性能计算机上,可以使用以下参数执行吞吐量优先优化:

j**a –xmx3800m –xms3800m –xmn2g –xss128k –xx:+useparallelgc–xx:parallelgc-threads=20 –xx:+useparalleloldgc
XMX3800M XMS3800M:设置 j**a 堆的最大值和初始值。 通常,为了避免频繁的堆内存和降低系统性能,我们将最大堆设置为等于最小堆。 假设最小堆减少到最大堆的一半,即 1900M,那么 JVM 将尽可能在 1900MB 的堆空间中运行,如果是这样,GC 发生的概率就更高了;

XSS128K:减小线程堆栈的大小,允许剩余的系统内存支持更多的线程;

XMN2G:将年轻一代区域的大小设置为 2GB;

xx:+useparallelgc:年轻一代使用并行垃圾回收器。 这是一个以吞吐量为中心的收集器,可最大限度地减少 GC 时间。

xx:parallelgc-threads:设置用于垃圾的线程数**,通常可以设置为等于 CPU 的数量。 但是,在CPU数量较多的情况下,设置一个相对较小的值是合理的;

xx:+useparalleloldgc:设置旧一代使用的收集器。

CPU 通过寻址访问内存。 32位CPU的寻址宽度为0 0xffffffff,计算大小为4G,这意味着可以支持的最大物理内存量为4G。 但是,在实践中,我们遇到了这样的问题,程序需要使用4G内存,而可用的物理内存小于4G,导致程序不得不减少内存占用。

为了解决这些问题,现代CPU引入了内存管理单元(Memory Management Unit)。 MMU的核心思想是使用虚拟地址而不是物理地址,即CPU使用虚拟地址进行寻址,MMU负责将虚拟地址映射到物理地址。 MMU的引入解决了物理内存的局限性,对于程序来说,就像使用4G内存一样。

内存分页是在使用 MMU 的基础上提出的一种内存管理机制。 它将虚拟地址和物理地址拆分为固定大小 (4k) 的页面和页面框架,并保证页面的大小与页面框架相同。 就数据结构而言,这种机制确保了对内存的有效访问,并使操作系统能够支持非连续的内存分配。 当程序内存不足时,还可以将不常用的物理内存页转移到其他存储设备,例如磁盘,这称为虚拟内存。

在 Solaris 系统中,JVM 可以支持使用较大的页面大小。 使用大内存分页可以增强 CPU 的内存寻址能力,从而提高系统的性能。

j**a –xmx2506m –xms2506m –xmn1536m –xss128k –xx:++useparallelgc–xx:parallelgcthreads=20 –xx:+useparalleloldgc –xx:+largepagesizeinbytes=256m
xx:+largepagesizeInbytes:指定大页面的大小。

在计算 HEAP 内部分区(perm、new、old)的内存占用时,过多的内存分页会导致 JVM 分区超出正常范围,在最坏的情况下,区域将占用额外的页面大小。

为了减少应用垃圾邮件的暂停,首先要考虑的是使用一个专注于系统暂停的CMS,其次,为了减少全GC的数量,对象应该尽可能地留给年轻一代,因为年轻一代的小GC的成本远小于老一代的全GC。

j**a –xmx3550m –xms3550m –xmn2g –xss128k –xx:parallelgcthreads=20–xx:+useconcmarksweepgc –xx:+useparnewgc –xx:+survivorratio=8 –xx:targetsurvivorratio=90–xx:maxtenuringthreshold=31
xx:parallelgcthreads=20:设置 20 个线程作为垃圾**;

xx: +useparnewgc: 年轻一代使用并行器;

xx:+useconcmarksweepgc:老一辈使用 CMS 收集器来减少暂停;

xx:+survivorratio:将伊甸园区与幸存者区的比例设置为8:1。 稍大一点的幸存者空间会增加年轻一代寿命较短的物体的可能性**,如果幸存者不够大,一些短命的物体可能会直接进入老一辈,这对系统是不利的。

xx:targetsurvivorratio=90:设置幸存者区域的可用率。 如果设置为 90%,则允许使用 90% 的幸存者空间。 默认值为 50%。 因此,此设置会提高幸存者区域的使用率。 当存储的对象数超过此百分比时,这些对象将被压缩到老一代。 因此,此选项更有助于将对象保留在年轻一代中。

xx:maxtenuringthreshold:设置年轻受试者晋升为老一辈的年龄。 默认值为 15 次,即如果受试者存活了 15 次,则进入老一代。 此值设置为 31,以便对象尽可能保留在年轻一代区域。

通过对本文的学习,读者了解了如何在年轻一代中保留新对象,如何让大对象进入老一代,如何将对象的年龄设置为老一代,稳定 j**a 堆与湍流 j**a 堆,增加吞吐量以提高系统性能,尝试使用大内存分页, 使用非占有垃圾**等主题,通过示例和相应的输出说明,让读者对JVM优化有一个初步的了解。与其他文章一样,没有固定的优化,读者需要自己判断和练习才能找到正确的路径。

相似文章

    股票交易的宝贵经验总结

    一 引言。作为一名拥有数十年经验的投资者,我深知风险与机遇并存。在这个不确定的市场中,我经历了无数的起起落落和曲折,也积累了宝贵的经验和教训。今天,我想和大家分享这些宝贵的经验,希望能帮助大家在 中取得更好的成绩。第二,赚钱和赔钱的哲学。在 中,快钱往往意味着更高的风险。我周围的一些朋友,他们试图通...

    从研究生入学考试的失败中吸取了哪些教训?

    亲爱的朋友们,大家好!我是秦老师,今天我想和大家聊聊 考研不及格的经历有哪些总结?这个话题。首先,我想说的是,失败并不是一件可怕的事情。每个人都会经历失败,无论是在学校 工作还是生活中。但关键是我们如何从失败中吸取教训,从失败中吸取教训,然后勇敢地重新开始。因此,今天我想和大家分享一些从研究生入学考...

    水泥厂设备巡检经验总结

    来到水泥熟料烧成车间,从事水泥厂设备检验六年多,一路上检验了很多岗位,经历了很多设备事故,这些造就了我的检验知识,为我积累了很多宝贵的经验,我很高兴站在这里与大家分享检验经验。在座的各位领导 各位同事,不妨想想一下,你心目中的检查员是什么样子的?这不是简单的加油,写记录,而不仅仅是知道如何报告设备是...

    数据回顾与经验总结 2024年独立站黑五网销售数据报告

    据美国公司Odobie Analytics称,今年在美国 黑色星期五 月 日 支出创纪录亿美元,同比增长 网络星期一 月 日 销售额约为 亿美元 黑色星期五 和 网络星期一 之间的周末。销售额增长 至亿美元 在当前经济形势下,美国消费者对今年购物季的活动仍印象深刻,这主要是由于零售商对市场环境和消费...

    个人经历总结:在日本留学如何花更少的钱

    不知道同学们最担心留学的是什么,反正学长们当时最担心的是什么。花太多钱怎么办?学长不是豪门,久而久之,他总结出一套省钱的玩法。最近有同学问,学长就根据自己的经验告诉同学们。.去日本前的准备 首先是中介费,它不会向中介扔钱。有脑子可以试试DIY,想省事省力,可以找个免费的中介申请。如今,互联网上有很多...