深入理解Java虚拟机 第二版读书随笔(二)Java内存区域与内存溢出异常


本篇为第二章前半部分随记

1.程序计数器

程序计数器可理解为当前线程所执行的字节码的行号指示器。分支,循环,异常,线程恢复等基础功能都需要依赖修改计数器的值来完成。
对于一个处理器内核来说,java多线程就是多条线程轮流切换分配处理器时间来完成的,每个线程里都需要跳转到正确的下一个字节码指令,因此程序计数器是线程隔离的,每个线程维护自己的计数器。
对于程序计数器的取值,在执行的方法是java方法时,值为对应字节码指令地址;如果执行的是native方法,值为空、
此内存区域是唯一一个在JVM规范中没有规定OOM情况的区域

2.Java虚拟机栈

Java Virtu Machine Stacks也是线程私有的,生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧 用于存储 局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
局部变量表存放了三种数据:
1.基本数据类型(8种)
2.对象引用 (reference类型,eg. 指向对象起始地址的引用指针)
3.returnAddress类型(指向一条字节码指令的地址)
64位长度的 long和double类型会占用两个局部变量空间(Slot),其余的数据类型只会占用一个。局部变量表所需的内存空间在编译期间完成分配,当进入一个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。
虚拟机栈会发生的两种异常情况:
1.线程请求的栈深度大于虚拟机所允许的深度,抛 StackOverflowError
2.如果虚拟机栈可以扩展,且扩展时无法申请到足够内存时,抛 OutOfMemoryError

3.本地方法栈

Native Method Stack与虚拟机栈的作用是相似的,区别就是 虚拟机栈执行Java方法(字节码)服务,本地方法栈是虚拟机用到的Native方法服务。
与虚拟机栈一样,本地方法栈区域也会抛出StackOverflow和OOM

4.Java堆

Java Heap是JVM所管理内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例。几乎所有对象实例都在这里分配内存(随着JIT编译器的发展与逃逸分析技术逐渐成熟,栈上分配、标量替换优化技术将会导致一些微妙的变化法伤,所有的对象都分配在堆上也渐渐变得不是那么绝对了)ps括号内没看懂
Java堆时垃圾收集器管理的主要区域。从内存回收角度来看,现在收集器基本采用分代收集算法,可分为新生代和老年代;从内存分配角度看,线程共享的java堆中可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer)
Java堆可以处于物理不连续的内存空间中,只要逻辑连续即可,因此在实现时可以实现成固定大小/可扩展两种(通过-Xmx -Xms控制)。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,会抛出OOM异常

5.方法区

Method Area是被所有线程共享的一块内存区域,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。Java虚拟机规范堆方法区的限制非常宽松,方法区不需要连续的物理内存,可以选择固定大小或可扩展,可以选择不实现垃圾收集。方法区相对来说GC行为比较少,这区域的内存回收目标主要是针对常量池的回收和对类型的卸载。一般来说这个区域的回收效果较差。当方法区无法满足内存分配需求时,抛出OOM异常。