运行时数据区域
Java虚拟机在运行Java代码时,它会把他所管理的内存分为若干个不同的数据区域。这些区域都各有各的目的和用途,大致可分为一下几个区域:程序计数器,方法区,虚拟机栈,本地方法栈,堆。
程序计数器:字节码的行号,编译器用来改变程序计数器来选取下一条需要执行的字节码指令,每个线程都有一个独立计数器。
虚拟机栈:Java方法执行的内存模型,每个方法在执行的时候会生成一个栈帧,栈帧用于存储局部变量表,操作数栈,动态链接,以及方法出口接口。对于方法的调用到结束,也就对应着一个栈帧的进栈和出栈。对于虚拟机栈的理解很好记忆,我们只需想起他是用于存储Java方法的内存模型,对于一个方法,它有局部变量,操作符,以及执行方法时需要知道方法的进出口,便可记住栈帧的大致内容,不需要死记硬背。
本地方法栈:与虚拟机栈的功能相似。但是,虚拟机栈执行的是Java方法,而本地方法栈执行的是Native方法服务。什么是Native方法?一个native方法就是该方法的实现由非java语言实现,为什么要有native方法?是用来于java外界进行交互。
堆:用来存储Java对象的内存区域,几乎所有的对象实例都存储在这里分配内存,当然不是绝对。由于Java堆是垃圾回收器管理的主要区域,有时候也被称作GC堆。从内存回收的角度来看,现在的收集器采用分代收集算法,所以Java堆可以分为:新生代,老年代。Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。
方法区:与堆一样,用来存储已被加载的类的信息,常量,静态变量,及时编译器编译后的代码等数据。
运行时常量池:是方法区的一部分,用于存放编译期生成的各种字面量和符号引用,这部分内容在类加载后进入方法的运行时常量池中。
*直接内存:并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域,但也被频繁使用。
HopSpot虚拟机的对象介绍
上面介绍的是虚拟机的运行时数据区的划分,已经都存放了上面东西,现在更进一步介绍,但是不同的虚拟机有所不同,所以必须针对特定的虚拟机进行特定的详细介绍。如HotSpot虚拟机。
①从虚拟机的角度
创建对象:当我们使用new关键字创建一个对象时,虚拟机遇到一个new关键字时,他会去检查常量池中是否能定位到一个类的符号引用,并且检查这个符号引用代表的对象是否已经被加载,解析,初始化。如果没有,就必须先执行相应的加载。
分配内存:在加载结束后,接下来为对象分配内存。对象分配内存有“指针碰撞”,“空闲列表”两种方法。指针碰撞即使用一份指针用来作为划分的标界。空闲列表即虚拟机维护一个列表,记录哪些块是有用的,在分配的时候从列表中找到一个足够大的空间进行划分给对象实例。
初始化:虚拟机需要将分配到的内存进行初始化为零值,
必要的设置:例如这个对象属于哪个类,如何才能找到类的元数据信息,对象的哈希码,对象的GC分代年龄等信息,这些信息放在对象的对象头中。
②Java的角度
对象创建才刚刚开始,-<init>方法还没有执行,所有的字段都还为零,所以执行new指令后会紧接着执行<init>方法,把对象按照程序员的意愿进行初始化。
在hotSpot中,对象的内存布局:
对象在内存中存储的布局可以分为3块区域:对象头,实例数据,对齐填充。
对象头:包括两部分,第一部分存储对象自身的运行时数据,如哈希码,GC分代炼灵,线程持有地锁等等,第二部分是类型指针,用来指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象属于哪个类的实例,并不是所有的虚拟机实现都必须在对象数据上保留类型指针。
实例数据:是对象真正存储的有效信息,也就是在程序中定义的各种类型的字段内容。
对齐填充:无意义,仅起者占位符的作用,由于HotSpot虚拟机规定自动内存管理系统要求对象起始地址必须是8字节的整数倍,也就是对象的大小必须是8字节的整数倍。对象头正好8字节的倍数,所以当对象实例数据没有对齐时,就需要自动对齐。
对象的访问
Java程序需要通过栈上的reference数据来操作堆上的具体对象,reference只是一个引用,并没有规定如何实现,所以目前主流的实现访问方式有两种,一种是句柄,一种是指针。
句柄:Java堆中划分一个句柄池,句柄保存对象的地址,reference中存储的就是对象的句柄地址。
指针:直接使用指针访问,指针直接指向对象的地址,reference中存储的直接就是对象地址。
以上两种方法各有各的优势,使用指针访问,速度快,使用句柄,对于对象的变动,只需修改句柄不需要修改reference本身。
本章内容讲解了Java虚拟机的运行时数据区在内存中的分布情况,以及以具体的HopSpot虚拟机为例,进行详细的对象生成,分布,访问的具体内容。下一章将介绍垃圾收集器。内容参考—《深入理解Java虚拟机》