JVM知识点总结

基本概念

JVM 是可运行Java代码的假想计算机 ,包括一套字节码指令集、一组寄存器、一个栈、一个垃圾回收、堆和一个存储方法域。JVM 是运行在操作系统之上的,它与硬件没有直接的交互。
JVM知识点总结

运行过程

我们都知道 Java 源文件,通过编译器,能够生产相应的.Class 文件,也就是字节码文件, 而字节码文件又通过 Java 虚拟机中的解释器,编译成特定机器上的机器码 。
简单来说,如下:
1 Java 源文件—->编译器—->字节码文件
2 字节码文件—->JVM—->机器码
每一种平台的解释器是不同的,但是实现的虚拟机是相同的,这也就是 Java 为什么能够 跨平台的原因了 ,当一个程序从开始运行,这时虚拟机就开始实例化了,多个程序启动就会 存在多个虚拟机实例。

Java内存模型

JVM知识点总结

JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法区】、线程共享区域【JAVA 堆、方法区】、直接内存,线程私有数据区域生命周期与线程相同,线程共享区域随虚拟机的启动/关闭而创建/销毁。

程序计数器

一块较小的内存空间, 是当前线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程私有”的内存,这个内存区域是唯一一个在虚拟机中没有规定任何 OutOfMemoryError 情况的区域。

虚拟机栈和本地方法区

是描述 java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈帧(Stack Frame) 用于存储局部变量表、操作数栈、动态链接、方法出口等信息。其中栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接 (Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。
本地方法区和 Java Stack 作用类似, 区别是虚拟机栈为执行 Java 方法服务, 而本地方法栈则为 Native 方法服务,

方法区/永久代

用于存储被 JVM 加载的类信息、常量、静 态变量、即时编译器编译后的代码等数据, 运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版 本、字段、方法、接口等描述等信息外,还有一项信息是常量池用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加 载后存放到方法区的运行时常量池中。

是被线程共享的一块内存区域,创建的对象和数组都保存在 Java 堆内存中,也是垃圾收集器进行 垃圾收集的最重要的内存区域。由于现代 VM 采用分代收集算法, 因此 Java 堆从 GC 的角度还可以 细分为: 新生代(Eden 区、From Survivor 区和 To Survivor 区)和老年代。

垃圾回收与算法

JVM知识点总结

如何确定垃圾

引用计数法
在 Java 中,引用和对象是有关联的。如果要操作对象则必须用引用进行。对象如果没有任何与之关联的引用,即他们的引用计数都不为 0,则说明对象不太可能再被用到,那么这个对象就是可回收 对象。
可达性分析
通过一系列的“GC roots”对象作为起点搜索。如果在“GC roots”和一个对象之间没有可达路径,则称该对象是不可达的。不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记 过程。两次标记后仍然是可回收对象,则将面临回收。

垃圾收集算法

标记清除算法(Mark-Sweep)
最基础的垃圾回收算法,分为两个阶段,标注和清除。标记阶段标记出所有需要回收的对象,清 除阶段回收被标记的对象所占用的空间。 从图中我们就可以发现,该算法最大的问题是内存碎片化严重,后续可能发生大对象不能找到可利用空间的问题。
复制算法(copying)
按内存容量将内存划分为等大小 的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用 的内存清掉,该算法存在的问题是可用内存被压缩到了原本的一半。
标记整理算法(Mark-Compact)
标记后不是清理对象,而是将存活对象移向内存的一端,然后清除端边界外的对象。
分代回收
分代收集法是目前大部分 JVM 所采用的方法,其核心思想是根据对象存活的不同生命周期将内存 划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。

类加载机制

JVM 类加载机制分为五个部分:加载,验证,准备,解析,初始化,其中验证,准备,解析可以并称为连接。
加载
加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的入口。
验证
确保 Class 文件的字节流中包含的信息是否符合当前虚拟机的要求,并 且不会危害虚拟机自身的安全。
准备
准备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间,这里所说的初始值概念并非赋予已定义的初始值,而是赋予最开始的值,如0,赋予已定义的值是类构造器方法之后。
解析
解析阶段是指虚拟机将常量池中的符号引用替换为直接引用的过程,符号饮用和直接引用的区别在于引用的目标是否已加载在内存中。
初始化
初始化阶段是类加载最后一个阶段,到了初始阶段,才开始真正执行类中定义的 Java 程序代码。
类构造器
初始化阶段是执行类构造器方法的过程,由编译器类变量的赋值操作和静态语句块中的语句合并而成的。
以下几种情况不会执行类初始化:

  1. 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
  2. 定义对象数组,不会触发该类的初始化。
  3. 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触
    发定义常量所在的类。
  4. 通过类名获取 Class 对象,不会触发类的初始化。
  5. 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初
    始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。
  6. 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作。

类加载器的双亲委派模型

JVM知识点总结
JVM 通过双亲委派模型进行类的加载,当一个类收到了类加载请求,他首先不会尝试自己去加载这个类,而是把这个请求委派给父类去完成,父类加载器无法完成这个请求的时候,子类加载器才会尝试自己去加载。这样的机制保证了使用不同的类加载器最终得到的都是同样一个Object 对象。