Java运行时数据区域分为方法区,虚拟机栈,本地方法栈,堆和程序计数器。

程序计数器

程序计数器可以看做是当前线程所执行的字节码的行号指示器。 每条线程都有一个独立的程序计数器。

java虚拟机栈

java虚拟机栈也是线程私有的,它主要存储局部变量表。包括各种基本数据类型,对象引用,returnAddress类型。

局部变量表所需要的空间在编译期间就完成分配,大小也是固定的,方法运行期间不会改变变量表的大小。

java堆

Java堆是被所有线程共享的一块内存区域,也是垃圾回收期管理的主要区域,因此很多时候也被称为”GC堆”。

Java堆还可以细分为新生代和老年代。新生代用于存放新生的对象,而老年代用来存放生命周期较长的对象。

方法区

方法区也是各个线程共享的内存区域,用于存储被虚拟机加载的类信息,常量,变量,静态变量,即时编译后的代码等。

运行时常量池

运行时常量池是方法区的一部分,用于存放编译期生成的各种字面量和符号引用。

对象创建的过程

当虚拟机遇到一条new指令时,会大致做以下操作:

  • 检查这个指令的参数是否能够在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载,解析和初始化过。

  • 为对象分配内存。根据java堆中的内存是否规整,有两种分配方式:如果内存规整,使用指针碰撞,只是将空闲内存的指针往空闲内存挪动一段与对象大小相等的距离。如果内存不规整,虚拟机就必须维护一个列表,记录哪些内存块是可用的,在分配的时候从列表中找到足够大的一块分配给对象实例,这种方式叫空闲列表。

  • 考虑到内存分配时的并发情况,我们可以对分配内存空间的动作做同步处理,比如CAS。另一种就是把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一小块内存,称为本地线程分配缓冲(TLAB).

  • 内存分配完成以后虚拟机将分配到的内存空间初始化为零值,然后对对象进行必要的设置,比如对象是哪个类的实例,对象的哈希码,对象的GC分代年龄信息等,这些信息都存放在对象头中。

  • 然后调用方法,把对象按照程序员的意愿进行初始化。

对象的内存布局

对象在内存中的布局可以分为三块区域:对象头,实例数据,对齐填充。

对象头包括两部分信息,第一部分用于存储对象自身的运行时数据,如哈希码,GC分代年龄等。另一部分是类型指针,即对象指向它的类元数据的指针。

实例数据存储对象真正的有效信息。

对齐填充部分只是在对象大小不是8字节的整数倍时用来填充补齐。

对象的访问定位

java程序需要通过栈上的引用数据来操作堆上的具体对象。目前主流的访问方式有使用句柄和直接指针两种。

  • 使用句柄访问时,java堆中会划分出一块内存来作为句柄池,引用存储的就是对象的句柄地址。句柄中包含了对象实例数据和类型数据各自的地址信息。

  • 使用直接指针访问,引用中存储的就是对象地址,java堆对象的布局中就必须考虑如何放置访问类型数据的相关信息。