《深入java虚拟机》读后(1)
第二章 java内存区域与内存溢出异常
一、运行时数据区域
1. 程序计数器:当前线程所执行的字节码的行号指示器。(线程私有)
2. java虚拟机栈:描述java方法执行的内存模型。(线程私有)
每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中入栈和出栈的过程。
3. 本地方法栈:描述虚拟机使用到的native方法的内存模型。(线程私有)
4. java堆:存放对象实例。垃圾收集器管理的主要区域。
5. 方法区:存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等。
运行时常量池是方法区的一部分。用于存放编译时期产生的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池存放。(具备动态性)
6. 直接内存:本机直接内存。
二、 HotSpot虚拟机中对象
1. 对象的创建
1)虚拟机遇到一条new指令。
2)检查这个指令的参数能否在常量池中定位到类的符号引用,并检查这个符号引用代表的类是否被加载、解析和初始化过。
3)给对象分配内存。(在类加载完成后已确定所需内存大小)
4)虚拟机将分配到的内存空间初始化为零值。(确保对象的实例字段在java代码中可以不赋初始值就直接使用)
5)设置对象(对象头)
6)执行<init>方法
注:两种方案解决分配空间的线程安全问题:
(1)分配内存空间的动作进行同步处理——虚拟机采用CAS配上失败重试的方式保证更新操作的原子性。
(2) 每个线程在java堆(Eden空间)中预先分配一块内存——本地线程分配缓冲(TLAB),哪个线程需要分配内存就在哪个TLAB分配,只有TLAB用完了需要分配新的TLAB时才需要同步锁定。同时虚拟机初始化内存空间这一操作可以提前至TLAB分配时进行。
2. 对象的内存布局
1)对象头
第一部分用于存储对象自身的运行时数据(Mark Word),极小存储空间。
第二部分是类型指针,指向他的类元数据,用来确定他是哪个类的实例(非必需保留类型指针)。
另外,如果对象是数组,对象头中必须有一块记录数组长度的数据,因为虚拟机不知道分配多少内存。
2)实例数据:对象真正存储的有效信息,也是程序代码中所定义的各种类型的字段内容。
3)对齐填充:补全对象大小至8字节的倍数。
3. 对象的访问定位(两种访问方式)
第一种:句柄访问。在java堆中划分一块内存作为句柄池,reference中储存句柄池地址,句柄中包含了对象实例数据(java堆)和类型数据(方法区)的地址。
优势:在对象被移动(gc)时只会改变句柄中的实例数据指针。
第二种:直接指针访问。reference中存储对象的地址。
优势:快,节约了一次指针定位的时间。