JVM内存管理——总结篇
内存划分及作用
程序计数器
- 线程私有、字节码行号指示器。
- 执行Java方法,计数器记录的是字节码指令地址;执行本地(Native)方法时,为空。
本地方法栈
与虚拟机栈类似,为Native方法服务Java虚拟机栈
- 每个方法执行对应一个栈帧,存储局部变量表、操作数栈、动态连接、方法出口等信息
- 局部变量表:存放编译期可知的基本数据类型、对象引用、返回值地址
- 局部变量表以局部变量槽为单位,long和double占两个槽位,其余一个
- 栈帧中的内存大小在编译期间已经确定
- 线程请求的内存大于虚拟机允许的深度,报错stackoverflowerror;栈拓展时无法申请足够内存,报错OutOfMemoryError
Java堆
方法区
存储类型信息、常量、静态变量、代码缓存等运行时常量池
编译期生成的字面量和符号引用
直接内存
Java堆中的DirectByteBuffer对象对这块内存直接操作,避免数据在Native和Java堆中来回复制。
常见问题
普通对象的创建过程
- 检测类是否已被加载
当虚拟机遇到 new 指令时,首先先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,就执行类加载过程。 - 为对象分配内存
类加载完成以后,虚拟机就开始为对象分配内存,此时所需内存的大小就已经确定了 - 为分配空间初始化零值
保证对象没有赋初始值也可以使用 - 其他设置
设置对象头信息,如所属类、hashcode、gc分代年龄 - 执行init方法
按程序代码分配初始值
- 检测类是否已被加载
Java堆为实例分配内存的方式
连续空间
使用指针碰撞方式,移动被占内存和可用空间的指针来分配。多线程发生内存冲突时,利用CAS加失败重试保证分配;或者本地线程分配缓存(TLAB)方式分配内存非连续空间
维护一张列表,记录可用空间,分配内存更新列表
对象内存布局
- 对象头
第一部分“Mark Word”:运行时数据,哈希码、GC分代年龄、锁状态
第二部分:类型指针,指向类的元数据; - 实例数据
- 对其填充(因为对象起始地址必须是8字节的整倍数)
- 对象头
对象的两种访问定位
- 句柄访问
堆中划分句柄池,句柄中包含对象实例数据和类型数据各自具体地址。优点:对象被移动时,只需改变句柄中实例数据指针 - 直接访问
直接访问对象地址。优点:少了一次开销,访问速度更快
- 句柄访问