《java performance》翻译 第七章jvm调优:堆内存设置

    设置jvm堆内存
    到目前为止,还没有为调优jvm的内存占用采取任何调优动作。下面这个步骤讲述了如何来确定一个应用应该使用的jvm内存大小。这个步骤的目标是帮助调优人员找出应用的常驻内存大小,因为应用的常驻内存大小为配置应用的堆内存提供了很好的参考作用。常驻内存大小是指应用在稳定运行状态需要使用的堆大小,另外一个角度来理解的话,可以认为是jvm在经历过一次fgc之后的内存占用大小。
    首先要确定在你的部署机器上能够给需要调优的jvm进程分配多大的内存空间,排除其他进程的内存消耗后,分配内存的原则是:如果你只在机器上部署一个jvm进程,那么机器上所有剩余的内存空间都可以分配给这个jvm进程。如果要部署多个jvm进程,那么就要合理分配每个jvm上的内存。
    在采取内存分配调优策略之前,需要先了解一下jvm的堆内存布局,以便更好得在内存分配调优时采取措施。
    Hotspot堆内存布局参数
     《java performance》翻译 第七章jvm调优:堆内存设置_第1张图片
    hotspot 虚拟机有三个主要的内存区域:年轻代、年老代和永久代。在hotspot虚拟机中,新创建的对象被分配到年轻代中,经过多次minor gc依然存活的对象被晋升到年老代中。而永久代中则用来放置java class的元数据、intern的string以及类静态变量等。
    -Xms参数和-Xmx参数分别用来设置年轻代加上年老代这两块堆内存的最小值和最大值。-Xms标识了堆内存的初始值和最小值,而-Xmx标识了最大值。当应用的可用内存数减少时,jvm会在-Xmx参数指定的大小范围内对对内存进行调整。一般情况下,建议把-Xms的值和-Xmx的值设成一样的值,因为堆内存大小的调整需要进行fgc,而fgc会对应用的吞吐量和响应时间都产生影响。
    年轻代大小的指定可以通过下列任一个参数进行:
    -XX:NewSize=<n> g|m|k设定了年轻代的初始和最小值。
    -XX:MaxNewSize=<n> g|m|k设定了年轻代的最大值。
    -Xmn参数设置了年轻代的初始、最小和最大值。这个参数设定时,年轻代的最小值和最大值被设置成同样大小。
    年老代的值则由堆内存的总设置大小和年轻代设置的大小决定,年老代的最小值为-Xms设定值减去-XX:NewSize设定值,年老代最大值为-Xmx设定值减去-XX:MaxNewSize设定值。
    永久代的大小由如下参数设置:
    -XX:PermSize=<n> g|m|k设定了永久代的初始和最小值。
    -XX:MaxPermSize=<n> g|m|k则设定了永久代的最大值。
    一般情况推荐把-XX:PermSize设置成和-XX:MaxPermSize的值为相同的值,因为永久代大小的调整也会导致堆内存需要触发fgc。
    如果没有设置这些java 堆内存分配的参数,jvm会根据系统的配置来确定默认的jvm堆内存设置参数。
    当堆内存中任意一块的内存区域不足,而jvm又需要在这一块内存上分配内存时,垃圾回收就执行了。当年轻代内存不足时,jvm会触发minor gc。minor gc是一种比fgc耗时更短的垃圾回收方式。经过几次minor gc,如果对象还存活着,会被晋升到年老代中。随着晋升对象的增加,当年老代中内存也不足时,fgc就发生了。实际上,jvm内部还会有特定的算法来判断“年老代中的对象不足以存放下一次minor gc晋升的对象”,根据这个判断触发fgc。此外,当永久代中的内存不足以jvm来分配java的类型元数据时,也会发生fgc。
     当因为年老代不足发生fgc时,即使永久代中的内存并没有满,永久代和年老代中的数据都会被回收。如果因为永久代不足触发fgc,年老代和永久代都会被垃圾回收。此外,如果没有在jvm参数中设置-XX:ScavengeBeforeFullGC参数,默认的fgc会先触发minor gc,-XX:ScavengeBeforeFullGC设置了在fgc之前不进行minor gc。
    设定堆初始值
    要开始调优jvm的堆内存大小,首先要设定一个jvm的初始内存大小。这一节开始会以比应用所需要的内存大得多的内存开始运行应用,这么做的目的是通过这个步骤收集一些应用正常运行的初始化数据,然后再进行进一步的内存调优。
    首先,使用-XX:UseParallelGC设置jvm的gc方式为并行GC。然后,如果你对应用需要的内存数量有一个大概的估计的话,你可以设置-Xms和-Xmx到你估计的这个值,如果你对应用需要的内存没有概念,直接不指定jvm的堆大小,即让jvm使用自己的初始化配置,这个配置和当前机器的配置有关。因为,这个步骤只是内存调优的初始步骤,我们随后将对应用的堆内存进行重新设置。
     接着,给你的应用以适当的请求压力,这个压力的大小和请求的内容应该是你期望应用正常服务情况下需要接收的请求大小和内容。这个状态叫做应用的稳定状态。
    如果在你的应用日志中观察到OutOfMemoryError,那你首先要确认是由于年老代不足还是永久代不足导致的。
     《java performance》翻译 第七章jvm调优:堆内存设置_第2张图片
    如果在gc日志中观察到了如上图所示的gc日志,可以判断是年老代不足导致的gc,因为从图中可知fgc前年老代的内存已经接近年老代的容量,而且FGC之后年老代内存的占有量也没有减少,而永久代还没有达到容量的一半。
    而如果在gc日志中观察到如下图所示的日志,则表明是由于永久代内存不足导致了fgc:
《java performance》翻译 第七章jvm调优:堆内存设置_第3张图片
    因为从截图中可以看到,在做fgc时,永久代已经使用了65536k,占用率已经达到了100%。而年老代的占用量132538k则远没有达到年老代的容量350208k。
    如果在gc日志中观察到了OutOfMemoryError错误,下一步可以把java堆的内存调大,最多可以使用空闲内存的80%-90%,也要注意调整的jvm堆区块,如果是年老代导致的OOM,则调整-Xms和-Xmx的值,如果是永久代导致的OOM,则调整-XX:PermSize和-XX:MaxPermSize的值。重复调整堆内存的大小,压测你的应用到稳定服务状态,然后观察应用的gc日志,直到应用没有OutOfMemoryError。下一步就可以开始计算应用的常驻内存大小了。
      JVM常驻内存大小
     JVM的常驻内存大小是指当应用在稳定提供服务状态下存活的内存对象的总大小。换句话来理解,常驻内存大小可以被认为是在应用稳定得提供期望提供的服务状态下进行一次fgc后年老代和年轻代占的总大小。所以常驻内存大小给应用提供的两个调优信息是:
     1.当应用在稳定提供期望服务状态下需要的年老代大小。
     2.当应用在稳定提供期望服务状态下需要的永久代大小。
     此外,在稳定提供期望服务状态下进行fgc还可以得到这个状态下发生fgc时对应用响应延迟的影响。
     要得到JVM的常驻内存大小可以通过多次观察应用在提供期望服务时发生fgc之后的堆内存情况来得到,如果应用需要花费很长时间才发生一次fgc,那则可以通过jvm的一些监控工具如VisualVM和jconsole来触发fgc,这些工具提供了手工触发jvm进行fgc的功能。使用jmap -histo:live <pid>也可以触发一次fgc,这个命令会触发jvm的一次fgc并收集jvm中存活对象的内存分配情况。
     设置jvm堆内存
     接下来一小节介绍如何根据jvm的常驻内存大小来设置初始化的jvm内存。下图介绍了如何从fgc中获取jvm的常驻内存信息:
     《java performance》翻译 第七章jvm调优:堆内存设置_第4张图片
     按照通常的经验来讲,jvm堆内存配置的值,即-Xms和-Xmx的值应该是jvm中年老代常驻内存大小的3至4倍。而永久代的大小(-XX:PermSzie和-XX:MaxPermSize)通常应用设置为永久代常驻内存的1.2到1.5倍。年轻代的大小(-Xmn)应该设置为年老代常驻内存大小的1至1.5倍。
      其他部分内存设置
      对于java应用,除了jvm堆内存的大小需要设置之外,还要考虑java应用中其他部分的内存消耗,比如线程栈中的内存分配,应用中的线程数越多,线程栈消耗的内存越大,线程的调用层次越深,线程栈需要使用到的内存也越多。此外,例如应用中使用了IO缓存时,本地方法也会消耗内存。
      当上述的设置不能满足应用的内存使用期望时,可能需要重新调整应用内存的分配,另外一点,也需要对应用本身的内存使用进行优化,最根本的办法就是减少应用中的对象分配和对象存活量。
      响应时间调优
       这个步骤的调优目的是调优jvm使得应用的响应时间满足需求。这个步骤可能会需要不断得调整jvm的堆内存大小并采集垃圾回收耗时和频率,也可能会调整垃圾回收器,最终确定各区的内存占用大小和垃圾回收器使用。
       经过这步骤的调优,可能会得到下面两个结果:
       1.应用响应时间的满足需求。如果这阶段的调优动作确实优化了应用的响应时间并满足了应用响应上的需求,则你可以继续下一步开始应用吞吐量的调优了。
       2.应用响应时间不满足需求。如果这阶段的调优动作完成后始终没有达到应用的响应时间需求,那么你需要做的可能是重新审视你对应用的响应时间需求,或者,在应用层面上进行优化,使之达到要求。你可能可以通常这两个思路来优化应用的响应时间:一是减少应用的对象分配和常驻内存数,二是改变jvm的部署模型,增加jvm实例或者按功能拆分应用并部分到不同jvm上。

你可能感兴趣的:(java)