你真的知道 JVM 内存一般都包含什么?

 2023-01-16
原文作者:干货满满张哈希 原文地址:https://juejin.cn/post/6916653766032752647

我们一般通过两个工具 pmap 还有 jcmd 中的 VM.native_memory 命令去查看 Java 进程内存占用,由于 pmap 命令有点复杂而且很多内存映射是 anon 的,这里采用 jcmd 中的 VM.native_memory 命令,去看一下 JVM 内存的每一部分。

    Native Memory Tracking:
    
    Total: reserved=6308603KB, committed=4822083KB
    -                 Java Heap (reserved=4194304KB, committed=4194304KB)
                                (mmap: reserved=4194304KB, committed=4194304KB) 
     
    -                     Class (reserved=1161041KB, committed=126673KB)
                                (classes #21662)
                                (  instance classes #20542, array classes #1120)
                                (malloc=3921KB #64030) 
                                (mmap: reserved=1157120KB, committed=122752KB) 
                                (  Metadata:   )
                                (    reserved=108544KB, committed=107520KB)
                                (    used=105411KB)
                                (    free=2109KB)
                                (    waste=0KB =0.00%)
                                (  Class space:)
                                (    reserved=1048576KB, committed=15232KB)
                                (    used=13918KB)
                                (    free=1314KB)
                                (    waste=0KB =0.00%)
     
    -                    Thread (reserved=355251KB, committed=86023KB)
                                (thread #673)
                                (stack: reserved=353372KB, committed=84144KB)
                                (malloc=1090KB #4039) 
                                (arena=789KB #1344)
     
    -                      Code (reserved=252395KB, committed=69471KB)
                                (malloc=4707KB #17917) 
                                (mmap: reserved=247688KB, committed=64764KB) 
     
    -                        GC (reserved=199635KB, committed=199635KB)
                                (malloc=11079KB #29639) 
                                (mmap: reserved=188556KB, committed=188556KB) 
     
    -                  Compiler (reserved=2605KB, committed=2605KB)
                                (malloc=2474KB #2357) 
                                (arena=131KB #5)
     
    -                  Internal (reserved=3643KB, committed=3643KB)
                                (malloc=3611KB #8683) 
                                (mmap: reserved=32KB, committed=32KB) 
     
    -                     Other (reserved=67891KB, committed=67891KB)
                                (malloc=67891KB #2859) 
     
    -                    Symbol (reserved=26220KB, committed=26220KB)
                                (malloc=22664KB #292684) 
                                (arena=3556KB #1)
     
    -    Native Memory Tracking (reserved=7616KB, committed=7616KB)
                                (malloc=585KB #8238) 
                                (tracking overhead=7031KB)
     
    -               Arena Chunk (reserved=10911KB, committed=10911KB)
                                (malloc=10911KB) 
     
    -                   Tracing (reserved=25937KB, committed=25937KB)
                                (malloc=25937KB #8666) 
     
    -                   Logging (reserved=5KB, committed=5KB)
                                (malloc=5KB #196) 
     
    -                 Arguments (reserved=18KB, committed=18KB)
                                (malloc=18KB #486) 
     
    -                    Module (reserved=532KB, committed=532KB)
                                (malloc=532KB #3579) 
     
    -              Synchronizer (reserved=591KB, committed=591KB)
                                (malloc=591KB #4777) 
     
    -                 Safepoint (reserved=8KB, committed=8KB)
                                (mmap: reserved=8KB, committed=8KB)

这里的 mmapmalloc 是两种不同的内存申请分配方式,例如:

    Internal (reserved=3643KB, committed=3643KB)
                                (malloc=3611KB #8683) 
                                (mmap: reserved=32KB, committed=32KB)

代表 Internal 一共占用 3643KB,其中3611KB是通过 malloc 方式,32KB 是通过 mmap 方式。 arena 是通过 malloc 方式分配的内存但是代码执行完并不释放,放入 arena chunk 中之后还会继续使用,参考:MallocInternals

可以看出,Java 进程内存包括:

  • Java Heap : 堆内存,即-Xmx限制的最大堆大小的内存。
  • Class :加载的类与方法信息,其实就是 metaspace,包含两部分: 一是 metadata,被-XX:MaxMetaspaceSize限制最大大小,另外是 class space,被-XX:CompressedClassSpaceSize限制最大大小
  • Thread :线程与线程栈占用内存,每个线程栈占用大小受-Xss限制,但是总大小没有限制。
  • Code :JIT 即时编译后(C1 C2 编译器优化)的代码占用内存,受-XX:ReservedCodeCacheSize限制
  • GC :垃圾回收占用内存,例如垃圾回收需要的 CardTable,标记数,区域划分记录,还有标记 GC Root 等等,都需要内存。这个不受限制,一般不会很大的。
  • Compiler :C1 C2 编译器本身的代码和标记占用的内存,这个不受限制,一般不会很大的
  • Internal :命令行解析,JVMTI 使用的内存,这个不受限制,一般不会很大的
  • Symbol : 常量池占用的大小,字符串常量池受-XX:StringTableSize个数限制,总内存大小不受限制
  • Native Memory Tracking :内存采集本身占用的内存大小,如果没有打开采集(那就看不到这个了,哈哈),就不会占用,这个不受限制,一般不会很大的
  • Arena Chunk :所有通过 arena 方式分配的内存,这个不受限制,一般不会很大的
  • Tracing :所有采集占用的内存,如果开启了 JFR 则主要是 JFR 占用的内存。这个不受限制,一般不会很大的
  • Logging,Arguments,Module,Synchronizer,Safepoint,Other,这些一般我们不会关心。

除了 Native Memory Tracking 记录的内存使用,还有两种内存 Native Memory Tracking 没有记录 ,那就是:

  • Direct Buffer :直接内存,主要是指java.nio.DirectByteBuffer在创建的时候分配内存。为啥要使用堆外内存。通常因为:

    • 在进程间可以共享,减少虚拟机间的复制
    • 对垃圾回收停顿的改善:如果应用某些长期存活并大量存在的对象,经常会出发YGC或者FullGC,可以考虑把这些对象放到堆外。过大的堆会影响Java应用的性能。如果使用堆外内存的话,堆外内存是直接受操作系统管理( 而不是虚拟机 )。这样做的结果就是能保持一个较小的堆内内存,以减少垃圾收集对应用的影响。
    • 在某些场景下可以提升程序I/O操纵的性能。少去了将数据从堆内内存拷贝到堆外内存的步骤。
  • MMap Buffer :文件映射内存,尽管从JDK 1.4版本开始,Java内存映射文件(Memory Mapped Files)就已经在java.nio包中,但它对很多程序开发者来说仍然是一个相当新的概念。引入NIO后,Java IO已经相当快,而且内存映射文件提供了Java有可能达到的最快IO操作,这也是为什么那些高性能Java应用应该使用内存映射文件来持久化数据。 作为NIO的一个重要的功能,Mmap方法为我们提供了将文件的部分或全部映射到内存地址空间的能力,同当这块内存区域被写入数据之后会变成脏页,操作系统会用一定的算法把这些数据写入到文件中,而我们的java程序不需要去关心这些。这就是内存映射文件的一个关键优势,即使你的程序在刚刚写入内存后就挂了,操作系统仍然会将内存中的数据写入文件系统。 另外一个更突出的优势是共享内存,内存映射文件可以被多个进程同时访问,起到一种低时延共享内存的作用。