[Inside HotSpot] Java分代堆
[Inside HotSpot] Java分代堆
1. 宇宙初始化
JVM在启动的时候会初始化各种结构,比如模板解释器,类加载器,当然也包括这篇文章的主题,Java堆。在hotspot源码结构中gc/shared
表示所有GC共同拥有的信息,gc/g1
,gc/cms
则是不同实现需要用到的特设信息。
λ tree
├─gc
│ ├─cms
│ ├─epsilon
│ ├─g1
│ ├─parallel
│ ├─serial
│ ├─shared
│ └─z
比如所有的Java堆都继承自CollectedHeap,这个结构就位于gc/shared
,然后Serial GC需要的特设信息位于gc/serial
,关于这点我们后面马上会提到。另外Java堆的类型很多,本文所述Java堆均为分代堆(Generational Heap),它广泛用于Serial GC,CMS GC:
关于什么是分代堆应该不用多说,新生代老年代堆模型都是融入每个Javer灵魂的东西。
在讨论分代堆之前,我们先从头说起。Java堆初始化会经过一个调用链:
JNI_CreateJavaVM(prims/jni.cpp)
->JNI_CreateJavaVM_inner
->Threads::create_vm(runtime/thread.cpp)
->init_globals(runtime/init.cpp)
->universe_init(memory/universe.cpp)
->Universe::initialize_heap()
Universe模块(宇宙模块?hh)会负责高层次的Java堆的创建与初始化:
jint Universe::initialize_heap() {
// 创建Java堆
_collectedHeap = create_heap();
// 初始化Java堆
jint status = _collectedHeap->initialize();
if (status != JNI_OK) {
return status;
}
// 使用的GC,如[38.500s][info][gc] Using G1
log_info(gc)("Using %s", _collectedHeap->name());
ThreadLocalAllocBuffer::set_max_size(Universe::heap()->max_tlab_size());
if (UseCompressedOops) {
if ((uint64_t)Universe::heap()->reserved_region().end() > UnscaledOopHeapMax) {
Universe::set_narrow_oop_shift(LogMinObjAlignmentInBytes);
}
if ((uint64_t)Universe::heap()->reserved_region().end() <= OopEncodingHeapMax) {
Universe::set_narrow_oop_base(0);
}
AOTLoader::set_narrow_oop_shift();
Universe::set_narrow_ptrs_base(Universe::narrow_oop_base());
LogTarget(Info, gc, heap, coops) lt;
if (lt.is_enabled()) {
ResourceMark rm;
LogStream ls(lt);
Universe::print_compressed_oops_mode(&ls);
}
Arguments::PropertyList_add(new SystemProperty("java.vm.compressedOopsMode",
narrow_oop_mode_to_string(narrow_oop_mode()),
false));
}
// TLAB初始化
if (UseTLAB) {
assert(Universe::heap()->supports_tlab_allocation(),
"Should support thread-local allocation buffers");
ThreadLocalAllocBuffer::startup_initialization();
}
return JNI_OK;
}
2. 创建Java堆
在JVM初始化宇宙模块时会调用create_heap()创建堆,这个函数会进一步调用位于memory/allocation
模块的AllocateHeap,但是这些APIs实际还没有做分配动作,它们只是包装底层分配,处理一下分配失败,真正的内存分配是位于底层runtime/os
模块:
说到runtime/os
是底层内存分配,那它到底有多底层?打开源码看看,并没有像OS这个名字一样使用操作系统的VirtualAlloc,sbrk,而是使用C/C++语言运行时的malloc()/free()
进行分配/释放的。
3. 初始化Java堆
在第一步中create_heap()创建了堆这个数据结构,但是里面的成员都是无效的,而第二步就是负责初始化这些成员。初始化分为initialize()和post_initialize()。
// hotspot\share\gc\shared\genCollectedHeap.cpp
jint GenCollectedHeap::initialize() {
char* heap_address;
ReservedSpace heap_rs;
// 获取堆对齐
size_t heap_alignment = collector_policy()->heap_alignment();
// 给堆分配空间
heap_address = allocate(heap_alignment, &heap_rs);
// 如果分配失败则关闭虚拟机
if (!heap_rs.is_reserved()) {
vm_shutdown_during_initialization(
"Could not reserve enough space for object heap");
return JNI_ENOMEM;
}
// 根据刚刚获得的堆空间来初始化
// CollectedHeap中的_reserved字段
initialize_reserved_region((HeapWord*)heap_rs.base(), (HeapWord*)(heap_rs.base() + heap_rs.size()));
// 创建并初始化remembered set
_rem_set = create_rem_set(reserved_region());
_rem_set->initialize();
CardTableBarrierSet *bs = new CardTableBarrierSet(_rem_set);
bs->initialize();
BarrierSet::set_barrier_set(bs);
// 根据刚刚获得的堆来初始化GenCollectedHeap的新生代
ReservedSpace young_rs = heap_rs.first_part(_young_gen_spec->max_size(), false, false);
_young_gen = _young_gen_spec->init(young_rs, rem_set());
heap_rs = heap_rs.last_part(_young_gen_spec->max_size());
// 根据刚刚获得的堆来初始化GenCollectedHeap的老年代
ReservedSpace old_rs = heap_rs.first_part(_old_gen_spec->max_size(), false, false);
_old_gen = _old_gen_spec->init(old_rs, rem_set());
clear_incremental_collection_failed();
return JNI_OK;
}
initialize()初始化新生代老年代,完成基础的分代;然后post_initialize()将新生代细分为Eden和Survivor,然后再初始化标记清楚算法用到的一些数据结构。至此JVM的分代堆就可以为垃圾回收器所用了。
4. JVM分代堆详细结构
4.1 CollectedHeap
前面我们提到JVM是如何建立一个堆的,这一节将详细分析这个堆长什么样子。JVM有很多垃圾回收器,每个垃圾回收器处理的堆结构都是不一样的,比如G1GC处理的堆是由Region组成,CMS处理由老年代新生代组成的分代堆。这些不同的堆类型都继承自gc/share/CollectedHeap
,抽象基类CollectedHeap表示所有堆都拥有的一些属性:
// hotspot\share\gc\shared\collectedHeap.hpp
class CollectedHeap : public CHeapObj<mtInternal> {
private:
GCHeapLog* _gc_heap_log; // GC日志
MemRegion _reserved; // 堆内存表示
protected:
bool _is_gc_active; // 是否正在GC
unsigned int _total_collections; // Minor GC次数
unsigned int _total_full_collections; // Full GC次数
GCCause::Cause _gc_cause; // 当前引发GC的原因
GCCause::Cause _gc_lastcause; // 上次引发GC的原因
...
};
上面的_reserved就表示Java堆这片连续的地址,它包含堆的起始地址和大小,即[start,start+size]
。然而这样的堆是不能满足GC需求的,Full GC处理老年代,Minor GC处理新生代,可是这两个“代”都没有在CollectedHeap中体现。翻翻上图继承模型,GenCollectedHeap才是分代堆。
4.2 GenCollectedHeap
//hotspot\share\gc\shared\genCollectedHeap.hpp
class GenCollectedHeap : public CollectedHeap {
public:
enum GenerationType {
YoungGen,
OldGen
};
protected:
Generation* _young_gen; // 新生代
Generation* _old_gen; // 老年代
...
};
看到GenCollectedHeap里面的_young_gen
和_old_gen
基本就稳了。它继承自CollectedHeap,其中CollectedHeap里面的_reserved表示整个堆,GenCollectedHeap的新生代和老年代进一步划分_reserved。这个划分工作发生在堆初始化中。不同GC使用的新生代老年代也是不同的,所以不能一概而论,hotspot为此建立了如下分代模型:
- 分代基类:公有结构,保存上次GC耗时,该代的内存起始地址,GC性能计数
- 默认新生代:一种包含Eden,From survivor, To survivor的分代
- 并行新生代:可并行GC的默认新生代
- 卡表代:包含卡表(CardTable)的分代
- 久任代:可Mark-Compact的卡表代
- 并行标记清楚代:可并行Mark-Sweep的卡表代
每个代都自己的特色,不同GC根据不同需要可以"自由"组合,比如Serial GC就使用的是DefNewGeneration
+ TenuredGeneration
的组合,CMS使用ParNewGeneration
+ ConcurrentMarkSweepGeneration
的组合。
4.3 SerialHeap
最后一步,Serial GC专用的堆继承自GenCollectedHeap并在其上稍作封装。这个SerialHeap最终将用于串行垃圾回收器(-XX:+UseSerialGC
)。
// hotspot\share\gc\serial\serialHeap.hpp
class SerialHeap : public GenCollectedHeap {
static SerialHeap* heap();
virtual Name kind() const {
return CollectedHeap::Serial;
}
virtual const char* name() const {
return "Serial";
}
...
};
5. 分代堆中的卡表代
关于GenCollectedHeap的各种代还有很多内容,我们关注DefNewGeneration
+ TenuredGeneration
的组合,它被用于SerialGC。
久任代继承自卡表代,所谓卡表代是指用卡(Card)划分整个老年代。我们知道,标记清除需要遍历整个老年代来找出指向新生代的指针,至于为什么要做这个遍历看两副图即可明白。假设有堆中已经存在这样的引用关系:
现在加入分配了新的对象:
其中老年代的对象存在指向新生代的指针,但是GC Root并没有,如果这时候只从GC Root出发标记对象,就会错过红线指向的对象,继而导致被误做垃圾而清除,所以必须遍历老年代找到指向新生代的对象。但是问题是老年代一般都很大,这样的遍历是比较慢的。卡表应此而生,它将老年代划分为512字节的卡(Card),这些卡组成卡表(Card table),卡表具体来说是一个字节数组。如果卡表中某一个字节不为dirty,则表示对应的512字节的区域不存在指向新生代的引用,那么就可以直接跳过该区域,减少了遍历时间:
在上图中只有卡1和卡5存在指向新生代的指针,对整个老年代的遍历就缩小到只对卡1、卡5的遍历了。
[Inside HotSpot] Java分代堆的更多相关文章
- Java分代的思考
我们都知道,java的gc是基于java的分代前提,不管是CMS还是G1,都是基于分代思想:老年代和新生代 思考一:为什么可以分代? 1.java中对象的生命周期是不一样的,在gc中对应年龄的概念. ...
- JAVA分代收集机制详解
Java堆中是JVM管理的最大一块内存空间.主要存放对象实例. 在JAVA中堆被分为两块区域:新生代(young).老年代(old). 堆大小=新生代+老年代:(新生代占堆空间的1/3.老年代占堆空间 ...
- Java分代垃圾回收机制:年轻代/年老代/持久代(转)
虚拟机中的共划分为三个代:年轻代(Young Generation).年老点(Old Generation)和持久代(Permanent Generation).其中持久代主要存放的是Java类的类信 ...
- [Inside HotSpot] Java的方法调用
1. 方法调用模块入口 Java所有的方法调用都会经过JavaCalls模块.该模块又细分为call_virtual调用虚函数,call_static调用静态函数等.虚函数调用会根据对象类型进行方法决 ...
- [Inside HotSpot] Epsilon GC
1. Epsilon GC简介 Epsilon GC源于RedHat开发者Aleksey Shipilëv提交的一份JEP 318: Epsilon: A No-Op Garbage Collecto ...
- JVM堆内存控制/分代垃圾回收
JVM的堆的内存, 是通过下面面两个参数控制的 -Xms 最小堆的大小, 也就是当你的虚拟机启动后, 就会分配这么大的堆内存给你 -Xmx 是最大堆的大小 当最小堆占满后,会尝试进行GC,如果GC之后 ...
- 浅谈Java堆内存分代回收
目录 1.概述 2.堆内存是如何分代的 3.各分代之间是如何配合工作的 1.概述 与C++不同的是, 在Java中我们无需关心对象占用空间的释放, 这主要得益于Java中的垃圾处理器(简称GC)帮助我 ...
- Android内存管理(11)*常见JVM回收机制「Java进程内存堆分代,JVM分代回收内存,三种垃圾回收器」
参考: http://www.blogjava.net/rosen/archive/2010/05/21/321575.html 1,Java进程内存堆分代: 典型的JVM根据generation(代 ...
- Java虚拟机:JVM内存分代策略
版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! Java虚拟机根据对象存活的周期不同,把堆内存划分为几块,一般分为新生代.老年代和永久代(对HotSpot虚拟机而言),这就是JVM的内存 ...
随机推荐
- STM32F407VET6之IAR之ewarm7.80.4工程建立(基于官方固件库1.6版本)
今天把stm32F407的工程之IAR建立完成了,特此记录下. 下载官方固件库,STM32F4xx_DSP_StdPeriph_Lib_V1.6.1,V1.8.0版本的同理.新建以下几个文件 src放 ...
- HTML5之中国象棋,附带源码!
好久没写随笔了,好怀恋2013年的日子,因为现在不能回到过去了! 再见了 感谢你为我做的一切! 进入正题:HTML5之中国象棋 很小就会下象棋了, 这是象棋的测试地址:点击我吧 然后点击里面的象 ...
- Java项目引入eclipse注意事项
我以前也搞过java,后转前端,接触html+css+js时间比较多,所以java后端也忘了差不多.最近负责公司的邮件系统项目,项目是java语言写,项目架构比较复杂,在部署项目的时候,遇到了很多问题 ...
- Spring core resourc层结构体系及JDK与Spring对classpath中资源的获取方式及结果对比
1. Spring core resourc层结构体系 1.1. Resource相关结构体系 1.2. ResourceLoader相关体系 2. JDK与Spring对classpath中资源的获 ...
- 大数据学习——akka学习
架构图 重要类介绍 ActorSystem 在Akka中,ActorSystem是一个重量级的结构,他需要分配多个线程,所以在实际应用中,ActorSystem通常是一个单例对象,我们可以使用这个Ac ...
- Pycharm 简单设置
- django自定义过滤器和标签
1.自定义过滤器和标签的流程: 1.在某个app下创建一个名为templatetags(必需,且包名不可变)的包.假设我们在名为app01的app下创建了一个templatetags的包,并在该包下创 ...
- 项目报错 java lang illegalargumentexception error at 0 can t find referenced pointcut
出现error at ::0 can't find referenced pointcut...这样的错误原因是:如果你用的JDK版本是1.6的话,而引用的aspectjrt.jar是spring-2 ...
- Python 开启线程的2中方式,线程VS进程(守护线程、互斥锁)
知识点一: 进程:资源单位 线程:才是CPU的执行单位 进程的运行: 开一个进程就意味着开一个内存空间,存数据用,产生的数据往里面丢 线程的运行: 代码的运行过程就相当于运行了一个线程 辅助理解:一座 ...
- 通过performance schema收集慢查询
MySQL5.6起performance schema自动开启,里面涉及记录 statement event的表 mysql> show tables like '%statement%'; + ...