深入学习Java虚拟机学习笔记-内存区域与内存溢出异常

1. Java运行时数据区域

深入学习Java虚拟机学习笔记-内存区域与内存溢出异常

1.1 程序计数器。字节码解释器工作就是通过改变这个计数器的值来选择下一条需要执行的字节码指令。

    为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器。

    如果当前执行Java代码,程序计数器记录的是政治执行的虚拟机字节码指令的地址。如果是Native方法,这个计数器的值为空。

1.2 Java虚拟机栈。也是线程私有的,它的声明周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等。

    Java虚拟机规范中,这个区域规定了两种异常状况:线程请求的栈深度大于虚拟机所允许的深度,抛出StackOverflowError异常;虚拟机栈可以动态扩展,当扩展时无法申请到足够的内存,抛出OutOfMemoryError异常。

1.3 本地方法栈。为虚拟机使用到的Native方法服务。HotSpot直接把虚拟机栈和本地方法栈合并。也会抛出虚拟机栈中的两种异常。

1.4 Java堆。被所有线程共享,在虚拟机启动时创建,用于存放对象实例。这里是垃圾收集管理器的主要区域。由于垃圾收集器基本算法都采用分代收集算法,所以java堆可细分为:新生代和老年代。大小可以通过Xmx/Xms来配置。内存不够时会抛出OutOfMemoryError异常。

1.5 方法区。被所有线程共享,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据,有个别名:Non-Heap。因为HotSpot把GC分代收集扩展到方法区,因此方法区也叫永久代。可以使用XX:MaxPermSize设置上限。JDK1.7以后,永久区的字符串常量池被移到堆中。内存不够时会抛出OutOfMemoryError异常。

1.6 运行时常量池。是方法区的一部分。相对于Class文件常量池,运行时常量池的另一个重要特征是“具备动态性”:java语言并不要求常量一定只有编译期才能产生,运行期间也可以将新的常量放入池中,例如String类的intern方法。

1.7 直接内存,并不是xu你几运行时数据区的一部分,也不是虚拟机规范中定义的内存区域。在jdk1.4中加入NIO(new input/output)类,它可以使用native函数库直接分配堆外内存,然后通过一个存储在java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。这样能显著提高性能,因为避免了在java堆和native堆中来回复制数据。显然,直接内存不受java堆大小限制,但因为受操作系统内存的限制,所以也会爆出OutOfMemoryError异常。

2. 对象的创建流程

2.1 虚拟机遇到new指令,检查参数是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已被加载、解析和初始化过。

2.2 如果没有,必须先执行类加载过程。

2.3 分配内存,有两种方式,java堆中内存规整,划一段完整内存给对象;加入内存不规整,被划分为一块一块地,空闲的内存块组成空闲列表,那么虚拟机从空闲列表中选择数量合适的块给对象。为了保证修改指针的动作具有线程安全性,有两种方法,第一种是采用CAS配上失败重试,以保证更新指针的操作具有原子性;另一种方式是使用TLAB(thread local allocation buffer),即为每个线程分配一个缓冲区,各个线程分配内存使用各自的TLAB。

2.4 格式化对象,内存空间初始化0

2.5 对对象进行必要的设置,例如对象是哪个类的实例,如何才能找到类的元数据信息,对象的哈希吗,对象的gc分代年龄等。这些都分布在对象的header中

2.6 执行<init>方法,按照程序员的意愿进行初始化。

3. Java的内存布局

3.1 header。第一部分Mark Word:类的元数据信息,对象的哈希吗,对象的gc分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等。第二部分是类型指针,即对象执行它的类元数据的指针,虚拟机通过这个指针,可以知道对象是哪个类的实例,但不是每个对象都是必须要这个指针的。如果对象是java数组,对象头中还必须有一块用于记录数组长度的数据。

3.2 数据区(Instance Data)

3.3 对齐填充(Padding),对象的大小必须是8字节的整数倍,空下来的部分通过对齐填充来补全。

4. 对象的定位

4.1 使用句柄。java堆中划分一块内存作为句柄池,用于存储对象的句柄地址,句柄包含了对象实例数据与类型数据各自的具体地址。好处就是reference中存储的是稳定的句柄,对象移动时只会改变句柄中的实例数据指针。

4.2 直接指针。好处是快,节约了一次指针定位的时间。