目录
内存分配
内存大小
内存空间布局
初始化内存
上两篇日志把一个简单例子的Java源程序十六进制字节码文件简单分析,JVM对字节码文件的解析顺序大致是从魔数、版本号、常量池、父类、接口、变量再到方法,也就是在第三步时解析程序中的常量池信息。常量池前面说过,它里面记录了Java类中的全部变量和方法信息,包括成员变量,类变量,成员方法和类方法,还有类的构造方法,最后JVM会将常量池信息解析后存储在内存模型的常量区中。JVM对常量池进行解析的链路大致如下:
总的来说分为两部分,JVM对常量池先分配内存再解析里面的信息。
先从常量池的内存分配链路开始:
constantPoolOop constant_pool =
oopFactory::new_constantPool(length,
oopDesc::IsSafeConc,
CHECK_(nullHandle));
这是ClassFileParser::parse_constant_pool()函数中实现内存分配部分的代码,可以看到,oopFactory::new_constantPool()函数的入参有三个,第一个length表示的是再Java程序的字节码文件中,常量池里一共包含多少个元素,该常量池参数length再编译期间就会根据程序中包含的变量和方法计算出来,所以它会直接编译在字节码文件里,JVM可以直接读取,不过这里要处理,length标识的是常量池里含有的元素个数,不是代表常量池所占的内存空间大小。
内存空间的分配从oopFactory::new_constantPool()一直到mutableSpace::allocate(),最终调用的是object_space()->allocate()函数:
HeapWord* MutableSpace::allocate(size_t size) {
assert(Heap_lock->owned_by_self() ||
(SafepointSynchronize::is_at_safepoint() &&
Thread::current()->is_VM_thread()),
"not locked");
HeapWord* obj = top();
if (pointer_delta(end(), obj) >= size) {
HeapWord* new_top = obj + size;
set_top(new_top);
assert(is_object_aligned((intptr_t)obj) && is_object_aligned((int
ptr_t)new_top), "checking alignment");
return obj;
} else {
return NULL;
}
}
HeapWord* obj = top();首先obj指针指向的是内存区域堆的最顶端,然后再执行HeapWord* new_top = obj + size;将当前堆顶端指针再往高地址方向移动size大小字节,来完成堆中当前类的常量池内存分配。
从上面的内存分配链路可以看到,从最开始的oopFactory::new_constantPool()函数就传入了大小参数length,到最后调用的object_space()->allocate()也要传入size,也就是这个常量池大小参数size从开始一直传递到末尾,前面说到length只是常量池中元素个数的参数,那么这个Java类常量池大小参数size是怎么获取的呢?在常量池初始化链路中会调用constantPoolOopDesc::object_size(length)方法来得到常量池的大小size,方法实现如下:
static int object_size(int length) {
return align_object_size(header_size() + length);
}
static int header_size() {
return sizeof(constantPoolOopDesc)/HeapWordSize;
}
在header_size对象头的大小计算中有一个HeapWordSize,顾名思义是HeapWord类的大小,HeapWord类只包含一个char*指针,前面说过在32位系统里该指针大小位4字节,在64位系统的大小为8字节。由此可见,在header_size()函数中,sizeof(constantPoolOopDesc)获得constantPoolOopDesc类实例所占内存字节,然后处以HeapWordSize也就是当前平台上的指针宽度,得到constantPoolOopDesc类实例需要的双字(一个双字占4个字节)内存大小。所以,object_size()函数的返回值标识的是constantPoolOopDesc类所需要的内存大小再加上常量池的大小length。
JVM为constantPoolOop对象分配headSize+length个指针宽度的内存,该片内存位于JVM的永久区permanent区,它是一片连续的区域:
下面为高地址位,上面为低地址位,由上图可以看到,低地址位里是个元素_mark到_orig_length是constantPoolOop对象里的元素,_mark和_metadata继承自oopDesc类,这十个元素一共占40个字节,在32位系统环境下每一个指针宽度位4个字节。为该对象分配内存时,先为对象头分配再为实例数据分配,也就是下面的指针宽度个数据,它们具体指的是Java字节码文件里的常量池元素。
为常量池分配好内存空间后,JVM还要做的就是对该部分内存进行初始化,随着JVM不断对堆区进行新类的加载和旧类的移除,在新类加载进来时,需要先对为其分配的区域进行清零,具体调用的函数是pd_fill_to_words():
static void pd_fill_to_words(HeapWord* tohw, size_t count, juint value) {
#ifdef AMD64
julong* to = (julong*) tohw;
julong v = ((julong) value << 32) | value;
while (count-- > 0) {
*to++ = v;
}
#else
juint* to = (juint*)tohw;
count *= HeapWordSize / BytesPerInt;
while (count-- > 0) {
*to++ = value;
}
#endif // AMD64
}
清零操作,在该函数中做法就是将内存区里的数据value清为0值,避免影响对后续的Java类解析。