【JVM】内存区域 & 对象创建定位
运行时数据区域
Java虚拟机在执行Java程序中把内存划分为若干个不同的数据区域
不同的区域有各自的用途,创建时间和销毁时间
方法区
- 用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码
- 各个线程共享区域
- 别名:非堆、永久代
去永久代原因
- 字符串在永久代中,容易出现性能问题和内存溢出
- 类和方法的信息难以确定大小,太小导致永久代溢出,太大导致老年代溢出
- 永久代为GC带来复杂度,使回收效率变低
去永久代过程
- JDK1.7将常量、静态变量放入堆中
- JDK1.8将类信息、编译代码放入元空间中
堆
- 用于存放对象实例和数组,在JVM启动时创建
- JVM管理内存中最大的一块
- 所有线程共享的内存区域
- 从GC角度来看,还可以细分为新生代(Eden、FromSurivivor、ToSurvivor)、老年代
程序计数器
- 通过改变计数器的值选取下一条需要执行的字节码指令
- 一块较小的内存空间,不会发生OutOfMemoryError的区域
- 线程私有内存,各线程计数器互不影响
虚拟机栈
- 用于方法执行时创建栈帧,存储局部变量表、操作数栈、动态链接、方法出口
- 线程私有,生命周期和线程相同
- 线程请求栈深度大于允许深度会抛出StackOverflowError,动态扩展无法申请到足够内存会抛出OutOfMemoryError
本地方法栈
- 和虚拟机栈不同点:本地方法栈执行本地方法,虚拟机栈执行Java方法
- 和虚拟机栈相同点:抛出StackOverflowError和OutOfMemoryError
对象创建过程
- 检查new指令的参数是否能在常量池中定位到一个类的符号引用
- 检查这个符号引用代表的类是否已被加载、解析和初始化,若没有则执行类加载
- 为新生对象分配内存(采用CAS+失败重试保证多对象同时分配的原子性;或使用TLAB)
- 把分配的内存空间都初始化为零值(保证了对象的实例字段没有初始值就可以直接使用)
- 对对象进行必要设置,放在对象头中(类信息、元数据信息、哈希码、GC年龄)
- JVM工作完成,然后按照程序员意愿进行初始化
对象内存布局
对象头:
- 对象自身运行时数据:HashCode、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID
- 类型指针:对象指向它的类元数据的指针
- 数组长度数据:如果对象是Java数组,它的对象大小从元数据中无法确定
实例数据:代码中定义的各种类型的字段内容
对象访问定位
通过栈上的reference数据操作堆上的具体对象,Sun HotSpot使用的是直接指针访问
句柄访问
- reference指向堆中划分的句柄池
- 句柄池存放指向堆里对象实例数据的指针
- 句柄池存放指向方法区里对象类型数据的指针
直接指针访问
- reference指向堆中对象实例地址
- 对象实例中存放指向方法区的对象类型数据的指针