《深入理解Java虚拟机》阅读笔记 第二章Java内存区域与内存溢出异常

点击查看《深入理解Java虚拟机》阅读梳理合集

程序计数器

程序计数器是运行时数据区中一块很小的内存空间。他可以看作当前线程所执行的字节码指令的行号指示器。字节码解释器工作时就是用过改变这个计数器的值来选取下一条需要执行的指令。程序计数器是每个线程私有的。如果正在执行java方法则计数器记录的是正在执行的字节码指令地址。如果是本地方法,则计数器为null。

java虚拟机栈

java虚拟机栈也是线程私有的。当java程序中有方法被执行时,就会创建一个栈帧存放在java虚拟机栈中。方法的执行与结束,对应着栈帧的入栈与出栈。栈帧中有局部变量表、操作数栈、动态链接等。

局部变量表

局部变量表的大小和深度在编译器就确定了。因此在创建虚拟机栈时,需要创建多大的空间已经确定。局部变量表中存放数据的空间叫做槽(slot),long double占用两个槽,其他的占用一个槽。这些槽是可以被重用的。
在HotSport虚拟机中java虚拟机栈不支持动态扩展,因此不会因为动态扩展而发生OutOfMemory但是可能会在一开始时分配空间失败而导致OutOfMemoryError。另一方面,如果java虚拟机栈中栈帧过多(满了),会发生StackOverflowError。

本地方法栈

本地方法栈与Java虚拟机栈类似,他俩的区别就是本地方法栈负责执行本地方法。

堆空间是进程间共享的一块区域。他通常是可扩展的(hotSport)。此内存区域存在的目的就是存放对象实例。几乎所有的对象都是在堆中存放的。java虚拟机规范中对堆的描述是:所有的对象实例以及数组都应当在堆上分配。 但是从目前的实现角度看,站上分配、标量替换优化手段使对象和数组不一定绝对分配在堆中。堆是垃圾回收的主要区域,堆(HotSport)中又分成了新生代与老年代(默认比例1:2)。新生代又分成了eden区 survivor0区和survivor1区(或者叫from survivor区 和to survivor区)(默认比例为8:1:1)。分区的目的是为了更好的完成垃圾回收。堆在物理内存中可以为不连续的,但是逻辑上应该是连续的。堆空间中每个进程也会拥有一块私有的空间(Thread Local Allocation Buffer,TLAB 在eden区)当java虚拟机无法继续扩展时将会发生OutOfMemoryError

方法区

方法区也是进程间共享的。方法区是用来存放被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。在hotsport中在JDK8以前采用“永久代”的实现方式。在JDK7时,将字符串常量池和静态变量部分从永久代中移除放到了堆中。在JDK8中直接把方法区删除了。取而代之的是本地内存中实现的元空间。将JDK7中永久代中剩余的内容放到了元空间。换掉永久代的原因是,永久代使用的是虚拟机内存,因此他的容量不能太大,很容易发生OOM。而元空间使用本地内存,因此理论上他的内存和计算机配置相关,可以很大。方法区可以进行垃圾回收也可以不进行垃圾回收。方法区垃圾回收是对常量池的回收和卸载类。

运行时常量池

运行时常量池是方法区的一部分。class文件中除了又类的版本、字段、方法、接口等描述信息外还有一项信息是常量池表,常量池表中用于存放编译期生成的字面量和符号引用。这部分内容将在类加载后存放到方法区的运行时常量池中。运行时常量池与class文件中的常量池的区别是,运行时常量池支持动态扩展。程序运行时也可以将新的常量放入运行时常量池(String 的 intern方法)。当无法进行扩展时时会发生OutOfMemoryError。

对象的内存布局

再HotSport虚拟机中对象在堆内存中存储布局可以划分为三个部分对象头、实例数据、对其填充。

对象头

对象头包括两类信息:
一类是用于存储对象自身的运行时数据(哈希码、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等)
另一类是类型指针,即对象指向他的类型元数据的指针,java虚拟机通过这个指针来确定该对象是哪个类的实例。
如果对象是数组的话,对象头中还会包括数组的长度

实例数据

实例数据部分是真正保存对象的有效信息,即我们在程序代码里面所定义的各种类型的字段内容(父类继承/子类定义)

对齐填充

堆中的自动内存管理系统需要每个对象的字段都是8的倍数。如果不够则需要填充

对象的定位访问

执行方法时java虚拟机栈的栈帧中的本地变量表中存放的只是对象的引用。如果需要使用对象,则需要通过对象引用定位到具体的对象。定位的方式有两种一种是使用句柄另一种是使用直接地址引用

句柄

如果使用句柄的话,需要在堆中开辟一个空间存放句柄(句柄池),句柄中存放了对象的实例地址以及类型地址。
优势:因为java对象在堆中的地址不是一成不变的,比如因为垃圾回收机制的存在,垃圾回收后,可能需要进行内存压缩。使用句柄的话就不需要更改本地变量表中的地址了。而只需要更改句柄池中相应句柄指向的地址。

直接指针(HotSport使用这种)

直接指针的方式是指,对象引用存放的直接就是对象在堆中的地址。但是需要在对象头中存放指向方法区中对象的类型信息的指针。
优点:速度更快,节约了一次指针定位的开销。

OutOfMemoryError异常

《Java虚拟机规范》的规定里,运行时数据区的各部分除了程序计数器都有发生OOM的可能

猜你喜欢

转载自blog.csdn.net/qq_30033509/article/details/110833962