购买
下载掌阅APP,畅读海量书库
立即打开
畅读海量书库
扫码下载掌阅APP

2.5 内存分配和管理

C/C++程序员和Java程序员最大的区别之一就是对内存管理的工作,Java程序员不需要管理内存,因为有JVM帮助管理。所以JVM的所谓开发必然涉及内存的分配和管理。我们这里尽可能地简化描述内存分配和管理,只描述和GC算法相关的部分。本质上来说,了解这一部分内容越多,特别是了解JVM如何与操作系统交互的部分,越容易对JVM调优。

JVM作为内存分配的管理器,一定涉及如何与内存交互。那么JVM是如何管理内存的?实际上内存管理的算法很多,简单来说JVM从操作系统申请一块内存,然后根据不同的GC算法进行管理。下面以Linux为例看一下JVM是如何做的。

首先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中我们还看到了使用类库函数malloc/free的地方。这和JVM内存管理策略有关,JVM内部也有很多数据需要在堆中分配,而这和Java堆空间没有关系,所以直接使用类库函数。另外需要提一下JVM推荐使用jemalloc替代glibc,原因是其效率更高。

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本身运行时出现的问题,我们将在最后的附录B中通过本地内存跟踪(Native Memory Tracking)来进一步解读这些信息。 qE/TGiyHd7HsE5syG9hWivtlAXUwEo6eLnjXxDGCMjfVX0SNzSuR+vYrBPPhkV8A

点击中间区域
呼出菜单
上一章
目录
下一章
×