一步步优化JVM六:优化吞吐量

若是你已经进行完了前面的步骤了,那么你应该知道这是最后一步了。在这一步里面,你须要测试应用的吞吐量和为了更高的吞吐量而优化JVM。html


   这一步的输入就是应用的吞吐量性能要求。应用的吞吐量是在应用层面衡量而不是在JVM层面衡量,所以,应用必需要报告出一些吞吐量指标或者应用的某些操做的吞吐量性能指标。观察到的吞吐量指标而后用能够用来和应用须要的性能指标进行比较,若是达到或者超过要求,那么这一步就完成了。若是你须要更好的吞吐量的话,有一些JVM优化能够去作。
 
   这一步的另一个输入就是,有多少内存能够供应用使用,就想前面说的GC最大化内存原则,越多可用的内存,性能就更好。这条原则不只仅适用于吞吐量优化,一样适用于延迟优化。
 
   应用的吞吐量需求多是没法知足的。若是是这种状况,那么就须要从新审视应用吞吐量的需求,应用就须要修改或者改变部署模型。若是上面的一种或者多种状况发生了,那么你须要从新进行前面的优化步骤。
 
   在前面的步骤里面,你可能使用吞吐量垃圾回收器解决了问题(经过-XX:+UseParallelOldGC或者-XX:+UsePrallelGC),或者你调整到并发垃圾回收器(CMS)来解决的问题。若是使用的CMS来解决的问题,下面有一些选项来提高应用的吞吐量,下面详细介绍。若是是使用的吞吐量垃圾回收器,咱们将在CMS以后介绍。
   
   CMS吞吐量优化
 
   可以用来提高CMS吞吐量的选项数量有限,下面列出一些能够单独使用或者联合使用的选项:
 
   一、使用一些额外的命令选项,在后面的“额外的性能命令行选项”中详细介绍。
   二、增长young代的空间大小,增长young代的空间大小,能够减小MinorGC的频率,就可以减小在一段时间里面MinorGC占用的时间。
   三、增长old代的空间大小,增长old代的空间,能够减小CMS垃圾回收的频率,减小潜在的碎片,能够减小
stop-the-world垃圾回收。
   四、进一步优化young代堆大小,已经在前面的“优化延迟和响应时间”里面说过了,以及如何优化eden空间任务后和survivor空间大小以减小对象从young代移动到old也在前面已经说过了。须要注意的是,当优化eden和survivor空间大小的时候考虑到一些权衡。
   五、优化CMS周期的启动,也在前面说过了。
   
   任何上面提到的优化,或者组合使用上面的选择,都是减小垃圾回收器占用CPU时间,把CPU留给应用计算。前面两种选择,提供一种可能性来提高吞吐量,可是会有stop-the-world垃圾回收的风险,会增长延迟。
 
   做为指导,不考虑CMS,MinorGC的次数应该减小10%,你可能只能下降1%-3%。一般来说,若是只能减小3%甚至更少,那么可以提高的吞吐量空间恐怕就有限了。
 
   吞吐量垃圾回收器优化
 
   优化吞吐量垃圾回收器的目标是避免FullGC或者理想状况下,避免在稳定状态下FullGC。这个须要优化对象的岁数,这个能够经过制定survivor空间优化完成。你可让eden空间更大,能够减小MinorGC的次数。我知道当对象的任期或者岁数达到必定值的时候就会移动到old代,而这个任期就是对象经历MinorGC的次数,MinorGC的次数越少,对象任期增加越慢,就有可能被MinorGC回收掉,而不是进入old代。
 
   使用HotSpot VM的吞吐量垃圾回收器,能够经过-XX:+UseParallelOldGC和-XX:+UsePrallelGC,这样能够提供最好的吞吐量。吞吐量垃圾回收器利用了一种叫作自适应大小的特性,自适应大小是基于对象的分配和存活率来自动改变eden空间和survivor空间大小,目的是优化对象的岁数分布。自适应大小的企图是提供易用性,容易优化JVM,以至于提供可靠的吞吐量。自适应大小在大多数应用下,可以很好的工做,可是关闭自适应大小以及优化eden空间和survivor空间以及old代空间是一个探索提高应用吞吐量的一种办法。关闭自适应大小会改变应用的程序的灵活性,尤为是在修改应用程序,以及随着时间的推移应用的数据发生了变化。
 
   关闭自适应大小可使用选项:
 
 
   -XX:-UseAdaptiveSizePolicy
 
   注意在“-XX”后面的“-”代表关闭UseAdapivieSizePolicy提供的特性。只有吞吐量垃圾回收器支持这个选项。在其余的垃圾回收器上使用这个选项是无用的。
 
   另一个可选的命令行选项,能够产生关于survivor空间占用更详细的信息,关于survivor空间是否溢出,对象是否从young代移动到old代,选项是-XX:+PrintAdaptiveSizePolicy。这个选项最好和-XX:+PrintGCDetails以及-XX:+PrintGCDateStamps或者-XX:+PrintGCTimeStamps一块儿使用。下面是一个垃圾回收的例子-XX:+PrintGCDateStamps, -XX:PrintGCDetails, -XX:-UseAdaptiveSizePolicy (关闭自适应大小), 以及-XX:+PrintAdaptiveSizePolicy:
 
   2010-12-16T21:44:11.444-0600:
[GCAdaptiveSizePolicy::compute_survivor_space_size_and_thresh:
survived: 224408984
promoted: 10904856
overflow: false
[PSYoungGen: 6515579K->219149K(9437184K)]
8946490K->2660709K(13631488K), 0.0725945 secs]
[Times: user=0.56 sys=0.00, real=0.07 secs]
 
   和之前不一样的是,以GCAdaptiveSizePolicy开头的一些额外信息输出来了,survived标签代表“to” survivor空间的对象字节数。在这个例子中,survivor空间占用量是224408984字节,可是移动到old代的字节数却有10904856字节。overflow代表young代是否有对象溢出到old代,换句话说,就是代表了“to” survivor是否有足够的空间来容纳从eden空间和“from”survivor空间移动而来的对象。为了更好的吞吐量,指望在应用处于稳定运行状态下,survivor空间不要溢出。
 
   为了开始优化,你应该关闭自适应大小以及获取在垃圾回收器日志里面额外的survivor空间统计信息,使用这两个选项-XX:-UseAdaptiveSizePolicy以及-XX:+PrintAdaptiveSizePolicy。这样提供了一些初始化的信息,以帮助作出优化决定。假如以前使用下面的命令行选项:
 
 
 -Xmx13g -Xms13g -Xmn4g -XX:SurvivorRatio=6 -XX:+UseParallelOldGC -XX:PrintGCDateStamps 
-XX:+PrintGCDetails

 
 
  那么,就应该以下一组命令行选项,来关闭自适应大小和捕获survivor空间统计信息:
 
 
 -Xmx13g -Xms13g -Xmn4g -XX:SurvivorRatio=6
-XX:+UseParallelOldGC -XX:PrintGCDateStamps -XX:+PrintGCDetails
-XX:-UseAdaptiveSizePolicy -XX:+PrintAdaptiveSizePolicy

 
 
   首先在应用稳定运行状态下寻找FullGC信息,包括日期和时间戳能够用来识别出应用是否从启动状态进入了稳定状态。举例,若是你知道应用启动须要30秒时间,那么在应用启动30秒以后才观察垃圾回收。
 
   观察FullGC信息,你可能会发现有一些短存活时间的对象移动到了old代空间,若是FullGC发生了,首先要肯定是old代的空间是FullGC以后存活对象的1.5倍。若是有须要,增长old代的空间来保持1.5倍的指标,这样,能够保证old代有足够的空间来处理不在预期内的转移率(致使短的存活时间的对象移动到old代)或者一些未知的状况——致使了对象的转移过快,拥有这样的额外空间,能够延迟甚至可能可以阻止FullGC的发生。
 
   在肯定了old代有足够的空间以后,就须要观察MinorGC的情况。首先须要观察survivor空间是否溢出,若是survivor空间溢出了,那么overflow标签会是true,不然,overload字段会是false。下面是一个survivor空间溢出的例子:
 
   2010-12-18T10:12:33.322-0600:
[GCAdaptiveSizePolicy::compute_survivor_space_size_and_thresh:
survived: 446113911
promoted: 10904856
overflow: true
[PSYoungGen: 6493788K->233888K(9437184K)]
7959281K->2662511K(13631488K), 0.0797732 secs]
[Times: user=0.59 sys=0.00, real=0.08 secs]
 
   若是survivor空间溢出,对象会再达到任期阀值或者消亡以前被移动到old代。换句话说,对象过快的移动到old代。频繁的survivor空间溢出会致使FullGC,下面说如何优化survivor。
   
优化survivor空间
 
   优化survivor空间的目标是保持或者老化短期存活动的对象在young代中,一直到不得不移动到old代中。开始查看每个MinorGC,尤为是存活的对象字节数。须要注意一点的是,为了不应用启动的时候对象对后面分析的干扰,能够考虑放弃应用刚进入稳定状态的前面5到10个MinorGC信息。
 
   每次MinorGC以后的存活对象数量能够经过-XX:+PrintAdaptiveSizePolicy来查看。在下面的例子中,survivor对象的字节数是224408984。
 
2010-12-16T21:44:11.444-0600:   
[GCAdaptiveSizePolicy::compute_survivor_space_size_and_thresh:
survived: 224408984
promoted: 10904856
overflow: false
[PSYoungGen: 6515579K->219149K(9437184K)]
8946490K->2660709K(13631488K), 0.0725945 secs]
[Times: user=0.56 sys=0.00, real=0.07 secs]
[GCAdaptiveSizePolicy::compute_survivor_space_size_and_thresh:
survived: 224408984
promoted: 10904856
overflow: false
[PSYoungGen: 6515579K->219149K(9437184K)]
8946490K->2660709K(13631488K), 0.0725945 secs]
[Times: user=0.56 sys=0.00, real=0.07 secs]
 
   使用最大存活对象数量以及知道目标survivor空间的占用量,你能够决定出最差survivor空间大小,以使得让对象老化得更加高效。若是目标survivor空间的占用率没有经过-XX:TargetSurvivorRatio=<percent>指定,那么目标survivor空间的占用率是50%。
 
   首先为最差的场景优化survivor空间,这个须要找出在MinorGC以后最大的存活对象数量,注意能够忽略应用进入稳定状态前面的5到10个MinorGC。能够经过awk或者perl脚原本完成这项工做。
 
   调整survivor空间的大小,不是仅仅修改survivor空间的大小以使得比存活的对象字节数更大那么简单。须要记住的是,若是不增长young代的空间大小,而增长survivor空间的大小,会减小eden空间的大小,这样会致使频繁的MinorGC,从而是的对象的老化速度加快,更快的进入old代,又会致使FullGC。因此,须要同步增长young代的空间大小。若是不增长old的空间,那么就有可能形成频繁的FullGC甚至内存溢出错误。所以,若是有能够获取的空间,须要同步增长Java堆的空间。
 
   一样建议,HotSpot Vm使用默认的目标survivor空间占用率(50%),若是使用了-XX:TargetSurvivorRatio=<percent>,会使用<percent>做为MinorGC以后目标survivor空间占用率。若是survivor空间的占用率可能超过这个目标值,会在对象达到最大岁数以前把对象移动到old代去。
 
   经过一个例子详细说明,考虑用下面的命令选项:
 
-Xmx13g -Xms13g -Xmn4g -XX:SurvivorRatio=6
-XX:+UseParallelOldGC -XX:-UseAdaptiveSizePolicy
-XX:PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintAdaptiveSizePolicy

 
 
   总共的Java堆空间是13g,young代是4g,old代是9g,survivor空间的大小是4g/(6+2)=512M。假如一个应用的存活对象是470M,因为没有明确指定-XX:TargetSurvivorRatio=<percent>,那么默认的目标survivor空间占用率是50%,那么最小的survivor空间应该是940M,也就是最坏的状况,须要设置940M的survivor空间。

   从上面的例子来看,4g的young代空间被分隔成两个512M的survivor空间和一个3g的eden空间。刚才分析的最坏状况分配给survivor的空间是940M,差很少和1g至关。为了保持对象老化速率,即保持MinorGC的频率,eden空间须要保持在3g。所以,young代须要给每个survivor空间1g内存以及3g的eden空间,那么young代须要增长到5g,也就是说young代须要增长1g空间,须要把-Xmn4g选项改为-Xmn5g选项。比较的理想的状况是,同步把Java堆的空间也增长1g。可是若是内存不够用,须要保证old代空间大小至少是存活对象的1.5倍。
 
   假设应用的内存需求知足,增长survivor空间占用后的命令选项是:
 
    
 -Xmx14g -Xms14g -Xmn5g -XX:SurvivorRatio=3
-XX:+UseParallelOldGC -XX:-UseAdaptiveSizePolicy
-XX:PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintAdaptiveSizePolicy

 
 
   old空间仍是9g,young代的空间是5g,比以前大了1g,eden仍是3g,每个survivor空间是1g。
 
   你可能须要重复屡次设定大小,直到知足内存占用的条件下到达吞吐量的峰值。吞吐的峰值通常都是在对象最有效的老化的时候的达到的。
 
   一般的建议是,吞吐量垃圾回收器的垃圾回收的开销应该小于5%。若是你只能把这个开销下降1%甚至更少,你可能须要使用除本章描述以外的特别努力和很大的开销来优化JVM。
  
优化Parallel GC线程
 
   吞吐量垃圾回收器的线程数的优化一样基于有多少应用运行在同一个系统里面以及硬件平台。就像前面的“优化CMS”里面提到的,若是是多个应用运行在同一个系统上面,建议使用比垃圾回收器默认使用的线程数更少的线程数,使用选项是-XX:ParallelGCThreads=<n>.
 
  另外,因为大量的垃圾回收线程同时执行,垃圾回收可能会严重影响其余应用的性能。因为Java 6 Update 23以后,默认的垃圾回收线程是执行Runtime.availableProcessors()得到的,若是这个方法的返回值小于等于8,那么就用这个返回值,若是比8更大,那么就取这个值的5/8。若是运行多个应用,能够根据应用的状况来分配线程数,若是应用的消耗是至关的,那么就用CPU的内核数除以应用数获得每个应用能够分配的线程。若是应用的load不至关,那么就能够根据应用的实际状况来衡量和分配。
   
下一步
   若是你到这一步都尚未可以达到吞吐量的要求,那么能够尝试后面的“额外的性能选项”,若是仍是没法达到,就只能修改应用或者JVM部署结构了。若是进行了修改应用或者修改了部署结构,你须要从新作前面的各个步骤。
   可能会用到的一些边缘场景,下面一节介绍。
边缘问题

   在某些场景下,按照前面的一步步优化指导没法产生效果。这一节说明一下这些状况。
 
   一些应用分配了一些少许的很是大的长时间存活的对象。这样的场景须要须要young代的空间比old代更大。
 
   一些应用会经历不多的对象转移。这样的场景可能须要old代的空间远远大于存活对象的大小,因为old的占用量增加率很小。
 
   一些应用有小延迟需求,会使用CMS垃圾回收器,并且使用小young代空间(以至于MinorGC时间更短),以及大的old代空间。在这种配置下,对象会快速的从young代移动到old代,替代了高效老化对象。另外,CMS垃圾回收移动后的对象,碎片的可能性经过大的old代空间来解决。
 
   下一节介绍一些其余的HotSpot VM选项来提高应用的性能。
 
其余一些的性能命令行选项
 
   几个可选的前面有提到的命令选项能够用来提高Java应用的延迟和吞吐量性能,这些选项是经过JIT编译器代码优化以及其余的HotSpot VM优化能力。下面介绍这些特性以及相适应的命令选项。
 
   最新和最大优化
   当新的性能优化集成到HotSpot VM中以后,能够经过-XX:+AggressiveOpts选项来启用。
 
   经过选项来引入新的优化,能够把最新及最大的优化和以及通过长时间使用证实是稳定的优化分离开。应用一般更但愿得到更好的稳定性,毕竟最新的优化可能会致使未知的问题。可是若是应用须要提高任何能够提高的性能优化的时候,可使用命令选项来启用这些优化。
 
   当新的优化被证实是稳定的以后,他们会被默认使用,也许须要升级几个版本以后才会变成默认。
 
   使用-XX:+AggressiveOpts命令选项以后,须要考虑到性能的提高,一样也须要考虑到性能提高所带来的不稳定风险。
 
   逃避分析
 
   逃避分析是一个种分析Java对象范围的技术,在特殊状况下,一个线程分配的对象可能被另一个线程使用,这个对象就叫着“逃避”。若是对象没有逃避,额外的优化技术能够应用,所以,这种优化技术叫作逃避分析。
 
   在HotSpot VM里面的逃避分析优化能够经过命令行选项:
 
 
   -XX:+DoEscapeAnalysis
   
   这是在Java 6 update 14中引入的,并且自动启用经过-XX:+AggressiveOpts。在Java 6 update 23中是默认开启的。
 
   经过逃避分析,HotSpot VM JIT编译器,可应用下面的优化技术:
 
   一、对象爆炸:对象爆炸是一种对象的属性存储在Java堆之外并且可能潜在的消失。好比说,对象属性能够直接被放置到内存的寄存器里面或者对象被分配栈里面而不是堆里面。
 
   分等级替换:分等级替换是一种用来减小内存使用的优化技术,考虑下面的Java类,表现为保存长方形的长和宽:
public class Rectangle {
  int length;
  int width;
}
   HotSpot VM能够优化内存分配和使用非逃避的Rectangle类的实例经过把长和宽都直接存储到CPU的寄存器而不是分配Rectangle对象,结果是当时须要使用长和宽属性的时候,不须要再复制到CPU的寄存器。这个能够减小内存的读取。
 
   二、线程栈分配:顾名思义,线程栈分配是一种把对象分配到线程栈中,而不是Java堆里面的优化技术。一个对象永远不逃避,就能够放置到线程栈框架里面,因为没有其余线程须要看到这个对象。线程栈分配能够减小对象分配到Java堆,能够减小GC的频率。
 
   三、消灭同步:若是线程分配的对象历来不会逃避,并且这个线程锁定了这个对象,这个锁可能会被JIT编译器消灭,毕竟没有其余线程会使用这个对象。
 
   四、消灭垃圾回收读写障碍:若是线程分配的对象历来不会逃避,只会被当前线程使用,因此在其余对象里面存储它的地址不须要障碍。读或者写障碍只有在对象会被其余线程使用的时候才有须要。
 
有偏见的锁
 
   有偏见的锁是使得锁更偏心上次使用到它线程。在非竞争锁的场景下,即只有一个线程会锁定对象,能够实现近乎无锁的开销。
 
   有偏见的锁,是在Java 5 update 6引入的。经过HotSpot VM的命令选项-XX:+UseBiasedLocking启用。
 
   Java 5 HotSpot JDK须要明确的命令来启用这个特性,在使用-XX:+AggressiveOpts选项,有偏见的锁会Java 5中会被自动启用。在Java 6中是默认启用的。
 
   各类经历告诉咱们这个特性对大多数应用仍是很是有用的。而后,有一些应用使用这个属性不必定可以表现的很好,好比,锁被一般不被上次使用它的同一个线程使用。对于Java应用来讲,因为stop-the-world安全点操做须要取消偏见,这样能够经过使用-XX:-UseBiaseLocking来得到好处。若是你不清楚你的应用是什么状况,能够经过分别设置这两个选项来测试。
 
大页面
 
   在计算机系统中,内存被分为固定大小的区块,这个区块就叫作页(page)。内存的存取是经过程序把虚拟内存地址转换成物理内存地址实现的。虚拟到物理地址是在一个块表里面映射的。为了减小每次存取内存的时候使用页表的消耗,一般会使用一种快速的虚拟到物理地址转换的缓存。这个缓存叫作转换后备缓冲区(translation lookaside buffer),简称TLB。
 
   经过TLB来知足虚拟到物理地址的映射请求,会比遍历页表来找到映射关系快不少,一个TLB一般包含指定数量的条目。一个TLB条目是一个基于页大小虚拟到物理地址映射,所以,更大的页大小容许一个条目或者一个TLB有更大的内存地址范围。在TLB中有更普遍的地址,更少的地址转换请求在TLB中不命中,就能够减小遍历页表(page table)操做。使用大页的目的就是减小TLB的不命中。
 
   Oracle solariz,Linux 以及Windows都支持HotSpot VM使用大页。一般处理器能够支持几种页大小,不过不一样的处理器各不相同。另外,操做系统配置须要使用大页。
 
   下面说说怎么样在Linux下使用大页(Large Page)
 
Linux下的大页面
  在写做本书的时候,在Linux下使用大页,除使用-XX:+UseLargePages命令选项之外,须要修改操做系统配置。Linux的修改操做具体和发行版本以及内核有关系。为了合理的启用Linux下的大页,能够征询Linux管理员的意见或者阅读Linux发行文档。一旦使用了Linux操做系统配置已经修改,-XX:+UseLargePage命令行选项就必需要使用了。好比:
 
   $ java -server -Xmx1024m -Xms1024m -Xmn256m -XX:+UseLargePages ...
  若是大页没有被合理设置,HotSpot VM一样会接受-XX:+UseLargePages是一个有效的选项,不过会报告没法获取大页,并且会退回操做系统的默认页大小。
相关文章
相关标签/搜索