一 | 运行时数据区域
1 | 堆
-
唯一目的:存储对象实例
-
运行时数据区最大的一块
-
《Java虚拟机规范》中,“所有对象和数组都应该在堆中分配”(逃逸分析,栈上分配,标量替换使之不绝对)
-
物理上可以不连续,逻辑上应该连续
-
可以固定大小,可以动态拓展,可能会
OutOfMemoryError
2 | 方法区
-
已加载的类型信息(JDK 8 中移到了本地内存的元空间中)、常量、静态变量、代码缓存
-
运行时常量池
-
存放编译期生成的字面量和符号引用
-
运行期也可以放入常量(
String类的intern()方法)
-
3 | 直接内存
-
不是运行时数据区一部分
-
受限于本机物理内存和寻址空间,可能会
OutOfMemoryError
4| 程序计数器
-
线程私有
-
控制程序执行流(顺序,分支,循环,跳转,异常处理,上下文切换时的恢复)
-
正在执行
Java方法:指向正在执行的虚拟机字节码指令的地址,当前程序所执行的字节码的行号指示器 -
正在执行
Native方法:为Undifined -
唯一一个不会
OutOfMemoryError的区域
5 | 虚拟机栈
-
线程私有,生命周期和线程相同
-
每执行一个
Java方法,都会创建一个栈帧:方法的调用和执行完毕对应了栈帧的入栈和出栈的过程 -
保存局部变量表,操作数栈,动态连接,方法出口等信息
- 局部变量表:由局部变量槽表示,编译器完成分配,运行时槽数量不变
-
线程所请求的深度大于虚拟机栈所允许的深度:
StackOverFlowError -
虚拟机栈动态扩展,当动态扩展申请不到内存时:
OutOfMemoryError
6 | 本地方法栈
-
线程私有,功能和虚拟机栈类似
-
为虚拟机用到的本地方法服务
-《Java虚拟机规范》对本地方法栈中用到的语言、使用方式与数据结构并没有任何强制规定
二 | HotSpot虚拟机对象
1 | 这里的对象
普通对象(不包括数组,Class对象)
2 | 遇到new指令
-
类加载检查
-
常量池检查是否有符号引用
-
该符号引用代表的类是否被加载、解析、初始化过
-
如没有,则执行类的加载过程
-
-
为新生对象分配空间
- 该空间大小在类加载完成后就可以确定
- 根据垃圾收集算法不同所导致的内存是否规整连续可以采用指针碰撞和空闲列表两种分配方式
- 为了解决并发有两种方式:CAS + 失败重试 / TLAB(Thread Local Allocated Buffer)
- 该空间大小在类加载完成后就可以确定
-
初始化零值
-
除了对象头
-
保证对象实例字段在 Java 代码中可以不赋初始值就直接使⽤
-
-
设置对象头
- 虚拟机要对对象进⾏必要的设置
- 这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的 GC 分代年龄等信息
-
执行
<init>方法- 构造函数的执行(Class文件中的
<init>方法)
- 构造函数的执行(Class文件中的
3 | 对象的内存布局
由三部分组成:对象头,实例数据,对齐填充
对象头
包含两类信息:Mark Word 和 类型指针
-
Mark Word
-
存储对象自身的运行时数据(哈希码,GC分代年龄,锁状态标志位,线程持有的锁,偏向线程ID,偏向时间戳)
-
动态的数据结构
-
-
类型指针
-
执行它的类型的元数据指针
-
虚拟机通过这个指针来确定是哪个类的实例
-
实例数据
-
真正存储对象的有效信息
-
顺序受
-XX:FieldsAllocationStyle参数和Java源代码的影响 -
默认是:相同长度的自动总是放到一起,父类在子类前
-
+XX:CompactFields参数默认为true,允许子类字段插入父类空隙中
对齐填充
-
不是必然存在
-
对象的起始地址是8字节的整数倍
4 | 对象的访问与定位
两种方式:句柄和直接指针
HotSpot使用的是直接指针
句柄
-
堆中有句柄池
-
访问实例数据需要两次引用
-
GC发生,对象的实例数据内存地址可能改变,只需要修改句柄中的实例数据指针
直接指针
-
访问实例数据需要一次引用
-
GC发生,对象的实例数据内存地址可能改变,需要修改
reference
三 | 实验
堆溢出
-
内存泄露(leak)
-
内存溢出(overflow)