7.1 什么是HotSpotVM

HotSpotVM的特点在于“仅将程序中执行频率很高的部分编译为机器语言”。这样做是为了优化程序中耗时最多的部分,缩短程序的整体运行时间。此外,由于HotSpotVM会把要编译为机器语言的代码限制在某个范围内,所以还可以缩短编译时间。“执行频率很高的部分”称为HotSpot,这也就是HotSpotVM这个名字的由来。
HotSpotVM另外一个特点实现了多种GC算法。GC算法的调优通常分为以响应性能优先,还是以吞吐性能为优先的。一般来说,优先响应性能的GC算法,其吞吐性能比较差;反之亦然。

7.2 什么是OpenJKDE

8 对象管理功能

在HotSpotVM中,我们可以自主选择使用哪种GC算法——只要在Java的启动选项中像“-XX:+UseParalleGC”这样指定即可。不同GC算法所管理的堆的内存布局并不相同。本书将HotSpotVM的堆和GC统称为对象管理功能。

8.1 对象管理功能的接口

下图是对象管理功能的接口示意图
image.png
对象管理功能对VM主要公开了以下3中接口
① 对象的分配
② 显示地执行GC
③ 依赖于对象位置和布局的处理

接口①会在VM指定对象的类型后,将VM堆内部分配的对象实体返回给我们。
接口②会在我们请求执行GC时,在VM堆内部执行GC。
VM并不知道VM堆内部对象的位置和布局,因此③是必须的。具体来说,③中定义了一系列接口,如对VM堆内部所有对象都执行指定函数的接口,对某个对象内部所有字段都执行指定的函数的接口,以及检查指定的内存地址是否被分配了对象的接口等。

8.2 对象管理功能的全貌

image.png
CollectedHeap类是接口。CollectedHeap类根据CollectorPolicy类内部的设定值来决定GC策略。而CollectedHeap类对各个GC类发送请求,要求他们对堆内部执行GC。

8.3 CollectedHeap类

image.png
如上图,VM堆是由CollectedHeap这个抽象类统一进行处理的。CollectedHeap类会根据VM堆的布局派生出子类。这个子类就是VM堆的实体。
下面是OpenJDK7中可以指定的GC启动选项和GC所对应的VM堆类

启动选项 GC算法 VM堆类
-XX:UseSerialGC 串行CG GenCollectedHeap
-XX:UseParallelGC 并行GC ParallelScavengeHeap
-Xincgc 增量GC GenCollectedHeap
-XX:UseConcMarkSweepGC 并发GC GenCollectedHeap
-XX:UseG1GC G1GC G1CollectedHeap

如上表,GC算法与VM堆类之间没有明确的对应关系。GenCollectedHeap会被多种GC算法使用,但G1CollectedHeap只会被G1GC使用。
HotSpotVM的并行GC和G1GC并没有使用GenCollectedHeap,但他们都属于分代GC算法。

8.4 CollectorPolicy类

下面是定义管理功能策略的CollectorPolicy类。
image.png

如上图,对象管理功能的策略是由CollectorPolicy抽象类统一管理的。CollectorPolicy会根据对象管理功能的策略派生出相应的子类。

8.4.1 启动选项和CollectorPolicy类

OpenJDK7中可以指定的GC启动选项,和所使用的CollectorPolicy子类之间的对应关系如下表

启动选项 策  略
- XX:UseSerialGC MarkSweepPolicy
-XX:UsePararllelGC GenerationSizer
-Xincgc ConcurrentMarkSweePolicy
( CMSIncrementalMode=true )
-XX:UseConcMarkSweepGC ConcurrentMarkSweePolicy
-XX:UseG1GC G1CollectorPolicy_BestRegionsFirst

CollectorPolicy中以某种形式保存着在Java的启动选中中指定的GC相关信息。除了以上可以指定GC算法的启动选项外,常用的还有通过-Xms指定的初始堆大小的选项。此外,与所有指定的GC算法相关的具体设定值,比如G1GC的MaxGCPauseMillis(最大暂停时间)等信息页保存在CollectorPolicy中。
CollectedHeap会参考保存在CollectorPolicy中的信息,自定决定GC的执行策略并执行合适的GC处理。

9 堆结构

下面是G1GC的VM堆结构。

9.1 VM堆

HotSpotVM的VM堆大体上分为以下两个部分。
① 程序员选择的GC算法所使用的内存空间
② 常驻(permanent)内存空间

image.png

当选择了GC算法后,HotSpotVM会创建一个结构上适合该算法的内存空间,这个内存空间就是上面的①。该GC算法管理的对象会被分配在这块内存空间中。
② 常驻内存空间中“常驻”的英文时permanent是“永久”的意思,顾名思义,常驻内存空间中分配的是类型信息或者方法信息等永久存在的对象。该空间的结构几乎不会随着GC算法的变化而变化。

9.1.1 VM堆类的初始化

所有的VM堆都继承自CollectedHeap。

9.2 G1GC堆

G1GC的堆被分为了一定大小的若干区域。

9.2.1 G1CollectedHeap类

以下主要介绍G1CollectedHeap类的3个主要成员变量

  • _hrs:通过数组维护所有的HeapRegion
  • _yong_list:新生代HeapRegion的链表
  • _free_region_list:空闲HeapRegion的链表

image.png

管理各个区域的是HeapRegion类。G1CollectedHeap类中有名为_hrs的成员变量,保存着指向HeapRegionSeq实例的指针。在HeapRegionSeq的成员变量_regions中,以数组的形式保存着与G1GC堆内部所有区域相对应的HeapRegion地址。
各个HeapRegion通过G1CollectedHeap类中的_yong_list和_free_region_list,以单向链表的方式连接在一起。
新生代的HeapRegion与_yong_list相连,对应空闲区域的HeapRegion与空区域链表(_free_region_list)相连,而老年代的HeapRegion则没有与任何东西连接在一起。

9.2.2 HeapRegion类

HeapRegion类中的两个成员变量_bottom和_end分别保存着区域的头地址和尾地址。
image.png

image.png

9.3 常驻空间

管理G1GC常驻空间的是CompactingPermGenGen类。
g1CollectedHeap类的_perm_gen成员变量中保存着指向CompactingPermGenGen实例的指针。
常驻空间并不是G1GC的回收对象,而是标记——压缩GC(mark-compact GC)的回收对象。

10 分配器

HotSpotVM的内存分配器

10.1 内存分配的流程

内存分配流程的第一步是按照G1GC最大VM堆(G1GC堆与常驻内存空间)的大小来申请内存空间。程序员可以指定GCGC最大堆空间和最大常驻空间的大小。如果没有指定,那么G1G最大堆空间默认为64MB,最大常驻空间也默认为64MB,总计128MB(根据使用的操作系统不同,默认是有所不同)。另外,G1GC的VM堆是按照区域大小对齐的。
image.png
在目前这个阶段还只是申请内存空间,并没有实际地分配物理内存。
接下来,需要为之申请的VM堆分配最小限度的内存空间。这里会实际分配物理内存。G1GC堆的内存会以区域为单位分配。

image.png

分配器已经在G1GC对上为区域分配了空间,对象则会被分配到相应的区域内。
image.png

随着越来越多的对象被分配,可使用的区域会逐渐枯竭。这时需要从之前申请的内存空间中取出内存分配给新的区域,让G1GC堆得到扩展。于是对象就可以被分配到刚才新分配的区域中。
image.png

10.2 VM堆的申请

各VM堆的初始化的处理器编写在继承自CollectedHeap类的各个子类的initialize()方法中。对于G1GC而言,就是在G1CollectedHeap的initialize()方法中,

10.3.4 VM堆中实现对齐的方法

VM堆是以区域大小对齐的。也就是说,VM堆的头地址是区域大小的整数倍。那么,HotSpotVM中如何实现对其的?
假设对其大小(区域大小)为1KB,VM堆的大小大于1KB。具体的对齐步骤如下图
image.png
① 申请VM堆大小的内存空间
② 记录在①的内存范围内且地址是1KB的倍数的地址
③ 释放之前申请的内存空间
④ 指定通过②记录的地址,再次申请VM堆大小的内存空间
⑤如果④失败了,则返回①重新开始

首先,在①中申请VM堆大小的内存空间。申请内存空间时使用os::reserve_memory函数。
在申请的内存空间反诬之内的某个地方一定存在1KB的倍数的地址——因为我们申请的是大于1KB的内存空间——这个1KB的倍数的地址就是对其地址。非常重要的一点是,所获取的对其地址是由操作及想鸥汀返回给我们的,这块内存空间确定可以使用。②是将这个地址记录下来的步骤。
③会把通过①申请的内存空间释放掉。由于只是申请了这块内存空间,还没有进行实际的内存分配,所以释放这块内存空间的性能开销很小。
④是通过②记录的内存地址,再次申请VM堆大小的内存空间。由于实际申请内存空间的mmap()和VirtualAlloc()可以用于指定要申请的空间的头地址,所以这里用它们来申请。
但是,②中能够使用的地址,到了④的时候可能会变得不可用。如果④失败了,则返回①重新开始,直到成功为止。

10.4 对象的分配

10.4.1 对象分配的流程

从调用CollectedHeap的通用接口,到实际从G1GC的VM堆上分配对象的流程如图
image.png

10.5 TLAB

TLAB(Thread Local Allocation Buffer,线程本第一分配缓存区)是对象分配的要点之一。
VM堆是所有线程共享的内存空间,因此在从VM堆上分配对象时,必须锁定整个VM堆,以防止其他线程同时分配对象。
但是让不同的线程工作于不同的CPU核心上就是为了提高效率,要是为了分配对象而让它们等待VM堆上的锁定释放,这未免来浪费了。而TLAB解决这个问题的思路,就是让各个线程分别持有自己专用的分配对象的缓冲区,从而减少锁定次数。
当一个线程第一次分配对象时,它会从VM堆中得到一定大小的内存空间,然后作为它自己的缓冲区保存起来。这块缓冲区就称为TLAB,这样一来,仅在分配TLAB时锁定VM堆即可。
当同一个线程第二次分配对象是,它会从TLAB中分配目标对象大小的内存。这时其他线程不可能访问这块缓冲,因而就没有必要锁定VM堆了。
image.png

TLAB默认不启用,程序员可以在Java的启动项中开启并指定其大小。

11 对象结构

12 HotSpotVM的线程管理

G1GC是一种结合了并行GC和并发GC的算法。并行GC和并发GC是通过它们各自的线程实现的。

12.1 线程操作的抽象化

在HotSpotVM内有一个能够以相同方式调用不同操作系统的线程的抽象层。为了能够在HotSpotVM内轻松调用线程,开发者们在设计上花了不少工夫。

13 线程的互斥处理

13.1 什么是互斥处理

如果线程共享内存空间,那么就会出现多个线程同时在一个地址上读写数据的情况。有些数据可能会因其他先的插入而更改,如果在编程是没有考虑这一点,内存就会在意想不到的地方遭到破坏。

13.2 互斥量

实现互斥处理的一种简单的方式是使用互斥量。这个词是有词组mutal exclusion(互斥条件)合成而来的。