《java性能优化权威指南》---- 第7章:Java性能调优入门(7.7 确定内存占用)

七、确定内存占用

活跃数据的大小是确定运行应用程序所需Java堆大小的不错切入点。同时,它也决定了我们是否需要重新回顾应用程序的内存占用需求、或者是否需要修改应用程序以满足应用程序的内存占用需求。
活跃数据的大小是指,应用程序稳定运行时长期存活对象所占用的Java堆内存量。换句话说,它是应用程序运行于稳定态时,Full GC之后Java堆所占用的空间大小。

1、约束

JVM可以使用的物理内存量,JVM部署模式也会影响,还要给操作系统预留一部分内存。

2、HotSpot VM堆的布局

HotsSpot VM有三个主要的空间,分别是:新生代、老年代以及永久代。这三块空间的分布如图7-2所示。
在这里插入图片描述Java应用程序分配Java对象时,首先在新生代空间中分配对象。存活下来的对象,即经历几次Minor GC之后还保持活跃的对象会被提升进入老年代空间。永久代空间中存放JVM和Java类的元数据以及驻留的Strings和类静态变量。

  • 新生代和老年代-Xmx-Xms命令行选项指定了新生代和老年代空间大小的初始值和最大值,初始值及最大值也被称为Java堆的大小。-Xms是初始值及最小值, -Xmx是最大值。关注吞吐量及延迟的Java应用程序应该将-Xms和-Xmx设定为同一值。这是因为无论扩展还是缩减新生代空间或老年代空间都需要进行Full GC,而Full GC会降低程序的吞吐量并导致更长的延迟。

  • 新生代:新生代空间可以通过下面任何一个命令行选项设置:

    -XX: NewSize=<n> [g|m|k] #新生代空间大小的初始值,也是最小值。
    

    <n>为设定的大小。[q1mlk]指大小的度量单位,分别是GB、MB 、KB。使用-XX: NewSize选项时,应当同时指定-XX: MaxNewSize选项。

    -XX:MaxNewsize=<n> [g|m|k] # 新生代空间大小的最大值。
    

    使用-XX: MaxNewSize选项时,应当同时指定-XX: NewSize选项。

    -Xmn<n> [g|m|k] # 新生代空间的初始值、最小以及最大值。
    

    通过-Xmn可以很方便地设定新生代空间的初始值和最大值。有一点需要特别注意,如果-Xms-Xmx并没有设定为同一个值,使用-Xmn选项时,Java堆的大小变化不会影响新生代空间,即新生代空间的大小总保持恒定,而不是随着Java堆大小的扩展或缩减做相应的调整。因此,请注意:只有在-Xms-Xmx设定为同一值时才使用-Xmn选项。

  • 老年代:老年代空间的大小会根据新生代的大小隐式设定。

  • 永久代:永久代空间大小可以通过下面的命令行选项设置。

    -XX:Permsize=<n> [g|m|k] # 永久代空间的初始值及最小值。
    
    -XX:MaxPermSize=<n> [g|m|k] #永久代空间的最大值。
    

    关注性能的Java应用程序应该将永久代大小的初始值与最大值(使用-XX: PermSize-XX:MaxPermSize选项)设置为同一值,因为永久代空间的大小调整需要进行FullGC才能实现。

  • 不指定堆大小:如果不显式指定Java堆大小,如初始值、最大值;新生代大小、永久代大小,HotSpot VM可以通过名为"自动调优"的自适应调优功能,依据系统配置自动选择合适的值。

    提示:关于HotSpot VM自适应调优,,包括Java堆大小默认值选择的更多信息可以参看3.5节。

  • 触发FullGC:新生代、老年代或永久代这三个空间中的任何一个不能满足内存分配请求时,就会发生垃圾收集,理解这一点非常重要。

    老年代空间不足以容纳新提升的对象时,HotSpot VM就会进行Full GC。实际上,当HotSpot VM发现当前可用空间不足以容纳下一次Minor GC提升的对象时就会进行Full GC。与因空间问题导致的MinorGC过程中的对象提升失败比较起来,这种方式的代价要小得多。从失败的对象提升中恢复是一个很昂贵的操作。永久代没有足够的空间存储新的VM或类元数据时也会发生Full GC。

    如果Full GC缘于老年代空间已满,即使永久代空间并没有用尽,老年代和永久代都会进行垃圾收集。同样,如果Full GC由永久代空间用尽引起,老年代和永久代也都会进行垃圾收集,无论老年代是否还有空困空间。开启-XX:+UseParallelGC-XX:+UseParalleOldGC时,如果关闭-XX:-ScavengeBeforeFullGC,HotSpot VM在Full GC之前不会进行Minor GC,但Full GC过程中依然会收集新生代;如果开启-XX:+ScavengeBeforeFullGC, HotSpot VM在Full GC前会先做一次Minor GC,分担一部分Full GC原本要做的工作。

3、堆大小调优着眼点

通过HotSpot命令行选项-XX: +PrintCommandLineFlags还可以查看堆的初始值及最大值。-XX:PrintCommandLineFlags选项可以输出HotSpot VM初始化时使用-XX:InitialHeapSize=<n> -XX:MaxHeapSize=<m>指定的堆的初始值及最大值。

尝试将应用程序推进到稳定状态的过程中,如果观察到GC日志中出现了OutOfMemoryErrors,就要查看老年代或永久代空间是否已经用尽。

老年代空间太小引发FullGC示例

下面的这个例子演示了这样的场景,由于老年代空间太小,最终发生了OutOfMemoryError:
在这里插入图片描述
GC重要部分已加粗显示。根据输出的GC日志,很容易得出结论:老年代空间太小,因为Full GC之后老年代占用的空间,即箭头(->)右边的值与设定的老年代大小(括号中的值)非常接近。

永久代空间太小引发FullGC示例

下面的这个例子展示了永久代过小导致的OutOfMemoryError:
在这里插入图片描述
GC重要部分已加粗显示。我们很容易得出结论,永久代空间不够大,因为Full GC之后永久代占用的空间,即箭头(->)右边的值与设定的永久代大小(括号中的值)非常接近。

如果GC日志中出现OutOtMemoryError,可以尝试通过增加JVM可用物理内存缓解,如将80%或90%的物理内存分配给JVM使用。尤其要关注引起OutOMemoryError的堆空间,确保增加其大小。例如,对老年代引起的OutOtMemoryErrors,增加-Xms-Xmx值;对永久代引起的OutOtMemoryErrors,增加-XX: PermSize-XX: MaxPermsize值。

一旦应用程序运行于稳定态,不再发生OutOMemoryError,下一步就该统计应用程序中的活跃数据大小了。

4、计算活跃数据大小

如前所述,活跃数据大小是应用程序运行于稳定态时,长期存活的对象在Java堆中占用的空间大小。换句话说,活跃数据大小是应用程序运行于稳定态,Full GC之后Java堆中老年代和永久代占用的空间大小

为了更好地度量应用程序的活跃数据大小,最好在多次Full GC之后再查看Java堆的占用情况。另外,需要确保Full GC发生时,应用程序正处于稳定态。如果应用程序没有发生Full GC或者不经常发生Full GC,你可以使用JVM监控工具VisualVM或JConsole人工触发Full GC。比如VisualVM和JConsole工具。

你也可以使用HotSpot JDK发行版中提供的jmap命令,通过命令行强制进行Full GC。为了实现这个目的, jmap需要使用-histo:live命令行选项及JVM进程号。JVM进程号可以通过JDK的jps命令获得。例如,Java应用程序的JVM进程号是348,使用jmap触发Full GC的命令行如下:

$ jmap -histo:live 348

jmap命令触发Full GC的同时也生成一份包含对象分配信息的堆分析文件。

5、初始堆空间大小配置

本节将介绍如何根据统计的活跃数据大小,确定Java堆的初始大小。图7-3显示了标识应用程序活跃数据大小的字段。计算活跃数据大小时,比较明智的方法是多取几次Full GC数据,通过取平均值的方式计算Java堆占用及GC时间。收集的次数越多,对启动时Java堆大小的估算越准确。
在这里插入图片描述
根据活跃数据大小定义初始Java堆大小时,还需要考虑Full GC的影响,推荐的做法是基于最差延迟进行估算。

通用法则之一,将Java堆的初始值-Xms和最大值-Xmx设置为老年代活跃数据大小的3-4倍。

通用法则之二,永久代的初始值-XX:Permsize及最大值-XX:MaxPermSize应该比永久代活跃数据大1.2-1.5倍。

补充法则,新生代空间应该为老年代空间活跃数据的1-1.5倍。

计算Java堆大小可以参考表7-3,该表基于GC数据区(见图7-3),综合了前文介绍的通用分级法则和对应的Java命令行。
在这里插入图片描述
在这里插入图片描述

6、其他考量因素

有一点很重要,我们要意识到按照前一节中介绍的方法计算出来的Java堆大小并不代表Java应用程序的总内存占用。另外,Java堆不一定是最耗应用程序内存的。例如,应用程序的线程栈可能需要较多的内存。线程的数目越多,消耗在线程栈上的内存就越多。应用程序中,方法调用的层次越深,线程栈占用的空间也越大。

谨记一点,调优过程中,这一步操作可能导致应用程序无法达到内存需求。如果出现这种情况,要么我们需要回顾或修改应用程序的内存占用需求,要么需要调整应用程序。

这一步中计算出的Java堆大小仅仅是一个出发点。后面的调优过程中,这些值可能会根据情况进行修改,具体的情况取决于应用程序需求。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值