1. Java内存区的构成
Java虚拟机运行时内存主要由五个部分组成:
(1)方法区
方法区用于存储被虚拟机加载的类信息、常量、static变量等数据。字符串常量池就位于方法区。
(2)堆
堆用于存储对象实例,java中通new创建的对象实例就保存在堆中。
(3)虚拟机栈
虚拟机栈用于实现方法调用,每次方法调用就对应栈中的一个栈帧,栈帧包含局部变量表、操作数栈、方法接口等于方法相关的信息。
(4)本地方法栈
本地方法栈类似于虚拟机栈,只是保存的是本地方法(其他语言实现的系统方法)的调用。实际上在HotSpot虚拟机的实现中,虚拟机栈和本地方法栈被合并在一起。
(5)程序计数器
程序计数器的功能相当于PC寄存器的功能,如果当前执行的是Java方法,则指示当前字节码指令的地址,如果执行的是本地本地方法,则值为Undefined。
其中,方法区和堆是线程共享的内存,虚拟机栈、本地方法栈、程序计数器是线程独立的内存,每个线程只保存自己的数据。
2. 虚拟机异常
虚拟机抛出的异常都是Error类的对象,是我们所无法解决的错误。
程序计数器不会抛出Error;
虚拟机栈中方法调用的深度超过虚拟机规定的最大深度,则抛出StackOverflowError,虚拟机栈可以动态扩展,如果扩展时没有足够的内存则抛出OutOfMemoryError;
本地方法栈和虚拟机栈一样,会抛出StackOverflowError和OutOfMemoryError;
方法区和堆在无法满足分配需求时会抛出OutOfMemoryError;
3. 虚拟机中的对象
(1)对象的创建
当执行一条new指令时,首先将去方法区检查对应的类是否已经加载过了,如果没有,则先加载对应的类信息到方法区;
虚拟机在堆中分配一块内存来存储对象实例并初始化对象内存空间的值为0;
虚拟机设置对象头的内容,对象头中保存的是与对象相关的信息;
执行构造方法初始化对象;
(2)对象的存储
对象在虚拟机中分三部分存储:对象头、实例数据、对齐填充。
对象头包括两部分,第一部分存储对象相关的运行时信息,如:Hash Code、GC分代年龄、锁状态标志等;第二部分是类型指针,用于指示该对象所属的类型。
实例数据存储的是对象的有效数据,存储数据时遵循两个原则,首先遵循将相同长度的类型数据存在一起的原则,其次遵循先存储父类属性后存储子类属性的原则。
对齐填充没有存储有意义的内容,仅仅用作占位符,用于满足内存地址的需求。
(3)对象的访问
访问对象通过对象的引用,引用保存在栈中,而通过引用访问对象的实例以及类型有两种方式:
通过句柄,如果使用这种方式,则在Java堆中有一个句柄池保存各个对象实例的地址以及对象所属类型的地址,而对象的引用指向句柄池中句柄;
通过直接指针,如果使用这种方式,则在Java堆中存储对象实例数据时还要存储对象所属类型的地址;
这两种方式各有优势:
第一种方式在对象实例被移动时,只需要改变句柄中对象实例的地址,不需要修改对象引用;
第二种方式的优势在于访问速度更快,节省了一次通过指针定位对象实例的开销,HotSpot虚拟机使用的就是这种方式。
参考资料 《深入理解Java虚拟机》