JVM
文章内容为阅读深入理解Java虚拟机后做的记录和总结,其中部分内容参考了一些博客如cyc大神的GitHub指南
运行时数据区域
程序计数器(Program Counter Register)
是一块较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器,如果当前线程执行的是Java方法,则这个技术器记录的是当前正在执行的虚拟机字节码指令的地址,如果正在执行的是本地方法,这个计数器值应为空
Java虚拟机栈
线程私有的,其生命周期与线程相同
每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧,用来存储局部变量表、操作数栈、动态连接、方法出口等信息。从方法调用直至执行完成的过程,对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
该区域可能抛出以下异常:
- 当线程请求的栈深度超过最大值,会抛出 StackOverflowError 异常;
- 栈进行动态扩展时如果无法申请到足够内存,会抛出 OutOfMemoryError 异常。
本地方法栈
本地方法栈与 Java 虚拟机栈类似,它们之间的区别只不过是本地方法栈为本地方法服务。
本地方法一般是用其它语言(C、C++ 或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序,对待这些方法需要特别处理。
Java堆
Java堆是虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的是存放对象实例。所有的对象实例都在这里分配内存。
Java堆是内存管理的是收集区域(GC堆)。现代垃圾收集器大部分都是基于分代理论设计的,其主要的思想是针对不同类型的对象采取不同的垃圾回收算法。可以将堆分成两块:
- 新生代(Young Generation):包括1个Eden和2个Survivor
- 老年代(Old Generation)
Java堆可以处于物理上不连续的内存空间中,但在逻辑上应该被视为连续的。Java堆是可扩展的,如果在Java堆中没有内存完成实例分配,并且堆也无法再扩展时,Java虚拟机将会抛出OutOfMemoryError(OOM)异常
方法区
同Java堆一样,是各个线程共享的内存区域。
用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
和堆一样不需要连续的内存,并且可以动态扩展,动态扩展失败一样会抛出 OutOfMemoryError 异常。
对这块区域进行垃圾回收的主要目标是对常量池的回收和对类的卸载,但是一般比较难实现。
HotSpot 虚拟机把它当成永久代来进行垃圾回收。但很难确定永久代的大小,因为它受到很多因素影响,并且每次 Full GC 之后永久代的大小都会改变,所以经常会抛出 OutOfMemoryError 异常。为了更容易管理方法区,从 JDK 1.8 开始,移除永久代,并把方法区移至元空间,它位于本地内存中,而不是虚拟机内存中。
方法区是一个 JVM 规范,永久代与元空间都是其一种实现方式。在 JDK 1.8 之后,原来永久代的数据被分到了堆和元空间中。元空间存储类的元信息,静态变量和常量池等放入堆中。
运行时常量池
方法区的一部分
Class文件中存放编译期生成的各种字面量和符号引用的常量池表在类加载后会存放到运行时常量池中
除了在编译期生成的常量,还允许动态生成,例如 String 类的 intern()。
直接内存
在 JDK 1.4 中新引入了 NIO 类,它可以使用 Native 函数库直接分配堆外内存,然后通过 Java 堆里的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在堆内存和堆外内存来回拷贝数据。
native关键字
凡是带有native关键字的,就说明Java方法达不到了,需要调用底层c语言的库,会进入到本地方法栈中调用本地方法本地接口JNI(Java native interface)。
JNI的作用:扩展Java的使用,融合不同的编程语言为Java所用
在内存区域中专门开辟了一块内存区域,本地方法栈(Native method stack)
垃圾收集
垃圾收集主要是针对堆和方法区进行。程序计数器、虚拟机栈和本地方法栈这三个区域属于线程私有的,只存在于线程的生命周期内,线程结束之后就会消失,因此不需要对这三个区域进行垃圾回收。
判断一个对象是否可以被回收
1.引用计数算法
为对象添加一个引用计数器,每当有一个地方引用它时,计数器值加1,当引用失效时,计数器值减1,任何时刻,计数器为0的对象就是不可能再被使用的。
在两个对象出现循环引用的情况下,此时引用计数器永远不为 0,导致无法对它们进行回收。正是因为循环引用的存在,因此 Java 虚拟机不使用引用计数算法。
public class Test {
public Object instance = null;
public static void main(String[] args) {
Test a = new Test();
Test b = new Test();
a.instance = b;
b.instance = a;
a = null;
b = null;
doSomething();
}
}
在上述代码中,a 与 b 引用的对象实例互相持有了对象的引用,因此当我们把对 a 对象与 b 对象的引用去除之后,由于两个对象还存在互相之间的引用,导致两个 Test 对象无法被回收。
2.可达性分析算法
通过一系列成为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程走过的路径成为引用链,如果某个对象到GC Roots间没有任何引用链相连,则证明次对象是不可能再被引用的。
Java 虚拟机使用该算法来判断对象是否可被回收,GC Roots 一般包含以下内容:
- 虚拟机栈中局部变量表中引用的对象
- 本地方法栈中 JNI 中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中的常量引用的对象
3.再谈引用
判定对象是否存活都和“引用”离不开关系
1.强引用
最传统的引用的定义,是指在程序代码中普遍存在的引用赋值。
无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象
Object obj