Java运行时的数据区域

方法区、虚拟机栈、本地方法栈、堆、程序计数器

程序计数器

线程私有
为了支持多线程,Java虚拟机为每个线程设置独立的程序计数器,各条线程之间计数器互不影响,独立储存。
如果线程执行的是Java方法,计数器指向正在执行的字节码指令地址,如果线程执行的是Native方法,这个计数器则为空
程序计数器区域是唯一一个没有任何OutOfMemoryError情况的区域。

Java虚拟机栈

线程私有
每个方法在执行的同时都会向虚拟机栈申请一个栈帧用于储存局部变量表、操作数栈、动态链接、方法出口等信息,每个方法的调用直到执行完成,对应一个栈帧在虚拟机栈中入栈到出栈的过程,局部变量表存放了方法执行过程中的所有局部变量,编译期间就确定了。所以,这个栈帧的大小是完全固定的。
虚拟机栈会有StackOverflowError和OutOfMemoryError,前者在固定长度的虚拟机栈中出现,后者在变长虚拟机栈中出现。这要看虚拟机是哪一种虚拟机。

本地方法栈

类似于Java方法使用Java虚拟机的栈,这一区域是本地方法使用的栈。
有StackOverflowError和OutOfMemoryError。

Java堆

线程共享
此内存唯一目的是存放对象实例,Java虚拟机规范中描述到:所有对象实例以及数组都要在堆上分配。但是随着JIT编译器等技术等发展,所有对象都分配在堆上也渐渐不那么绝对了。
Java堆允许不连续
Java堆有OutOfMemoryError

方法区

线程共享
储存虚拟机加载的类信息,常量,静态变量,即时编译后的代码等数据
可以选择不进行垃圾回收,但这不明智
有OutOfMemoryError

运行时常量池

是方法区的一部分
允许在程序运行时创建常量
有OutOfMemoryError

直接内存

不是虚拟机的一部分,在NIO类中,允许Native函数库向操作系统申请内存,提供一些方式访问,使得在一些场景提高性能,
有OutOfMemoryError

HotSpot VM

对象的创建

当虚拟机碰到new的时候,会先检查类是否被加载、解析、初始化,如果没有,则先执行相应的加载过程,当类检查通过以后,会为对象分配内存,这个内存大小是完全确定的,虚拟机会从虚拟机堆中分配这块内存并将这块内存初始化为0,然后执行init方法。
因为Java需要支持多线程,所以这里实际需要同步处理,还有一种解决方案叫做TLAB(Thread Local Allocation Buffer)预先为每个线程分配一小块内存,就不会受到多线程影响,当TLAB用完以后,需要分配新的TLAB,这时才需要同步锁定,在TLAB分配时即可提前初始化这块内存为0,当然也可以不提前。

对象的内存布局

内存中储存的布局可以分为3块区域: 对象头、实例数据和对齐填充。
对象头分为两部分,第一部分储存了哈希码、GC分代年龄、锁状态、线程持有锁、偏向线程ID等等这部分在32位机器上为32bit,在64位机器上为64bit,第二部分可有可无,储存类型指针,指向类元数据。另外对于Java数组,就还有第三部分用于记录数组长度。
对齐填充就是为了让对象大小变成8字节的整数倍

对象的访问定位

Java程序通过栈上的reference数据来操作堆上的具体对象,主流的对象访问方式有两种,第一种是句柄访问,Java堆会划分出一块内存用作句柄池,reference储存句柄地址,句柄储存对象实例和类型各自的具体地址;第二种是直接访问,这种情况Java对象的布局中就要考虑储存类型数据,reference就储存对象的直接地址。前者在垃圾收集时移动时快,后者访问速度快。