JVM常量池:内存分配链路

目录

内存分配

内存大小

内存空间布局

初始化内存


      上两篇日志把一个简单例子的Java源程序十六进制字节码文件简单分析,JVM对字节码文件的解析顺序大致是从魔数、版本号、常量池、父类、接口、变量再到方法,也就是在第三步时解析程序中的常量池信息。常量池前面说过,它里面记录了Java类中的全部变量和方法信息,包括成员变量,类变量,成员方法和类方法,还有类的构造方法,最后JVM会将常量池信息解析后存储在内存模型的常量区中。JVM对常量池进行解析的链路大致如下:

  1. ClassFileParser::parseClassFile();是JVM解析Java程序字节码文件的接口。
  2. ClassFileParser::parse_constant_pool();对常量池进行内存分配,其中包含解析常量池中的信息。

总的来说分为两部分,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类解析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值