一 Java内存区域
如图所示,java内存区域分为以下几块。
1、程序计数器
程序计数器是一块很小的内存空间,可以当作当前线程所执行字节码的行号指示器。
字节码解释器就是通过改变这个程序计数器的值来选取下一条需要执行的字节码指令。
线程私有,每个线程都有一个独立的程序计数器。
2、Java虚拟机栈
线程私有,生命周期与线程相同
虚拟机栈描述的是Java方法执行的内存模型:每个方法执行时都会创建一个帧栈,用于存储局部变量表、操作树栈、动态连接、方法出口等信息。
局部变量表存放的是编译器可知的各种数据类型(boolean,byte,char,short,int,float,long,double)、对象引用类型。其中64位长度的long,float类型占用2个局部变量空间。
3、本地方法栈
线程私有,生命周期与线程相同
与虚拟机栈类似,虚拟机栈为Java方法服务,而本地方法栈为Native方法服务。
HotSpot虚拟机将虚拟机栈和本地方法栈合并。
4、Java堆
也称GC堆,是Java虚拟机管理内存最大的一块。
线程共享区域,虚拟机启动时创建
该内存空间唯一的作用就是存放对象实例
Java堆分为:新生代、老年代;再分可以分为Eden空间、From Survivor空间、To Survivor空间。
具体分配如图
线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(TLAB)。
5、方法区
线程共享区域
用于存储虚拟机加载的类信息、常量、静态变量、编译后的代码。
由于HotSpot将GC分代扩展到了方法区,称为永久代,实际上是永久带包括了方法区。
HotSpot垃圾回收器会回收方法区中的内存。
6、运行时常量池
属于方法区
存储编译期生成的字面量和符号引用。
运行期间也可将常量存放到常量池。
7、直接内存
不是虚拟机运行时的一部分,也不是Java虚拟机规范中的内存区域。
收到本机硬件限制。
二、对象的创建
1、虚拟机收到new指令,首先检查常量池中是否有这个类的符号引用,并且检查这个符号引用是否被加载、解析和初始化过。有则直接返回。
2、如果上一步不通过,则进行类的加载
3、虚拟机分配内存空间,也就是将Java堆中的一块区域划分出来存储对象实例。
4、将分配的内存初始化为零值
5、虚拟机对该对象进行设置,如属于哪一个类的实例,如何找到类的元数据信息,对象的哈希值,对象的分代年龄等信息,并将这些信息存放在对象头中。
6、执行init()方法,对象创建完成。
三、对象的内存布局
分为三块,对象头、实例数据、对齐填充
1、对象头
分为两部分
1)对象自身运行时数据(Mark Word)
主要包括哈希值hashcode, GC分代年龄, 锁状态标志 ,线程持有的锁, 偏向线程的ID, 偏向时间戳
2)类型指针
虚拟机通过该类型指针确定该对象属于哪一个类。
2、实例数据
存储对象的有效信息,也就是程序中定义的各种类型的字段内容。
父类和子类的都会记录
存储顺序收到虚拟机分配策略参数影响和源代码中定义顺序影响。
相同宽度的字段被分配到一起,如long/double, ints, shorts/chars,bytes/booleans,oops。
父类中定义的变量出现在子类之前。
3、填充对齐
无实际意义,起到占位符的作用。
四、对象的访问定位
有两种方式
1、使用句柄
1)使用句柄访问,则栈中reference中存放的就是对象的句柄地址
2)java堆中划分出一块区域作为句柄池
3)句柄中需要存放对象的实例地址和对象类型数据地址
4)优点:reference中存放的是稳定的地址,在对象被移动,GC时时常会发生,这个时候只会改变句柄中存放的实例地址,而reference中存放的数据不变。
2、使用指针访问
1)java栈中reference直接存放对象的实例地址
2)java堆中实例对象的对象头的类型指针可以获取到对象的类型数据
3)优点:减少了一次指针定位,效率高,速度快