-
Java内存区域
JAVA内存区域划分
#程序计数器
程序计数器是一块较小的内存空间,字节码解释器工作时,通过改变该计数器的值来选择下一条需要执行的字节码指令,如果正在执行的是JAVA方法,计数器记录的是正在执行的虚拟机字节码指令的地址,如果执行的是native方法,则计数器值为空。程序计数器是线程私有内存区域。
#虚拟机栈
虚拟机栈也是线程私有的,每个方法被执行时,虚拟机会创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。
-
局部变量表
局部变量表中存放着各种基本类型、对象引用、returnAddress类型。
#本地方法栈
区别于 Java 虚拟机栈的是,Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。也会有 StackOverflowError 和 OutOfMemoryError 异常。在HotSopt虚拟机中将本地方法栈和Java栈合二为一。
#JAVA堆
Java虚拟机管理的最大的一块内存区域,几乎所有的JAVA对象的实例都存放在堆中,堆是垃圾收集器管理的区域。Java堆可以是物理上不连续的区域。堆是所有线程共享的内存区域。
#方法区
方法区也是所有线程共享的内存区域,用于存放已经被虚拟机加载的类型信息,常量,静态变量,即时编译器编译后的代码缓存。
-
运行时常量池
运行时常量池保存着Class文件中常量池表的各种字面量与符号引用,和运行时间所产生的新变量。
#直接内存
使用NIO的时候,使用Native函数库直接分配的堆外内存。
Java堆上对象的创建、布局和访问的过程
#对象的创建
当碰上字节码指令new时
1. 检查能否在常量池中找到这个类的符号引用,并检查该类是否被加载,解析,初始化过。
2. 如果没有,进入类加载阶段
3. 类加载检查通过后,为新生对象分配内存
- 指针碰撞分配方式:将堆内存分成一块已经分配过的和一块未被分配过的,中间使用指针来表明分界线,为新生对象分配内存后,将指针向空闲空间挪动。
- 空闲列表分配方式:堆内存的分配方式并非连续,虚拟机维护一个列表,表面哪些内存空间是可用的,大小是多少。
- 维护线程安全:分配内存空间不是线程安全的,假设要给对象A分配一块内存空间,还未移动指针,对象B的分配使用了原来的指针。虚拟机一种方法是使用了CAS加上失败重试的方式保证线程安全,另一种方法是将Java堆给各个线程预先分配一块区域,称为本地线程分配缓冲,分配对象首先在本地缓冲区中分配。
4. 将分配到的空间不包括对象头都初始化为0
5. 对对象头进行必要的设置
6. 执行构造方法
#对象的内存布局
对象的内存布局可以划分为三部分:对象头、实例数据和对齐填充。
- 对象头:包含两类信息,一类是对象运行时的数据,例如哈希码,分代年龄,锁状态等;另一类是指向方法区的类型指针,通过该指针来确定该对象是哪个类的实例。
- 实例数据:用来存放各种类型的字段内容,包括从父类继承的。
- 对齐填充:HotSpot虚拟机的自动内存管理系统要求对象的起始地址必须是8字节的整数倍,所以要将不满8字节整数倍的对象补齐。
#对象的访问
对象的访问方式主要有通过句柄和直接指针访问两种。
- 通过句柄访问对象:Java栈中的引用类型指向Java堆中的句柄池中的相应句柄地址,句柄中包括到对象实例数据的指针和到对象类型数据的指针,前者指向Java堆中的实例池中,后者指向方法区。
- 通过直接指针访问对象:Java栈中的引用类型指向Java堆中的对象地址,而对象头中保存着到对象类型数据的指针。