《JVM G1源码分析和调优》 —2.5 内存分配和管理

举报
华章计算机 发表于 2019/12/20 13:21:40 2019/12/20
【摘要】 本节书摘来自华章计算机《JVM G1源码分析和调优》 一书中第2章,第2.5节,作者是彭成寒。

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)来进一步解读这些信息。


【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。