倒推法推导JVM堆内存配置

本文通过JVM内存模型的演变,探讨了如何通过倒推法确定JVM堆内存的大小,包括堆、非堆内存(如元空间、线程栈、代码缓存、GC相关空间和JavaNIO直接内存)的分配。提供了基于物理内存和系统占用的建议堆内存配置,以及元空间和代码缓存的设置指导。
摘要由CSDN通过智能技术生成

目录

JVM内存模型

一个JVM进程内存占用的例子

系统占用

JVM的非堆内存

元空间

线程相关

代码缓存

GC 相关

Java NIO 使用的直接内存

其它

堆的大小

建议参考值

参考

附:文章开头部分的例子的详细数据

本文主要是结合JVM内存模型,借助大佬经验使用倒推法学习JVM堆内存分配调参

JVM内存模型

下图是 JDK 1.6、1.7、1.8 的内存模型演变过程,其实这个内存模型就是 JVM 运行时数据区依照JVM虚拟机规范的具体实现过程。在图中各个版本的迭代都是为了更好的适应CPU性能提升,最大限度提升的JVM运行效率。这些版本的JVM内存模型主要有以下差异:

  • JDK 1.6:有永久代,静态变量存放在永久代上。
  • JDK 1.7:有永久代,但已经把字符串常量池、静态变量,存放在堆上。逐渐的减少永久代的使用。
  • JDK 1.8:无永久代,运行时常量池、类常量池,都保存在元数据区,也就是常说的元空间。但字符串常量池仍然存放在堆上。

一个JVM进程内存占用的例子

例:JVM进程内存占用。 8 CPU, 12 GB 物理内存,OpenJDK 11.0.7+10
非堆总占用
<不细分>元空间线程相关代码缓存GC 相关Java NIO 使用的直接内存其它
committed8192 MB  196 MB  73 MB110 MB368 MB229 MB59 MB  9226 MB
reserved8192 MB1195 MB589 MB250 MB368 MB229 MB59 MB10881 MB
33381 个类
584 个线程不一定全是

*文末有该例的详细数据。

在内存一定且够用的情况下,下面通过倒推法找出合适的堆空间大小。

系统占用

JVM 进程之外,主要是操作系统和其它进程所占的内存。

在 KVM(实体机类似)虚假机上,另外运行的进程主要是 flume, 用于日志收集的。两个 flume 相关的进程共占内存大概是 500 MB. 其它进程或者说操作系统大概占 700 MB. 这些部分合起来留点富余,至少要保留 1.5 G 的空间。用 kvm 的总物理内存去掉这部分,就是留给 JVM 进程的总可用内存大小 。比如,4G - 1.5G = 2.5G, 8G - 1.5G = 6.5G, 12G - 1.5G = 11.5G.

特殊的, docker 容器的实例和 kvm 虚拟机不同,没有那么多操作系统相关的线程占用内存,也没有 flume 进程用于处理日志收集,目前除 tomcat 进程外,主要有一个 bistoury 进程占用空间较明显一些,committed 136MB, 但这个应该是 bistoury 进程相对空闲时候的占用,不同的使用可能会有不同的占用情况。建议至少预留 500MB ~ 1GB 的空间。如果 docker 容器的内存空间较充足,比如 12G,可以无脑考虑跟 kvm 一样,认为环境占用的内存是 1.5G.

实体机的物理内存一般都比较大,主要会影响 GC 相关的内存占用也会相应增大,请以实际情况为准。JVM外的系统占用也可以多留一下富余。

JVM的非堆内存

一个应用程序的源代码和各部分的运行情况确定以后,这部分空间一般也是确定的,需要通过一定的工具来查看,从而掌握具体的情况。不同的应用之间差异可能会比较明显。

这里说明一下内存各部分分配时主要考虑的几个方面:

元空间

主要存储类信息和代码相关的内容,这部分空间在 JDK1.8 之后,默认是无限制的,如果需要限制,可通过 -XX:MaxMetaspaceSize=[size] 指定可提交的最大空间限制。JDK1.8 之前,通过 -XX:MaxPermSize=[size] 参数设置,效果类似。

-XX:MaxMetaspaceSize=[size] 这个参数虽然不指定时是无限制的,事实上还是受制于系统可用的空间大小。如果堆分配的过大,留给这部分可用的空间可能就会很紧张。如果物理内存不够用,就会有部分数据使用 swap. docker 不允许使用 swap, 所以一旦超出可用量,可能的结果就是进程崩溃。

-XX:MetaspaceSize=[size] 这个参数主要影响什么时候第一次触发 GC(一般会是 Full GC, 因为要有效回收元空间的内容,需要先回收堆中的内容)。第一次触发后,触发 GC 的阈值会根据实际的占用情况动态变化。之前遇到过一种情况是这个值不设置,JVM启动时选择的初始值较小,随着系统加载的内容越来越多,系统整个启动和预热的过程中会不断地触发 GC 并增长实际的占用空间,导致的问题会比较明显。不记得这个问题所对应的 JDK 版本了,新版本的 JDK 可能表现会更好。但建议总是设置这个值为系统稳定工作时实际占用空间的至少 120%.

为了减少 JVM 进程 reserved 的空间大小(或者叫虚拟地址空间大小),建议设置 -XX:MaxMetaspaceSize=[size] 的值为 2 倍的 -XX:MetaspaceSize=[size] 这个值。即使不这么设置,也要在设置堆空间时留出这么多的空间。设置上还有一个好处是,便于快速识别到这个空间的需求大小,也方便其它空间的计算。

线程相关

在 linux x64 平台上,JVM 线程栈的默认空间大小为 1024KB, 可通过 -XX:ThreadStackSize=[size] 或者 -Xss[size] 进行调整,一般不需要调整这个值。

从上面的例子可以看到,584个线程预留的空间 reserved 大小为 589 MB,而实际提交占用的空间为 73 MB. 在这个例子中,实际占用的情况相对是比较低的,而且这个跟捕捉这个信息时进程所处的执行状况也比较相关。

最保险的作法是留出和线程个数相当的 MB 大小的空间。如果想少留这部分空间,需要仔细观察进行在各种情况下的实际占用情况。

代码缓存

用于存储 JIT 编译后的代码。

-XX:ReservedCodeCacheSize=[size] 参数用于限定这个空间的最大值,默认为 240MB. 另外,这个值最大允许的值为 2GB. 如果需要设置超过 240MB 的值,建议按实际占用情况的 150% 进行设置。

JIT 编译后的代码不一定会常驻内存,如果这些代码长时间不被执行,是有可能会被卸载的。所以,如果程序的某些部分不是一直在使用,这个空间的大小可能会经常波动。

GC 相关

这部分是 GC 工作时需要的一些内部空间。其大小跟管理的堆空间大小有一定关系,堆空间越大,这个空间相应的也会越大。

这个没有参数来影响,但考虑堆空间大小时要把这部分的空间预留出来。实体机的物理内存一般都比较大,分配的堆空间相应的也会比较大,可考虑给 GC 这部分多留一些空间,请以实际情况为准。

Java NIO 使用的直接内存

Dubbo、netty 以及一些异步的 HTTP Client 工具都可能会使用这个空间。应以程序工作稳定时的实际占用为准。

-XX:MaxDirectMemorySize=[size] 可用于限定这个空间的最大值,默认为 0, 表示由 JVM 选择合适的大小 。

其它

还有好多细项,所占内存不多,因为其它项一般都留了不少富余,这部分内容一般可不考虑。

如果要查看 JVM 进程的非堆内存占用情况,可使用命令 jcmd <pid> VM.native_memory 达到目的,其所呈现的内容由 -XX:NativeMemoryTracking=[mode] 参数影响,mode 取值有 off, summary, detail 三个,建议使用 summary, 在线上未发现明显影响。

docker 的机器的 jcmd 没有成功执行过,估计跟 bistoury 进程有关,目前只能通过 bistoury 的页面来查看各项指标,只是没有 jcmd 分的那么细,主要内容也是可以找到的。

堆的大小

当上面的部分都确定以后,堆的大小自然也就出来了。上面的部分所需的空间预留充足以后剩下的部分可尽数分给堆空间。用这个值设置 -Xms[size] 和 -Xmx[size] 即可(总是把 -Xms 和 -Xmx 设置为合适的相同值一般是较好的推荐作法)。

以上面的例子来看,

commited:  JVM非堆大小 = 196 + 73 + 110 + 368 + 229 + 59 = 1035MB = 1GB,  堆大小可取 12 - 1.5 - 1 = 9.5GB

reserved:  JVM非堆大小  = 1195 + 589 + 250 + 368 + 229 + 59 = 2690MB = 2.6GB, 堆大小可取 12 - 1.5 - 2.6 = 7.9GB

堆的实际配置是 8GB,相当于预留了较多的富余以备波动之需。如果想多给堆分配一些,以这个例子来说,不建议分配超过 9GB 的堆空间。因为超过了 9GB 的空间,相当于非堆部分的富余量就很小了,有点波动可能就会出问题。

以该例来看,建议指定的 JVM 启动参数为 "-Xms8g -Xmx8g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m".

建议参考值

  4G 物理内存,最大 2G 堆空间;

  8G 物理内存,最大 5G 堆空间;

12G 物理内存,最大 9G 堆空间,建议 8G.

大内存物理机,建议留出系统占用量之后,剩余空间的 80% 分配给堆内存。

元空间,如无特殊情况,建议按照  -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m 进行设置。第2个参数可以不设置,但建议总是设置第一个参数。

注意

除了 Java 自身管理的堆内和堆外内存之外,其实还有一部分内存占用容易被忽视,就是在系统的 cache 中占用的部分。

我们自己负责的应用在 cache 中所占部分,观察到的从 500M ~ 1.5G 不等,不同的应用差异也比较大。

上面的配置值的建议值,并未考虑这部分的占用。所以在实际中,应该把这部分也考虑进去,留出相应的空间。尤其是容器化部署的应用。

top 命令和 java 的各种查看内存占用的工具,都不显示这部分内存占用,而 docker 的 Watcher 监控里对应用进程的内存占用的监控是有这部分的。

参考

JVM源码分析之Metaspace解密 - 你假笨

Native Memory Tracking

Diagnostic Tools

附:文章开头部分的例子的详细数据

需要开启参数:-XX:NativeMemoryTracking=detail

查看命令:jcmd <PID> VM.native_memory summary

Native Memory Tracking:

Total: reserved=11141643KB, committed=9446843KB
-                 Java Heap (reserved=8388608KB, committed=8388608KB)
                            (mmap: reserved=8388608KB, committed=8388608KB) 
 
-                     Class (reserved=1223914KB, committed=200554KB)
                            (classes #33381)
                            (  instance classes #31535, array classes #1846)
                            (malloc=11498KB #155642) 
                            (mmap: reserved=1212416KB, committed=189056KB) 
                            (  Metadata:   )
                            (    reserved=163840KB, committed=163328KB)
                            (    used=153557KB)
                            (    free=9771KB)
                            (    waste=0KB =0.00%)
                            (  Class space:)
                            (    reserved=1048576KB, committed=25728KB)
                            (    used=20700KB)
                            (    free=5028KB)
                            (    waste=0KB =0.00%)
 
-                    Thread (reserved=603124KB, committed=74364KB)
                            (thread #584)
                            (stack: reserved=600268KB, committed=71508KB)
                            (malloc=2105KB #3506) 
                            (arena=751KB #1167)
 
-                      Code (reserved=255553KB, committed=112873KB)
                            (malloc=7865KB #34588) 
                            (mmap: reserved=247688KB, committed=105008KB) 
 
-                        GC (reserved=376369KB, committed=376369KB)
                            (malloc=31773KB #74855) 
                            (mmap: reserved=344596KB, committed=344596KB) 
 
-                  Compiler (reserved=3098KB, committed=3098KB)
                            (malloc=3033KB #4022) 
                            (arena=65KB #5)
 
-                  Internal (reserved=4869KB, committed=4869KB)
                            (malloc=4837KB #11584) 
                            (mmap: reserved=32KB, committed=32KB) 
 
-                     Other (reserved=234017KB, committed=234017KB)
                            (malloc=234017KB #361) 
 
-                    Symbol (reserved=31041KB, committed=31041KB)
                            (malloc=27037KB #368757) 
                            (arena=4004KB #1)
 
-    Native Memory Tracking (reserved=11484KB, committed=11484KB)
                            (malloc=541KB #7560) 
                            (tracking overhead=10943KB)
 
-               Arena Chunk (reserved=695KB, committed=695KB)
                            (malloc=695KB) 
 
-                   Logging (reserved=7KB, committed=7KB)
                            (malloc=7KB #264) 
 
-                 Arguments (reserved=19KB, committed=19KB)
                            (malloc=19KB #520) 
 
-                    Module (reserved=7310KB, committed=7310KB)
                            (malloc=7310KB #24522) 
 
-              Synchronizer (reserved=1529KB, committed=1529KB)
                            (malloc=1529KB #12818) 
 
-                 Safepoint (reserved=8KB, committed=8KB)
                            (mmap: reserved=8KB, committed=8KB)
  • 34
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值