浅谈Java内存区域

一、运行时数据区域

Java在运行时,会根据需要,将内存区按照如下区域划分,分为多个部分:


其中,蓝色为线程共享的内存区域,橙色为线程独享的内存区域。

1.1、方法区

保存了虚拟机加载的类信息、常量、静态变量等数据

1.2、常量区

是方法区的一部分,常用于存放编译期生成的各种字面量和符合引用。但是常量池具有动态性,并不要求常量一定是在编译期才能产生,也就是并发预置入Class文件中的常量池的内容才能放入常量区,运行期间也可能将新的常量放入其中,比如Sring类的intern方法

1.3、堆

这也算程序员们常提到的堆内存(相对于栈内存),此内存占用空间一般是最大的,该区域的唯一用途就是存放对象实例,堆是垃圾收集器管理的主要区域。从内存回收的角度,可以再细分为Eden区、From Survivor区、To Survivor区等;从内存分配的角度看,可以分出各个线程私有的分配缓冲区(TLAB)。需要注意的是,堆是可以处于物理上不连续,但是逻辑上连续的空间中。

1.4、程序计数器

这是比较小的一个内存空间,每个线程独有。它记录了每个线程所执行的字节码的行号指示器,分支、循环等都需要此计数器来完成。

1.5、虚拟机栈

线程独有。该区域也就是程序员们常提到的栈内存(相对于堆内存)。每次调用方法,都会在此区域创建栈帧,栈帧包括方法的局部变量、操作数栈、动态链接、方法出口等信息。如果方法内调用方法,就会加深栈帧的深度,当达虚拟机所允许的最大深度时,会抛出StackOverFlowError异常。

1.6、本地方法栈

线程独有。与虚拟机栈的作用非常相似,区别只是在于是虚拟机栈为Java方法服务,本地方法栈为Native (比如com.mics.Unsafe类的compareAndSwap方法)方法服务。

1.7、直接内存

直接内存不是Java虚拟机规范中定义的内存区域。JDK1.4中引入了NIO类,引入了一种基于通道和缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。

二、对象的内存布局

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

2.1、对象头

对象头包含两部分信息:对象运行时数据、类型指针

2.1.1、对象自身的运行时数据,比如哈希码、GC分代年龄、锁状态标识,线程持有的锁、偏向锁的线程ID、偏向时间戳等,也称之为“Mark Word”。在32位的HotSpot虚拟机中,如果对象处于未锁定状态,Mark Word的32位空间中,25位用于哈希码,4位GC分代年龄,2位锁标志位,1位固定为0。其他状态下则会进行扩展,本身Mark Word也是非固定的数据结构。

2.1.2、类型指针:通过该指针可以确定对象是哪个类的实例。然而并不是所有虚拟机都会有类型指针,比如使用句柄访问的虚拟机就不需要。

2.2、实例数据

类中定义的各种类型的字段内容。HotSpot中,默认同样宽度的字段会分配到一起,在此前提下,父类的字段会在子类之前。

2.3、对齐填充

并非必然存在,只是起占位的作用。HotSpot虚拟机的内存管理要求对象的起始地址是8字节的整数倍,所以当实例数据不符没有对齐时,就需要通过对齐填充来补全。

三、对象的访问定位

对象的访问定位是指栈上面的reference数据来操作堆上面的具体对象。目前主流的访问方式有使用句柄和直接指针两种

3.1、使用句柄

Java堆中会划分出一块内存来作为句柄池,reference存储的是句柄池的地址。句柄池中则包含了对象实例数据和类型数据的具体地址(可以通过此地址确定该对象的类型)

使用句柄的好处是稳定的句柄地址,当垃圾收集时,对象会被移动,此时只需要修改句柄池中的地址即可。

3.2、直接指针

reference中存储的是对象在堆中的地址。最大好处就是速度快,直接节省了一次指针定位时间开销。HotSpot是采用该方式进行对象访问的。

四、溢出测试

待补充。


注:借鉴《深入理解Java虚拟机》

猜你喜欢

转载自blog.csdn.net/vincentchen1990/article/details/79779303