JVM 内存非典型术语介绍(shallow/retained/rss/reserved/committed)

JVM 内存非典型术语介绍(shallow/retained/rss/reserved/committed)

背景

在服务器性能优化内存这一项时,有一些现象很诡异。如top显示的RES很大,但是实际jvm堆内存占用很小,同时使用nmt发现committed更大。所以决定写这篇wiki大概介绍一下

JVM 中如何计算一个对象的实际大小

实际在服务器估算内存时经常会抛出这个问题,而这里有两个概念,Shallow Size和Retained Size

  1. Shallow Size

    • 对象自身占用的内存大小,不包括它引用的对象
  2. Retained Size

    • 当前对象大小+当前对象可直接或间接引用到的对象的大小总和
    • Retained Size就是当前对象被GC后,从Heap上总共能释放掉的内存(排除被GC Root引用的对象)
  3. 计算方式(Shallow Size)

    • (对象头 + 实例数据 + padding) % 8等于0且0 <= padding < 8
    • HotSpot的对齐方式为8字节对齐
    • 32位对象头8个字节,64位16个字节
    • 可通过 jinfo 命令判断是否开启指针压缩 开启后64bit对象都变为12字节
    • 引用在32bit上是4个字节、64bit是8个字节、开启指针压缩后是4个字节
  4. 举例1

    • Float对象
      • 对象头:12字节(64bit#指针压缩)
      • 实例数据(float value):4字节
      • 不用对齐
      • 12 + 4 = 16字节
    • Example对象
      • 对象头:12字节(64bit#指针压缩)
      • 实例数据
        • 当前类3个Float引用+1个其他引用
        • 父类1个Float引用+1个其他引用
        • 4 * 6 = 24(64bit#指针压缩)
      • 12 + 24 = 36
      • 需要补齐4
      • 最终40
  5. 如何计算一个对象的真实大小(Retained Size)

    • 个人认为最简单的办法就是直接jmap#dump出堆内存快照
    • 然后,直接利用如mat工具分析 方便快捷
    • 其他API
      • https://github.com/jbellis/jamm
      • https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/Instrumentation.html#getObjectSize-java.lang.Object-
      • https://www.javamex.com/classmexer/
      • https://github.com/DimitrisAndreou/memory-measurer
      • https://stackoverflow.com/questions/52353/in-java-what-is-the-best-way-to-determine-the-size-of-an-object
  6. 注意

    • jmap -histo:live 命令显示出来的 instances 和 bytes 其实是Shallow Size,
    • 所以,用jvm命令本身很难计算一个对象的真实内存占用情况

排查 JVM 内存的几种方式

1、top res
  • top(res) 当前进程使用的内存大小

    • 其中可以观察到java进程占用的res内存是1.6g
    $ top
    PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
    14730 playcrab  20   0 9283m 1.6g  19m S  0.3 20.5  30:43.57 java
    
2、jstat
  • 可以看到从新生代(eden+s0+s1)到老年代的compacity显示和used显示
  • -gcutil 参数可以显示使用百分比
  • 注:这里的M指的是metaspace,从下面的输出可以看到M显示97.16,这里其实并非指metaspace 直接使用了97.16,这里是指的 Commited,而 MU 则是实际使用的size
 $ jstat -gc 14730
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
143360.0 143360.0  0.0    0.0   1146880.0 27569.6  4300800.0   88770.2   40448.0 39299.8 4648.0 4382.2     18    1.441   4      1.213    2.654

$ jstat -gcutil 14730
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT
  0.00   0.00   2.40   2.06  97.16  94.28     18    1.441     4    1.213    2.654
3、jmap
  • -heap 命令可以把堆内存的使用情况打印出来

    注意:1.8 旧的update可能有bug会导致cms的显示有问题

  • -histo 命令用来打印堆内存实例数和占用的字节数 注意是Shallow Size

  • -dump可dump出堆内存快照用来排查内存泄露或者大对象占用或者对象真正占用大小问题

$ jmap -heap 14730
Attaching to process ID 14730, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.172-b11

using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 5872025600 (5600.0MB)
   NewSize                  = 1468006400 (1400.0MB)
   MaxNewSize               = 1468006400 (1400.0MB)
   OldSize                  = 4404019200 (4200.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

Heap Usage:
New Generation (Eden + 1 Survivor Space):
   capacity = 1321205760 (1260.0MB)
   used     = 44450000 (42.39082336425781MB)
   free     = 1276755760 (1217.6091766357422MB)
   3.364351060655382% used
Eden Space:
   capacity = 1174405120 (1120.0MB)
   used     = 44450000 (42.39082336425781MB)
   free     = 1129955120 (1077.6091766357422MB)
   3.7848949432373047% used
From Space:
   capacity = 146800640 (140.0MB)
   used     = 0 (0.0MB)
   free     = 146800640 (140.0MB)
   0.0% used
To Space:
   capacity = 146800640 (140.0MB)
   used     = 0 (0.0MB)
   free     = 146800640 (140.0MB)
   0.0% used
concurrent mark-sweep generation:
   capacity = 4404019200 (4200.0MB)
   used     = 90900656 (86.68962097167969MB)
   free     = 4313118544 (4113.31037902832MB)
   2.0640385945638022% used

14494 interned Strings occupying 1262456 bytes.
$ jmap -histo:live 14730 | head -13

num     #instances         #bytes  class name
  1:        341052       13981272  [C
  2:        194673       11402072  [Ljava.lang.Object;
  3:        337876        8109024  java.lang.String
  4:        227540        7281280  java.util.HashMap$Node
  5:        180237        7209480  com.playcrab.achilles.spurs.gs.service.WordFilterService$WordNode
  6:         62226        5685448  [Ljava.util.HashMap$Node;
  7:        220913        5301912  java.util.ArrayList
  8:         17448        4789712  [B
  9:          3841        4219752  [I
 10:         62187        2984976  java.util.HashMap
4、nmt
  • nmt是Native Memory Tracking 可用来追踪native memory泄露这块
  • summary这块可以看到所有内存占用的部分
  • detail这块是具体到每一个内存起始地址段的内存占用
  • 注意:使用该工具需要启动参数增加-XX:NativeMemoryTracking=detail 而且某些1.8x某些update版本可能有问题
5、pmap
  • 这个也主要用来排查native memory的
  • 最终也对应到实际的内存地址和rss关系
  • 注意:这个内存地址和上面nmt的内存地址很多都对的上
    • 如0000000662000000这个地址内存其实就是java heap的内存地址
    • 而且可以看到这个地址的rss大约是1.35g
6、google-perftools#分析堆外内存

简单总结

  1. top 显示的res是指进程常驻内存 。目前从经验上看这个常驻内存会比堆内存大 这个很容易理解 因为jvm除了堆内存外,还有metaspace、stack、jvm本身、堆外内存等

  2. 使用 nmt工具可以看到committed size比较大 比rss要大。 这其实也可以理解 rss其实是真用使用的物理内存 而committed只是提交内存 而这些提交内存可能还没真正被touch

  3. 通常我们主要关注java堆内存,可以使用jmap来追踪内存使用和内存实例对象占用等。 如果要追踪大对象 。则可以使用jmap dump出堆快照后用mat等工具分析

    • 所以top显示的res进程内存可以参考 但更多应关注堆内存
    • 只要res/rss没有超过最大堆内存就先不用太担心
  4. 如果发现res占用非常大 那么要怀疑可能是堆外内存泄露

    • 可结合pmap、nmt、google-perftools追踪即可
  5. free这个命令可能有时候比较另外迷惑

    • 比如现在我们的服务器用free查看发现used是5.8G(4c8g) 但我们java进程占用的常驻内存却只有1.6G

    • 其实还是对free命令不熟悉 因为used指总计分配给缓存(包含buffers 与cache )使用的数量,但其中可能部分缓存并未实际使用

    • 而buffers/cached则表示系统分配但未被使用的

    • 所以从下面的输出可以看到分配了5.8G 但是未被使用的cached就达到了3.7G 所以整体没有大问题

       $ free -h
                    total       used       free     shared    buffers     cached
       Mem:          7.8G       5.8G       2.0G       940K       220M       3.7G
       -/+ buffers/cache:       1.9G       5.9G
       Swap:           0B         0B         0B
      

参考

  1. http://blog.yufeng.info/archives/2456
  2. https://stackoverflow.com/questions/31173374/why-does-a-jvm-report-more-committed-memory-than-the-linux-process-resident-set
  3. https://stackoverflow.com/questions/31071019/does-rss-tracks-reserved-or-commited-memory
  4. https://docs.oracle.com/javase/8/docs/api/java/lang/management/MemoryUsage.html
  5. http://lovestblog.cn/blog/2016/10/29/metaspace/
  6. http://lovestblog.cn/blog/2015/08/21/rssxmx/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

写文章的大米

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值