JVM探索-5.内存分配与管理

时间:2022-10-31 13:12     作者:林立     分类: 作品


  JVM作为内存分配的管理器,一定涉及如何与内存交互。那么JVM是如何管理内存的?实际上内存管理的算法很多,简单来说JVM从操作系统申请一块内存,然后根据不同的GC算法进行管理。
  首先JVM先通过操作系统的系统调用(system call)进行内存的申请,典型的就是mmap。众所周知glibc提供了我们常用的内存管理函数如malloc/free/realloc/memcopy/memset等。为什么JVM不直接使用这些函数?glibc里面的malloc也是通过mmap等系统调用来完成内存的分配,之后glibc再对已经分配到的内存进行管理。GC算法实现了一套自己的管理方式,所以再基于malloc/free实现效率肯定不高。mmap必须以PAGE_SIZE为单位进行映射,而内存也只能以页为单位进行映射,若要映射非PAGE_SIZE整数倍的地址范围,要先进行内存对齐,强行以PAGE_SIZE的倍数大小进行映射。还要注意一点,操作系统对内存的分配管理典型地分为两个阶段:保留(reserve)和提交(commit)。保留阶段告知系统从某一地址开始到后面的dwSize大小的连续虚拟内存需要供程序使用,进程其他分配内存的操作不得使用这段内存;提交阶段将虚拟地址映射到对应的真实物理内存中,这样这块内存就可以正常使用。
  对于保留和提交,Windows在使用VirtualAlloc分配内存时传递不同的参数MEM_RESERVE/MEM_COMMIT,Linux在mmap保留内存时使用MAP_PRIVATE|MAP_NORESERVE|MAP_ANONYMOUS,提交内存时使用MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS。其中MAP_NORESERVE指不要为这个映射保留交换空间,MAP_FIXED使用指定的映射起始地址。
  JVM中常见的对象类型有以下6种:
  ResourceObj:线程有一个资源空间(Resource Area),一般ResourceObj都位于这里。定义资源空间的目的是对JVM其他功能的支持,如CFG、在C1/C2优化时可能需要访问运行时信息(这些信息可以保存在线程的资源区)。
  StackObj:栈对象,声明的对象使用栈管理。其实栈对象并不提供任何功能,且禁止New/Delete操作。对象分配在线程栈中,或者使用自定义的栈容器进行管理。
  ValueObj:值对象,该对象在堆对象需要进行嵌套时使用,简单地说就是对象分配的位置和宿主对象(即拥有这个ValueObj对象的对象)是一样的。
  AllStatic:静态对象,全局对象,只有一个。值得一提的是C++中静态对象的初始化并没有通过规范保证,可能会有一个问题,就是两个静态对象相互依赖,那么在初始化的时候可能出错。JVM中的很多静态对象的初始化,都是显式调用静态初始化函数。
  MetaspaceObj:元对象,比如InstanceKlass这样的元数据就是元对象。
  CHeapObj:这是堆空间的对象,由new/delete/free/malloc管理。其包含的内容很多,比如Java对象、InstanceOop(G1对象分配出来的对象)。除了Java对象,还有其他的对象也在堆中。
  JVM中为了准确描述这些堆中的对象,以方便对JVM进行优化,所以又定义了更具体的子类型,代码如下所示:

hotspot/src/share/vm/memory/allocation.hpp
// JVM中使用的内存类型
  mtJavaHeap          = 0x00,  // Java堆
  mtClass             = 0x01,  // JVM中Java类
  mtThread            = 0x02,  // JVM中线程对象
  mtThreadStack       = 0x03,
  mtCode              = 0x04,  // JVM中生成的编译代码
  mtGC                = 0x05,  // GC的内存
  mtCompiler          = 0x06,  // 编译器使用的内存
  mtInternal          = 0x07,  // JVM中内部使用的类型,不属于上述任何类型
  mtOther             = 0x08,  // 不是由JVM使用的内存
  mtSymbol            = 0x09,  // 符号表使用的内存
  mtNMT               = 0x0A,  // NMT使用的内存
  mtClassShared       = 0x0B,  // 共享类数据
  mtChunk             = 0x0C,  // Chunk用于缓存
  mtTest              = 0x0D,
  mtTracing           = 0x0E,
  mtNone              = 0x0F,

  这些信息描述了JVM使用内存的情况,这一部分信息能够帮助定位JVM本身运行时出现的问题。