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本身运行时出现的问题。