Alvin Liu

  • Home
  • About
  • Privacy Policy
  1. Main page
  2. JVM
  3. Main content

JVM内存调优实战经验

2023-02-26 18371hotness 0likes 0comments

JVM内存分配

  1. NMT是了解JVM内存分配的最佳途径, 但一定不要相信commited的数据(比如Thread, JNI和malloc的缺陷).
  2. 检查内存占用最准的工具是ps查看rss, 进程物理内存占用.
  3. 如果在Container/Pod里使用cgroup限制了内存, 加就直接看系统历史最大内存占用. 因为cgroup就是按这个数值来杀pod的. /sys/fs/cgroup/memory/memory.max_usage_in_bytes
  4. Total memory = Heap + Code Cache + Metaspace + Symbol tables + Other JVM structures + Thread stacks + Direct buffers + Mapped files + Native Libraries + Malloc overhead + ...
  1. 参考另一篇笔记 :JVM parts (Native Memory Tracking) https://stackoverflow.com/questions/53451103/java-using-much-more-memory-than-heap-size-or-size-correctly-docker-memory-limi

内存分析常用命令

  1. top, free. 查看内存占用, free = total - used - buff/cache. 查看进程的物理内存RES占用. 有I/O的时候还会占用buff.

shift+m按MEM排序. 如果想按RES排序: shfit+f在RES上按s再按esc

  1. jps, 获得进程号
  1. ps -aux 输出的RSS是进程的物理内存占用, 应该和jcmd 1 VM.native_memory summary输出的committed相符. 否则就是JNI Native Memory内存占用. RSS=pmap -x 7, 使用pmap可以查看具体内存占用, anon类型的就是gclib给JNI分配的
  2. jmap -histo:live 1 无法回收的实例数量和内存占用, live参数会触发GC
  3. jmap -heap 7 获堆空间分配报告, 参考<<jmap heap 堆参数分析>>
  4. jmap -dump:format=b,file=heap.dump 1 导出堆内存
  5. Native Memory Tracking 检查内存变化, 包括堆外内存, from Java8
    • jcmd 1 VM.native_memory summary 检查堆外内存占用. HotSpot专用, 需要提前用JVM参数启用-XX:NativeMemoryTracking=summary. Reserved只是申请了页地址, Committed才是实际占用的内存.
    • jcmd 1 VM.native_memory baseline 记录NMT基准点
    • jcmd 1 VM.native_memory summary.diff 和baseline对比内存变化
    • jcmd 7 VM.native_memory detail.diff 和baseline对比内存变化
    • pmap导出的进程内存不会显示JVM的使用细节, 只有对应NMT报告的地址才能解释
  6. pmap -x 1 | sort -n -k3 > pmap.txt 查看内存分配情况, 缺页次数, [ anon ] 为mmap等堆外内存占用. 这里的地址段和NMT报告的地址是对应的. Reserved(PROT_NONE) -> commited(JVM已经管理和使用的内存, 计算在rss里面) -> segfault(commited内存不足时触发) -> increase size(从reserved部分commite更多的内存).
    可以使用-XX:+AlwaysPreTouch强制JVM直接commit所有内存, 防止缺页中断影响性能.
    如果在pmap里发现大量的64MB匿名块, 可能是Linux内存分配的bug, 这些块(由DirectBuffer申请)不会被回收, 也不会计算在NMT的内存里. 如果rss和nmt的数据相差很大, 可以考虑替换系统malloc为:jemalloc(FreeBSD, 推荐, --enable-prof), tcmalloc(Google)或者mimalloc(MSFT).
  7. 使用gdb导出内存并分析内容, JDK9开始可以使用jhsdb, OpenJDK可以使用clhsdb
    • apk add gdb
    • gdb --batch --pid 1 --ex "dump memory mem.dump 0x7fc181025000 0x7fc181026000"
    • strings -n 10 -f mem.dump 查找内存文件中长度超过10的字符串
  8. jstat 分析GC数据, gc基本gc情况
    gcutil 按百分比显示
    gccause显示原因. EC: Eden Capacity, EU: Eden Usage. OC: Old Capacity, OU: Old Usage
    jstat官方文档: https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html
  9. jcmd 7 VM.flags 获取JVM运行时隐含参数
    jcmd 7 VM.info 获取JVM所有信息
    jcmd 7 VM.info 获取JVM所有信息
  10. jstack 打印线程信息, 放在https://fastthread.io/ 分析
    如果线程很多, 可能占用很多栈空间.
    而且还可能出现HeapByteBuffer的缓存问题, 当使用 HeapByteBuffer 进行网络通信等 IO 操作时,由于只有“本地”内存才能传递给操作系统调用,此时会将数据复制到一个临时的 DirectByteBuffer 对象中,JDK 会为每个线程缓存这个临时对象,且不限制内存大小。因此,如果程序有多个线程生成很多大 HeapByteBuffer 对象时,且线程一直存活,则会导致进程会占用大量的本地内存,造成内存泄露。
    https://blog.csdn.net/u012099869/article/details/82870082
    解决办法是用-Djdk.nio.maxCachedBufferSize=262144,来限制这个缓存的大小. Jdk8 u102
  11. 获取垃圾回收日志
    添加JVM启动参数:
    -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -Xloggc:/opt/scratch/$(MY_POD_NAME)_gclog.txt
    在另一个vm tail -f 这个文件, 可以解决vm重启覆盖的问题.
    把gc log上传到https://gceasy.io/ 可以获得分析报告. 例子在最下面.
  12. 查找Java Home java -XshowSettings:properties -version
  13. CGroup的内存限制
    如果使用-XX:+UseCGroupMemoryLimitForHeap参数动态配置堆大小.
    遇到容器报OOMKill错误时, 需检查堆外内存(non-heap)占用
    /sys/fs/cgroup/memory/memory.limit_in_bytes是CGroup设置的内存上限, 超过就会kill pod
    /sys/fs/cgroup/memory/memory.max_usage_in_bytes是pod历史最大内存占用, 要和上一个保持一定差距
    下图只剩79MB是比较危险的, 可以限制一下最大堆外内存.

    优化方式: 系统稳定运行一段时间后, 使用下面的参数把上面两个内存的差距调小.
    -XX:MaxRAMPercentage, 这是设置Xmx = memory.limit_in_bytes * percentage的
    剩下的堆外内存部分有各种限制参数, 不建议全部写死, 参考下面"限制每一个内存区域的大小"的部分
  14. 命令行访问JMX MBean
    • 下载命令行工具Jmxterm https://github.com/jiaqi/jmxterm
    • 启动ManagementAgent jcmd 1 ManagementAgent.start_local
    • 读取MBean数据 echo "get -d java.lang -b name=Code\ Cache,type=MemoryPool Usage" | java -jar jmxterm-1.0.1-uber.jar -l 1 -n
    • 关闭ManagementAgent jcmd 1 ManagementAgent.stop
  15. 查看已经被杀掉的进程的信息(Pod被删除的不适用)
    dmesg|grep -i kill
  16. G1GC优化参数
    • -XX:MarkStackSize
    • -XX:MaxGCPauseMillis
    • -XX:MaxRAMPercentage = <= 90.0 (heap size, default 25)
    • -XX:MaxDirectMemorySize = 不建议限制, 最大不能超过heap size - metaspace(默认=heap size).
    • -XX:G1HeapRegionSize = 物理内存/2048 (byte)
    • -XX:ParallelGCThreads= 逻辑处理器数量
    • -XX:ConcGCThreads= <ParallelGCThreads/4 ~ ParallelGCThreads/2>
    • 不要配置-XX:+DisableExplicitGC禁用System.gc(), 有的库需要主动调用gc回收DirectBuffer. 可以使用-XX:+ExplicitGCInvokesConcurrent代替
  17. G1GC参考资料
    • 中文: https://www.oracle.com/cn/technical-resources/articles/java/g1gc.html
    • 英文: https://www.oracle.com/technical-resources/articles/java/g1gc.html
  18. G1 解决Evacuation Failure和Humongous Allocation
    单独有一篇笔记 https://blog.csdn.net/u011381576/article/details/79755069
  19. 限制每一个内存区域的大小
    • Class : -XX:MaxMetaspaceSize(默认=Xmx大小, 但不使用Xmx堆空间), -XX:CompressedClassSpaceSize(包含在MaxMetaspaceSize之内, 默认1G). 不要使用-XX:MetaspaceSize, 会触发GC
    • Code: -XX:InitialCodeCacheSize, -XX:ReservedCodeCacheSize (默认240MB, 如果满了会清空已编译的代码, 导致极大的性能损失. 可以用profiler检查空间占用)
    • Compiler(Arena): 使用C1+C2会增加Code的内存占用?
    • Thread: 默认一个stack 1mb,实际commit了也不一定占用内存, 这块会导致NMT的commit和实际系统占用不一致. -Xss可以设置每个线程的stack大小.
    • Symbol: SymbolTable和StringTable的空间, 基本不会占用太多. 使用jcmd 7 VM.stringtable和jcmd 7 VM.symboltable查看占用. 100万字符串700MB.
    • Internal:著名的直接内存访问区, JDK11转入Other区, 使用DirectBuffer.mmap和Unsafe申请, 用-XX:MaxDirectMemorySize限制, 默认也等于Xmx(联合Metaspace已经用了三份Xmx内存了...怪不得Xmx默认25%内存...). 可以用MBean java.nio.BufferPool.direct查看使用情况. DirectBuffer能被gc回收但不会主动触发GC, 满了就会OOM, 有的库会主动调用System.gc()回收DirectBuffer, 所以不要配置-XX:+DisableExplicitGC禁用System.gc(), 可以使用-XX:+ExplicitGCInvokesConcurrent代替.
  20. Linux perf, 如果出现JVM监控范围之外的内存泄漏, 可以参考另一篇文章使用perf监控系统tracepoint https://developer.aliyun.com/article/65255

内存分析常用工具

  • Eclipse Memory Analyzer, 自带内存泄漏检测功能 https://www.eclipse.org/mat/downloads.php
  • jconsole 图形化调试工具, 可以看线程, 内存情况, 读取配置MXBean. 还可以远程连接JVM的jmxremote接口, from Java 5 by Sun
  • jmc Java Mission Control 图形化调试工具, 可以分析jcmd记录的JFR(收费), from Java 7, (BEA JRockit)
  • jvisualvm Visual VM, from Java 6 u7 (NetBeans), recommand use open source version http://visualvm.github.io/
  • http://gceasy.io/ 根据gc log生成报告.

参考资料

  • NMT: https://juejin.im/post/5c986e48f265da60cc02b83c
  • Understanding Java Memory Model: https://medium.com/platform-engineer/understanding-java-memory-model-1d0863f6d973
  • Andrei Pangin: Memory footprint of a Java process: https://vimeo.com/364039638
  • Andrei Pangin: NMT: https://stackoverflow.com/questions/53451103/java-using-much-more-memory-than-heap-size-or-size-correctly-docker-memory-limi
This article is licensed with Creative Commons Attribution-NonCommercial-No Derivatives 4.0 International License
Tag: Heap JVM Memory Performance
Last updated:2023-02-26

Alvin Liu

Software Developer in Toronto

Like

Comments

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
Cancel

COPYRIGHT © 2024 Alvin Liu. alvingoodliu@[email protected] ALL RIGHTS RESERVED.

Theme Made By Seaton Jiang