JVM调优通用知识

背景和价值

1. 垃圾回收器选择

  • 单核CPU 选择 Serial 垃圾回收器
  • 多核CPU,4G内存建议CMS
  • 8G或者以上物理内存,在G1(JDK8以上)和ZGC (JDK15或者以上)之间做选择。

核心选型维度对比

维度 G1 GC ZGC
目标停顿时间 50~200ms(可预测) <1ms(亚毫秒级)
吞吐量 中高(适合通用型应用) 中等(牺牲部分吞吐换延迟)
内存开销 堆的5%~10%(维护RSet等结构) 堆的15%~20%(染色指针、元数据)
适用堆大小 4GB~64GB(最佳区间) ≥16GB(优势随堆增大而提升)
适用场景 Web服务、企业应用、批处理 实时交易、游戏服务器、云原生

2. 堆内存设置多大

(1) ​中小规模内存(4GB–64GB)​​
物理内存配置​:
堆内存(-Xmx)建议设为物理内存的50%~60%,例如:
物理内存8GB → 堆内存4GB–5GB
物理内存16GB → 堆内存8GB–10GB
(2) 大规模内存(64GB–2TB)​​
​适用场景​:大数据处理、实时分析等。
​物理内存配置​:
堆内存可占物理内存的70%~80%,例如:

最佳实践 8G容器5G堆

JVM进程占用的内存:heap+metaspace+ 额外的几百M(代码多的程序,甚至上G的空间),所以8G物理内存,分配5G最大堆内存比较合适

案例:生产环境8G内存,JDK为1.8,最大堆设置6G,元数据空间设置为256M。 生产运行一段时间,运维监控发现物理内存用到90%!!
通过top查看,JAVA进程占7G内存,远大于6G+256M+线程栈的几十M的内存。 那么多出的700M内存花到哪里呢?

通过arthas dashboard分析

Memory                          used       total      max       usage      GC

heap                            2210M      6092M      6092M     36.28%     gc.parnew.count                       2078
par_eden_space                  204M       409M       409M      49.83%     gc.parnew.time(ms)                    115760
par_survivor_space              3M         51M        51M       7.28%      gc.concurrentmarksweep.count          24
cms_old_gen                     2002M      5632M      5632M     35.56%     gc.concurrentmarksweep.time(ms)       101565
nonheap                         287M       299M       -1        95.96%
code_cache                      100M       101M       240M      41.77%
metaspace                       167M       177M       -1        94.57%
  1. JVM本身需要的内存,包括其加载的第三方库以及这些库分配的内存。内存映射文件,包括JVM加载的一些JAR和第三方库,以及程序内部用到的。上面 pmap 输出的内容里,有一些静态文件所占用的大小不在Java的heap里
  2. NIO的DirectBuffer是分配的native memory
  3. Native Memory包含Native Code,Native Stack。对应arthas的nonheap+code_cache。JIT(Just-In-Time (JIT) 编译器是运行时环境的一个组件,通过在运行时将字节码编译为本机机器代码来提高Java™ 应用程序的性能),JVM会将Class编译成native代码,这些内存也不会少,如果使用了Spring的AOP,CGLIB会生成更多的类,JIT的内存开销也会随之变大。一些JNI接口调用的native库也会分配一些内存,如果遇到JNI库的内存泄露,可以使用valgrind等内存泄露工具来检测
  4. 线程栈,每个线程都会有自己的栈空间,如果线程一多,这个的开销就很明显
  5. Metaspace:class文件元信息描述,编译后的代码数据,引用类型数据,类文件常量池(而字符串常量池 存在于堆中)

总结
JVM进程占用的内存:heap+metaspace+ 额外的几百M(代码多的程序,甚至上G的空间),所以8G物理内存,分配5G最大堆内存比较合适

什么是运行时常量池
运行时常量池
运行时常量池就是将编译后的类信息放入方法区中,也就是说它是方法区的一部分。

运行时常量池用来动态获取类信息,包括:class文件元信息描述、编译后的代码数据、引用类型数据、类文件常量池等。

运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中。每个class都有一个运行时常量池,类在解析之后将符号引用替换成直接引用,与全局常量池中的引用值保持一致。

3. 其他通用调优考虑

-Xms 和 -Xmx` 设置为相同的值,以避免堆内存动态调整带来的性能开销。
设置 Survivor 区比例。如果应用有大量短期对象,可以适当增加 Survivor 区的大小。

参考资料

https://blog.csdn.net/guyue35/article/details/124416121

arthas dashboard命令
https://zhuanlan.zhihu.com/p/383283993

posted @ 2025-03-11 15:34  向着朝阳  阅读(110)  评论(0)    收藏  举报