深入理解Java虚拟机学习日记
第二章:Java内存区域和内存溢出异常
- 程序计数器(PCR):当前线程所执行的字节码行号计数器,占用内存较小。每一个线程(顺序执行单元)都有自己的PCR,如果线程执行的是Java代码,则PCR记录的是正在执行的虚拟机字节码指令的地址;如果执行的是native方法,这个计数器的值为undefined。此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryErroe情况的区域。
- Java虚拟机栈:线程私有,生命周期与线程相同。方法的执行与执行完毕都在虚拟机栈中。
2.1 栈帧:而每个方法的执行都会创建一个栈帧,伴随着方法从创建到执行完毕。栈帧用于局部变量表、操作数栈、动态链接、方法出口等。当虚拟机栈中村放不下栈帧时,会出现stackOverFlowError,递归时出现的概率大。
2.2 局部变量表:内存空间在编译期完成了分配,之后不会改变大小。存放了编译期克制的各种基本数据类型(如:boolean、byte、char、short等)、对象引用(如:reference类型)和returnAddress类型(指向了一条字节码指令的地址)。其中64位长度的long和double占用两个局部变量空间,其他一个。 - 本地方法栈:与虚拟机栈类似,但为native方法服务。
- Java堆:线程共享区域,存放对象实例并分配内存(几乎所有对象都在此分配内存),是GC管理的主要区域。是Java虚拟机管理最大的一块区域。
4.1 Eden空间:
4.2 From Survivor空间
4.3 To Survivor空间 - 方法区:线程共享区域,存储虚拟机加载的类信息、敞亮、静态常量、即时编译器编译后的代码等数据。
5.1 类信息:类的版本、字段、方法、接口等
5.2 运行时常量池:存放编译器生成的自变量以及符号引用,这部分内容将在类加载后进入到方法区的常量池中引用。
如String str1;(对象引用)
String str2 = “abc”;(对象实例化)
str1 = new String();(对象)
------------------------------------------------
String str1 = new String(“abc”);(两个对象,引用对象str1和实例对象new String(“abc”))
String str1 = “abc”;
String str2 = “abc”;
由于实例都存放在常量池中,先从常量池中查找有无abc内容,若无则创建,有则直接引用,所以str1==str2.
而new String()则是每次都在堆中创建新的对象。 - 对象:创建对象之前需要先加载、解析和初始化类,如果在常量池中没有找到一个类的符号引用,则说明该类还没有被加载,那么则进行累的加载、解析和初始化。
6.1 给对象分配内存(由于GC导致不同情况)
## 6.1.1 指针碰撞(当空闲内存比较有规律、连续时)
## 6.1.2 空闲列表(Java堆不规整时,即空内存多块分布)
虚拟机维护一个表,记录上哪些内存块可用,在分配是从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录。
6.2 线程安全性问题:多线程要求同一块空内存。当正在给对象A分配内存是,指针还没来得及修改,对象B又用同一个指针来分配内存。
## 6.2.1 线程同步:对分配内存空间的动作进行同步处理。
## 6.2.2 本地线程分配缓冲(TLAB):每个线程在Java堆中预先分配一小块内存,哪个线程要分配内存,则在哪个线程的(TLAB)上分配。
当内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),此时创建对象才完成。
-
对象的内存布局
在hotspot虚拟机中分为三个区域:对象头、实例数据和对齐填充(非必要)对象头分为两部分,第一部分是用于存储对象自身的运行时数据,如哈希吗、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等。长度则根据虚拟机32位或64位分别为32bit和64bit。
第二部分则是类型指针,即对象指向他的类元数据的指针,虚拟机通过这个指针确定这个对象是哪个类的实例。
-
对象的访问定位
8.1 句柄
在java堆中划分出一块内存用作句柄池。
优点:reference中存储的是稳定的句柄地址,在对象被移动时只会改变句柄中的实例数据指针,而reference本身不需要修改。
8.2 直接指针
优点:速度更快,节省了一次指针定位的时间开销。