`

一步步优化JVM五:优化延迟或者响应时间(2)[转]

    博客分类:
  • Java
 
阅读更多

优化CMS(concurrent garbage collection)
   使用CMS,old代的垃圾回收执行线程会和应用程序的线程最大程度的并发执行。这个提供了一个机会来减少最坏延迟的频率和最坏延迟的时间消耗。CMS没有执行压缩,所以可以避免old代空间的stop-the-world压缩(会让整个应用暂停运行)。

   优化CMS的目标就是避开stop-the-world压缩垃圾回收,然而,这个说比做起来容易。在一些的部署情况下,这个是不可避免的,尤其是当内存分配受限的时候。

   在一些特殊的情况下,CMS比其他类型的垃圾回收需要更多优化,更需要优化young代的空间,以及潜在的优化该什么时候初始化old代的垃圾回收循环。

   当从吞吐量垃圾回收器(Throughput)迁移到CMS的时候,有可能会获得更慢的MinorGC,由于对象从young代转移到old会更慢 ,由于CMS在old代里面分配的内存是一个不连续的列表,相反,吞吐量垃圾回收器只是在本地线程的分配缓存里面指定一个指针。另外,由于old代的垃圾回收线程和应用的线程是尽可能的并发运行的,所以吞吐量会更小一些。然而,最坏的延迟的频率会少很多,由于在old代的不可获取的对象能够在应用运行的过程被垃圾回收,这样可以避免old代的空间溢出。

   使用CMS,如果old代能够使用的空间有限,单线程的stop-the-world压缩垃圾回收会执行。这种情况下,FullGC的时间会比吞吐量垃圾回收器的FullGC时间还要长,导致的结果是,CMS的绝对最差延迟会比吞吐量垃圾回收器的最差延迟严重很多。old代的空间溢出以及运行了stop-the-world垃圾回收必须被应用负责人重视,由于在响应上会有更长的中断。因此,不要让old代运行得溢出就非常重要了。对于从吞吐量垃圾回收器迁移到CMS的一个比较重要的建议就是提升old代20%到30%的容量。

   在优化CMS的时候有几个注意点,首先,对象从young代转移到old代的转移率。其次,CMS重新分配内存的概率。再次,CMS回收对象时候产生的old代的分隔,这个会在可获得的对象中间产生一些空隙,从而导致了分隔空间。

   碎片可以被下面的几种方法寻址。第一办法是压缩old代,压缩old代空间是通过stop-the-world垃圾回收压缩完成的,就像前面所说的那样,stop-the-world垃圾回收会执行很长时间,会严重影响应用的响应时间,应该避开。第二种办法是,对碎片编址,提高old代的空间,这个办法不能完全解决碎片的问题的,但是可以延迟old代压缩的时间。通常来讲,old代越多内存,由于碎片导致需要执行的压缩的时间久越长。努力把old的空间增大的目标是在应用的生命周期中,避免堆碎片导致stop-the-world压缩垃圾回收,换句话说,应用GC最大内存原则。另外一种处理碎片的办法是减少对象从young代移动到old的概率,就是减少MinorGC,应用MinorGC回收原则。

   任期阀值(tenuring threshold)控制了对象该什么时候从young代移动到old代。任期阀值会在后面详细的介绍,它是HotSpot VM基于young代的占用空间来计算的,尤其是survivor(幸存者)空间的占用量。下面详细介绍一下survivor空间以及讨论任期阀值。

survivor空间

   survivor空间是young代的一部分,如下图所示。young代被分成了一个eden区域和两个survivor空间。
   
   两个survivor空间的中一个被标记为“from”,另外一个标记为“to”。新的Java对象被分配到Eden空间。比如说,下面的一条语句:
Map<String,String> map = new HashMap<String,String>();
    一个新的HashMap对象会被放到eden空间,当eden空间满了的时候,MinorGC就会执行,任何存活的对象,都从eden空间复制到“to” survivor空间,任何在“from” survivor空间里面的存活对象也会被复制到“to” survivor。MinorGC结束的时候,eden空间和“from” survivor空间都是空的,“to” survivor空间里面存储存活的对象,然后,在下次MinorGC的时候,两个survivor空间交换他们的标签,现在是空的“from” survivor标记成为“to”,“to” survivor标记为“from”。因此,在MinorGC结束的时候,eden空间是空的,两个survivor空间中的一个是空的。

   在MinorGC过程,如果“to” survivor空间不够大,不能够存储所有的从eden空间和from suvivor空间复制过来活动对象,溢出的对象会被复制到old代。溢出迁移到old代,会导致old代的空间快速增长,会导致stop-the-world压缩垃圾回收,所以,这里要使用MinorGC回收原则。

   避免survivor空间溢出可以通过指定survivor空间的大小来实现,以使得survivor有足够的空间来让对象存活足够的岁数。高效的岁数控制会导致只有长时间存活的对象转移到old代空间。

   岁数控制是指一个对象保持在young代里面直到无法获取,所以让old代只是存储长时间保存的对象。

   survivor的空间可以大小设置可以用HotSpot命令行参数:-XX:SurvivorRatio=<ratio>

   <ratio>必须是以一个大于0的值,-XX:SurvivorRatio=<ratio>表示了每一个survivor的空间和eden空间的比值。下面这个公式可以用来计算survivor空间的大小
survivor spave size = -Xmn<value>/(-XX:SurvivorRatio=<ratio>+2)
    这里有一个+2的理由是有两个survivor空间,是一个调节参数。ratio设置的越大,survivor的空间越小。为了说明这个问题,假设young代的大小是-Xmn512m而且-XX:SurvivorRatio=6.那么,young代有两个survivor空间且空间大小是64M,那么eden空间的大小是384M。

   同样假如young代的大小是512M,但是修改-XX:SurvivorRatio=2,这样的配置会使得每一个survivor空间的大小是128m而eden空间的大小是256M。

   对于一个给定大小young代空间大小,减小ratio参数增加survivor空间的大小而且减少eden空间的大小。反之,增加ratio会导致survivor空间减少而且eden空间增大。减少eden空间会导致MinorGC更加频繁,相反,增加eden空间的大小会导致更小的MinorGC,越多的MinorGC,对象的岁数增长得越快。

   为了更好的优化survivor空间的大小和完善young代空间的大小,需要监控任期阀值,任期阀值决定了对象会再young代保存多久。怎么样来监控和优化任期阀值将在下一节中介绍。
   
任期阀值

   “任期”是转移的代名词,换句话说,任期阀值意味着对象移动到old代空间里面。HotSpot VM每次MinorGC的时候都会计算任期,以决定对象是否需要移动到old代去。任期阀值就是对象的岁数。对象的岁数是指他存活过的MinorGC次数。当一个对象被分配的时候,它的岁数是0。在下次MinorGC的时候之后,如果对象还是存活在young代里面,它的岁数就是1。如果再经历过一次MinorGC,它的岁数变成2,依此类推。在young代里面的岁数超过HotSpot VM指定阀值的对象会被移动到old代里面。换句话说,任期阀值决定对象在young代里面保存多久。

   任期阀值的计算依赖于young代里面能够存放的对象数以及MinorGC之后,“to” servivor的空间占用。HotSpot VM有一个选项-XX:MaxTenuringThreshold=<n>,可以用来指定当时对象的岁数超过<n>的时候,HotSpot VM会把对象移动到old代去。内部计算的任期阀值一定不会超过指定的最大任期阀值。最大任期阀值在可以被设定为0-15,不过在Java 5 update 5之前可以设置为1-31。

   不推荐把最大任期阀值设定成0或者超过15,这样会导致GC的低效率。

   如果HotSpot VM它无法保持目标survivor 空间的占用量,它会使用一个小于最大值的任期阀值来维持目标survivor空间的占用量,任何比这个任期阀值的大的对象都会被移动到old代。话句话说,当存活对象的量大于目标survivor空间能够接受的量的时候,溢出发生了,溢出会导致对象快速的移动到old代,导致不期望的FullGC。甚至会导致更频繁的stop-the-world压缩垃圾回收。哪些对象会被移动到old代是根据评估对象的岁数和任期阀值来确定的。因此,很有必要监控任期阀值以避免survivor空间溢出,接下来详细讨论。

监控任期阀值

   为了不被内部计算的任期阀值迷惑,我们可以使用命令选项-XX:MaxTenuringThreshod=<n>来指定最大的任期阀值。为了决定出最大的任期阀值,需要监控任期阀值的分布和对象岁数的分布,通过使用下面的选项实现
 -XX:+PrintTenuringDistribution
    -XX:+PrintTenuringDistribution的输出显示在survivor空间里面有效的对象的岁数情况。阅读-XX:+PrintTenuringDistribution输出的方式是观察在每一个岁数上面,对象的存活的数量,以及其增减情况,以及HotSpot VM计算的任期阀值是不是等于或者近似于设定的最大任期阀值。

   -XX:+PrintTenuringDistribution在MinorGC的时候产生任期分布信息。它可以同其他选项一同使用,比如-XX:+PrintGCDateStamps,-XX:+PrintGCTimeStamps以及-XX:+PringGCDetails。当调整survivor空间大小以获得有效的对象岁数分布,你应该使用-XX:+PrintTenuringDistribution。在生产环境中,它同样非常有用,可以用来判断stop-the-world的垃圾回收是否发生。

   下面是一个输出的例子:

   Desired survivor size 8388608 bytes, new threshold 1 (max 15) 
   - age 1: 16690480 bytes, 16690480 total

   在这里例子中,最大任期阀值被设置为15,(通过max 15表示)。内部计算出来的任期阀值是1,通过threshold 1表示。Desired survivor size 8388608 bytes表示一个survivor的空间大小。目标survivor的占有率是指目标survivor和两个survivor空间总和的比值。怎么样指定期望的survivor空间大小在后面会详细介绍。在第一行下面,会列出一个对象的岁数列表。每行会列出每一个岁数的字节数,在这个例子中,岁数是1的对象有16690480字节,而且每行后面有一个总的字节数,如果有多行输出的话,总字节数是前面的每行的累加数。后面举例说明。

   在前面的例子中,由于期望的survivor大小(8388608)比实际总共survivor字节数(16690480)小,也就是说,survivor空间溢出了,这次MinorGC会有一些对象移动到old代。这个就意味着survivor的空间太小了。另外,设定的最大任期阀值是15,但是实际上JVM使用的是1,也表明了survivor的空间太小了。

   如果发现survivor区域太小,就增大survivor的空间,下面详细介绍如何操作。
   
设定survivor空间

   当修改survivor空间的大小的时候,有一点需要记住。当修改survivor空间大小的时候,如果young代的大小不改变,那么eden空间会减小,进一步会导致更频繁的MinorGC。因此,增加survivor空间的时候,如果young代的空间大小违背了MinorGC频率的需求,eden空间的大小同需要需要增加。换句话说,当survivor空间增加的时候,young代的大小需要增加。

   如果有空间来增加MinorGC的频率,有两种选择,一是拿一些eden空间来增加survivor的空间,二是让young的空间更大一些。常规来讲,更好的选择是如果有可以使用的内存,增加young代的空间会比减少eden的空间更好一些。让eden空间大小保持恒定,MinorGC的频率不会改变,即使调整survivor空间的大小。

   使用-XX:+PrintTenuringDistribution选项,对象的总字节数和目标survivor空间占用可以用来计算survivor空间的大小。重复前面的例子:
   Desired survivor size 8388608 bytes, new threshold 1 (max 15) 
   - age 1: 16690480 bytes, 16690480 total

   存活对象的总字节数是1669048,这个并发垃圾回收器(CMS)的目标survivor默认使用50%的survivor空间。通过这个信息,我们可以知道survivor空间至少应该是33380960字节,大概是32M。这个计算让我们知道对survivor空间的预估值需要计算对象的岁数更高效以及防止溢出。为了更好的预估survivor的可用空间,你应该监控应用稳定运行情况下的任期分布,并且使用所有的额外总存活对象的字节数来作为survivor空间的大小。

   在这个例子,为了让应用计算岁数更加有效,survivor空间需要至少提升32M。前面使用的选项是:
 -Xmx1536m -Xms1536m -Xmn512m -XX:SurvivorRatio=30
  那么为了保持MinorGC的频率不发生变化,然后增加survivor空间的大小到32M,那么修改后的选项如下:
 -Xmx1568m -Xms1568m -Xmn544m -XX:SurvivvorRatio=15
    当时young代空间增加了,eden空间的大小保持大概相同,且survivor的空间大小增减了。需要注意的时候,-Xmx、-Xms、-Xmn都增加了32m。另外,-XX:SurvivvorRatio=15让每一个survivor空间的大小都是32m (544/(15+2) = 32)。

   如果存在不能增加young代空间大小的限制,那么增加survivor空间大小需要以减少eden空间的大小为代价。下面是一个增加survivor空间大小,每一个survivor空间从16m增减加到32m,那么会见减少eden的空间,从480m减少到448m(512-32-32=448,512-16-16=480)。
-Xms1536m -Xms1536m -Xmn1512m -XX:SurvivorRatio=14
    再次强调,减少eden空间大小会增加MinorGC的频率。但是,对象会在young代里面保持更长的时间,由于提升survivor的空间。

   假如运行同样的应用,我们保持eden的空间不变,增加survivor空间的大小,如下面选项:
-Xmx1568m -Xms1568m -Xmn544m -XX:SurvivorRatio=15
   可以产生如下的任期分布:
   Desired survivor size 16777216 bytes, new threshold 15 (max 15)
- age 1: 6115072 bytes, 6115072 total
- age 2: 286672 bytes, 6401744 total
- age 3: 115704 bytes, 6517448 total
- age 4: 95932 bytes, 6613380 total
- age 5: 89465 bytes, 6702845 total
- age 6: 88322 bytes, 6791167 total
- age 7: 88201 bytes, 6879368 total
- age 8: 88176 bytes, 6967544 total
- age 9: 88176 bytes, 7055720 total
- age 10: 88176 bytes, 7143896 total
- age 11: 88176 bytes, 7232072 total
- age 12: 88176 bytes, 7320248 total

   从任期分布的情况来看,survivor空间没有溢出,由于存活的总大小是7320248,但是预期的survivor空间大小是16777216以及任期阀值和最大任期阀值是相等的。这个表明,对象的老化速度是高效的,而且survivor空间没有溢出。

   在这个例子中,由于岁数超过3的对象很少,你可能像把最大任期阀值设置为3来测试一下,即设置选项-XX:MaxTenuringThreshhold=3,那么整个选项可以设置为:
 -Xmx1568m -Xms1658m -Xmn544m -XX:SurvivorRatio=15 -XX:MaxTenuringThreshold=3
    这个选项设置和之前的选项设置的权衡是,后面这个选择可以避免在MinorGC的时候不必要地把对象从“from” survivor复制到“to” survivor。在应用运行在稳定状态的情况下,观察多次MinorGC任期分布情况,看是否有对象最终移动到old代或者显示的结果还是和前面的结果类似。如果你观察得到和前面的任期分布情况相同,基本没有对象的岁数达到15,也没有survivor的空间溢出,你应该自己设置最大任期阀值以代替JVM默认的15。在这个例子中,没有长时间存活的对象,由于在他们的岁数没有到达15的时候就被垃圾回收了。这些对象在MinorGC中被回收了,而不是移动到old代里面。使用并发垃圾回收(CMS)的时候,对象从young代移动到old代最终会导致old的碎片增加,有可能导致stop-the-world压缩垃圾回收,这些都是不希望出现的。宁可选择让对象在“from” survivor和“to” survivor中复制,也不要太快的移动到old代。

   你可能需要重复数次监控任期分布、修改survivor空间大小或者重新配置young代的空间大小直到你对应用由于MinorGC引起的延迟满意为止。如果你发现MinorGC的时间太长,你可以通过减少young代的大小直到你满意为止。尽管,减少young代的大小,会导致更快地移动对象到old代,可能导致更多的碎片,如果CMS的并发垃圾回收能够跟上对象的转移率,这种情况就比不能满足应用的延迟需求更好。如果这步不能满足应用的MinorGC的延迟和频率需求,这个时候就有必要重新审视需求以及修改应用程序了。

   如果满足对MinorGC延迟的需求,包括延迟时间和延迟频率,你可以进入下一步,优化CMS垃圾回收周期的启动,下节详细介绍。

分享到:
评论

相关推荐

    一步步优化JVM.docx

    3. **响应时间**:用户请求到系统响应之间的时间,优化响应时间可以提升用户体验。 4. **内存占用**:优化内存使用可以减少系统资源消耗,但可能导致性能下降,需要找到平衡点。 5. **启动时间**:优化启动时间...

    WebSphere性能优化之二:JVM的运行效率.doc

    2. 针对停顿时间优化(-Xgcpolicy:optavgpause):通过并发GC来缩短停顿时间,牺牲部分吞吐量。 3. 分代并发(-Xgcpolicy:gencon):针对短期和长期存活对象采取不同处理,适合短生命周期对象多的应用,兼顾停顿时间...

    jvm初识及JIT优化

    jvm初识及JIT优化jvm初识及JIT优化jvm初识及JIT优化jvm初识及JIT优化jvm初识及JIT优化jvm初识及JIT优化jvm初识及JIT优化jvm初识及JIT优化jvm初识及JIT优化jvm初识及JIT优化jvm初识及JIT优化jvm初识及JIT优化jvm初识...

    深入JVM内核—原理、诊断与优化

    《深入JVM内核—原理、诊断与优化》是一份深度探索Java虚拟机(JVM)的视频教程,旨在帮助开发者全面理解JVM的工作机制,掌握性能诊断技巧,并能进行有效的优化。本教程覆盖了从基础到高级的JVM主题,不仅适用于Java...

    JVM内存模型深度剖析与优化.pdf

    JVM内存模型深度剖析与优化 JVM内存模型是Java虚拟机的核心组件之一,它直接影响着Java应用程序的性能和可靠性。本文将深入剖析JVM内存模型的结构和工作机理,并讨论如何优化JVM参数以提高Java应用程序的性能。 一...

    jvm详细介绍以及性能优化方法

    优化Tomcat涉及多个方面,包括调整连接器配置(如最大连接数、超时时间)、线程池设置、Web应用的部署优化(如减少session使用、压缩响应内容)以及启用HTTP/2协议等。此外,监控Tomcat的运行状态,如CPU使用率、...

    深入JVM内核—原理、诊断与优化视频教程-2.JVM运行机制

    2. **内存区域划分**:JVM内存分为堆内存和栈内存,其中栈内存又包括方法区、虚拟机栈、本地方法栈。堆内存用于存储对象实例,而栈内存则存储线程私有的局部变量、方法参数和运算结果。方法区存储类的信息,如常量池...

    淘宝JVM优化实践-长仁.pdf

    2. **TaobaoJVM的发展历程**:2010年4月,淘宝开始进行JVM实现优化,同年7月,实现了第一个优化点——GC Invisible Heap。2010年10月,第一个优化的JVM版本TaobaoJVM上线Beta测试。2011年7月,淘宝与Oracle达成合作...

    jvm优化参数配置

    "jvm优化参数配置"是确保Tomcat稳定运行的关键环节,能够提高应用的响应速度,减少内存泄露,提升系统整体性能。以下是对JVM参数优化的详细解释: 1. **内存配置**: - **堆内存(Heap Memory)**:分为新生代...

    openjdk jvm zgc低延迟

    OpenJDK JVM 的 ZGC(Z Garbage Collector)是一个针对低延迟需求设计的垃圾收集器。这个GC的主要目标是提供可扩展的低延迟性能,即使在处理多TB大小的堆内存时也能保持极短的垃圾收集暂停时间。Oracle公司开发的ZGC...

    JVM详解:带书签超清文字版.pdf

    #### 一、Java与JVM概览 **1.1 Java定义** Java是一种广泛使用的面向对象的编程语言,以其强大的跨平台兼容性和安全性著称。Java程序能够运行在任何安装了Java运行环境(JRE)的设备上,这得益于Java虚拟机(JVM)...

    JVM性能优化(PPT)

    JVM性能优化是一项细致而关键的任务,能够显著提升程序的运行效率,减少资源消耗,提高系统稳定性。本PPT详细探讨了JVM的工作原理以及如何进行性能优化。 1. **JVM架构** JVM分为几个主要区域:方法区、堆、栈、...

    深入理解JVM内幕:从基本结构到Java 7新特性

    Java虚拟机(JVM)是Java程序的核心组件,它负责解析和执行Java字节码,使得Java具有“一次编译,到处运行”的特性。本文将深入探讨JVM的基本结构、执行流程,以及Java 7的新特性。 首先,JVM由Java API和JRE组成,...

    JVM性能优化:线程锁优化.docx

    Java虚拟机(JVM)性能优化的一个重要领域是线程锁优化,这直接影响到多线程应用程序的效率和并发性能。线程锁是确保多线程环境下数据一致性和线程安全的关键机制。以下是对线程锁优化的一些核心知识点的详细说明: ...

    jvm优化中文版

    综上所述,JVM优化是一个系统性工程,涵盖了从基础知识点到监控分析再到性能调优的一系列知识。理解JVM内部机制和不同JVM实现之间的区别,对于高效地优化Java应用性能是必不可少的。通过合理地应用各种优化手段,...

    深入jvm 内核-原理,诊断于优化视频教程

    - **案例2:大数据处理系统调优**:针对大数据处理系统,通过对JVM参数的调整以及合理的内存分配策略,显著提升了系统的处理速度和吞吐量。 #### 六、总结 本教程旨在帮助开发者深入了解JVM的工作原理及其内部机制...

    JVM系列之性能调优参考手册(实践篇).pdf

    2. 非标准参数:以“-X”开头,提供给JVM实现特定的功能,但这些功能并非所有JVM都支持,可能在不同版本间也有变化。非标准参数通常用于更精细的控制,比如“-Xint”强制解释执行、“-Xcomp”强制编译执行等。 3. ...

    JVM内存配置优化

    ### JVM内存配置优化 #### 一、理解JVM内存模型 在进行JVM内存配置优化之前,我们需要了解Java虚拟机(JVM)的内存结构。Java的逻辑内存模型大致分为几个部分: 1. **堆内存(Heap)**:主要用于存储对象实例、数组...

Global site tag (gtag.js) - Google Analytics