其中,其中Method Area 和 Heap 是线程共享的 ,VM Stack,Native Method Stack 和Program Counter Register 是非线程共享的。JVM初始运行的时候都会分配好 Method Area(方法区) 和Heap(堆) ,而JVM 每遇到一个线程,就为其分配一个 Program Counter Register(程序计数器) , VM Stack(虚拟机栈)和Native Method Stack (本地方法栈), 当线程终止时,三者(虚拟机栈,本地方法栈和程序计数器)所占用的内存空间也会被释放掉。
程序计数器是一块较小的内存空间,可以看作当前线程所执行的字节码的行号指示器。为了线程切换后能够恢复到正确的位置。 如果线程执行java方法,计数器记录正在执行的虚拟机字节码指令的地址,如果是native方法,值为空。 简单地讲,一个Native Method就是一个java调用非java代码的接口。
描述的是一个java方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧,用于存储局部变量,操作数栈,动态链接,方法出口等信息。 这个区域有两种异常情况:线程请求的栈深度>JVM允许的深度,导致stackoverflow错误; 扩展时无法获取足够的内存,导致OutOfMemoryError错误。
与java虚拟机栈类似,不同的是存储的是本地方法。
线程共享的内存区域,在虚拟机启动时创建,用来存放对象实例。 java堆是垃圾收集器管理的主要区域,由于垃圾收集器都采用分代收集算法,一般分为新生代与老年代,再细致一点分为Eden空间,From Survivor空间,To Survivor空间。 java堆可以处于物理上不连续但逻辑上连续的空间。
线程共享的内存区域,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。又被称为“永久代”,因为GC分代扩展到了方法区,或者说使用永久代来实现方法区而已。 和java堆一样,只需要逻辑上连续的空间。还可以选择不实现垃圾收集,因为这个区域的内存回收目标主要针对常量池的回收以及类型的卸载,但是类型的卸载条件相当严格,所以回收效率不高。
方法区的一部分,存放编译器生成的各种字面量和符号引用。JVM规范并未对这部分做严格要求,所以提供商可以按照自己的要求实现这部分。运行时常量池具有动态性,运行期的常量也可以放入池中,如String类的intern()方法。
这部分并不是虚拟机运行数据区的一部分,也不是JVM规范中定义的内存区域。虽然本机直接内存的分配不会受到java堆的影响,但是还会受到本机总内存以及处理器寻址空间的限制。
指针碰撞:假设java堆中内存是绝对规整的,中间放着一个指针作为分界点的指示器,分配内存只需把指针向空闲空间那边移动一段与对象大小相等的距离。 空闲列表:内存不规整,虚拟机必须维护一个表,记录哪些内存块是可用的,从中找到一块足够大的内存分配给实例。 对象创建时的冲突:创建对象是一个非常频繁的行为,并发情况下修改指针位置并不是线程安全的,可能出现给A对象分配内存是指针还未来得及修改B对象就是用这个指针进行下一步操作。有两种解决方案:
对象中的内存布局可以分为三个区域:对象头(Header),实例数据(Instance Data),对齐填充(Padding)。
对象头包括两部分信息,一部分用于存储对象自身的运行时数据,如哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等,被称为“Mark Word”。在32位与64位的虚拟机中分别位32bit以及64bit。在32bit状态下,25bit用于存储对象哈希码,4bit用于存储对象分代年龄,2bit用于存储锁标志位,1bit固定为0,其他情况下存储状况如下:
存储内容 | 标志位 | 状态 |
---|---|---|
对象的哈希码,分代年龄 | 01 | 未锁定 |
指向锁记录的指针 | 00 | 轻量级锁定 |
指向重量级锁的指针 | 10 | 膨胀(重量级锁定) |
空,不需要记录信息 | 11 | GC标记 |
偏向线程ID,偏向时间戳,对象分代年龄 | 01 | 可偏向 |
对象头的另一部分是类型指针,即对象指向它的类元数据的指针,虚拟机通过这个指针判断这个对象是哪一个类的实例。并不是所有虚拟机都必须这一部分。
对象真正存储的有效信息,即程序代码中定义的各种类型的字段内容,无论是父类继承的还是子类定义的都需要记录。存储顺序受虚拟机分配参数以及字段在java源码中的定义顺序的影响。
不是必须存在的,因为虚拟机的内存管理系统要求对象的起始地址必须是8字节的整数倍,即对象的大小必须是8字节的整数倍,当对象实例数据部分没有对齐时,需要通过对齐填充来补全。
java程序通过栈上的reference数据来操作堆上具体的对象,但是虚拟机并没有定义该通过何种方式区定位,访问堆中的对象的具体位置。目前的主流访问方式有使用句柄和直接访问两种。
句柄访问的优势:reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,reference本身不需要修改。 直接指针访问的优点:速度快,节省了一次指针定位的时间开销。