JVM虚拟机(3):概念、构造、垃圾回收
JVM 全称是 Java Virtual Machine,属于程序虚拟机。
JVM 封装了一组自定义的字节码指令集,有自己的程序计数器和执行引擎,内存管理机制,线程及栈管理机制,看起来就像一台完整的计算机,这就是JVM被称为机器的的原因。
虚拟机
虚拟机(Virtual Machine):指通过软件模拟具有完整硬件系统功能的,运行在一个完全隔离环境中的完整计算机系统。虚拟机是一款软件,用来执行虚拟计算机指令,称为虚拟机。大体上可分为系统虚拟机和程序虚拟机。
系统虚拟机:完全对物理计算机的仿真,提供软件对硬件资源的访问,可以运行操作系统的软件平台。例如 VMware,Visual Box。
程序虚拟机:通常为某一类编程语言的程序设计。最大作用和目的是跨平台。
JVM概念
Java 是一种跨平台语言,一次编译,到处运行。是因为Java编译的字节码文件不是直接在底层的系统平台运行,而是在Java虚拟机上运行的。
JVM 屏蔽底层系统的差异,为Java字节码构造了一个统一的运行环境。
JVM 封装了一组自定义的字节码指令集,有自己的程序计数器和执行引擎,内存管理机制,线程及栈管理机制,看起来就像一台完整的计算机,这就是JVM被称为机器的的原因。
JVM的构造
JVM主要由 类加载器,运行时数据区,扫许引擎 三个部分组成。
运行时数据区 主要包括:方法区,堆,Java栈,程序计数寄存器。Java栈包含 虚拟机栈 和 本地方法栈。
- 方法区:主要存放从磁盘加载进来的类字节码。
- 堆:存放在程序运行过程中创建的类实例。
- Java栈:存放着方法运行时的局部变量。
- 程序寄存器:存放当前线程执行到的字节码指令。
Java程序运行的时候,实际上是以线程为单位运行的,当JVM进入启动类的main方法的时候,就会为应用程序创建一个主线程,main 方法里的代码就会被这个主线程执行,每个线程都有自己的Java 栈。
JVM垃圾回收
JVM 通过一种 可达性分析算法进行垃圾对象的识别,具体过程是:先从线程栈帧中的局部变量或者方法区的静态变量出发,将这些变量引用的对象进行标记,然后看这些被标记对象是否引用了其他对象,继续进行标记,所有被标记过的对象都是使用的对象,而那些没有被标记的对象就是可回收的垃圾对象。
由引可以看出,可达性分析算法其实是一个引用标记算法。
标记完后,JVM就会对垃圾对象占用的内存进行回收,回收主要有三种方法:
第一种方法是清理:将垃圾对象占据的内存清理掉。
其实JVM并不会真的将这些垃圾内存进行清理,而是将这些垃圾对象所占用的内存空间标记为空闲,记录在一个空闲列表里,当应用程序需要创建对象的时候,就从这个空闲列表中找一段空闲内存分配给这个新对象。
缺陷也很明显,因垃圾对象是散落在内存各处,标记出来的空闲空间也是不连续的,当程序需要创建一个数组申请一块大内存空间时,即使堆中有足够的空闲空间,也无法为程序分配内存。
第二种方法是压缩:从堆空间的头开始,将存活的对象拷贝放在一段连续的内存空间中,那么,其余的空间就是连续的空闲空间。
第三种方法是复制:将堆空间分成两部分,只在其中一部分创建对象,当这部分空间用完时,将标记过可用对象复制到另一个空间中。
JVM 将这两个空间分别命名为 from 区域和 to 区域。当对象从 from 区域复制到 to 区域后,两个区域交换名称引用,继续在 from 区域创建对象,直到 from 区域没有空间。
垃圾分代回收
JVM 在进行具体的垃圾回收的时候,会进行分代回收。绝大多数的 Java 对象存活时间非常短,很多时候就是在一个方法内创建对象,对象引用放在栈中,当方法调用结束,栈帧出栈时这个对象就失去引用,成为垃圾。
针对这种情况,JVM 将堆内存分成新生代(young)和老年代(old)两个区域,创建对象的时候只在新生代创建,当新生代空间不足时,只对新生代进行垃圾回收,这样需要处理的内存空间就比较小,垃圾回收的速度就比较快。
新生代又分为 Eden,From,和 To 三个区域,每次垃圾回收都是扫描 Eden 区 和 From 区,将存活对象复制到 To 区。当一个对象经过几次新生代垃圾回收,也就是几次从 From 区复制到 To 区以后,依然存活,那么,这个对象就会被复制到老年代区域。
当老年代空间已满,也就是无法将新生代中多次复制后依然存活的对象复制进去的时候,就会对新生代和老年代的内存空间进行一次全量垃圾回收,即 Full GC。
所以根据应用程序的对象存活时间,合理设置老年代和新生代的空间比例对JVM垃圾回收的性能有很大影响,JVM 设置老年代和新生代的比例参数是:**-XX:NewRatio**。
JVM垃圾回收器
具体执行垃圾回收的垃圾回收器有四种:
第一种是串行(Serial)垃圾回收器,JVM早期的垃圾回收器,只有一个线程执行垃圾回收。
第二种是并行(Parallel)垃圾回收器,它启用多线程执行垃圾回收。在多核CPU上效率高。
在串行和并行垃圾回收过程中,当垃圾回收线程工作时,必须停止用户线程的工作,否则可能会导致对象的引用标记错乱。因此垃圾回收过程也被称为 stop-the-world,在用户视角来看,所有程序都不再执行,即世界被停止。
第三中是CMS并发垃圾回收器,在垃圾回收的某些阶段,垃圾回收线程和用户线程可以并发运行,因此对用户线程影响较小。适用于 Web 类应用。
最后一种是G1垃圾回收器,它将整个堆空间分成多个子区域,然后在这些子区域上各自独立进行垃圾回收,在回收过程中垃圾回收线程和用户线程也是并发运行的。适用于各种场景,是未来主要的垃圾回收器。
JVM相关问题
OutOfMemoryError
堆空间不足,原因有两点:
- 可能是JVM分配的内存空间不足以让程序正常运行,这时可通过调整 -Xmx 参数增加内存空间;
- 也可以是内容泄露,无法垃圾回收导致内存溢出,这时可通过 jmap 命令查看堆中的对象情况,分析是否有内存泄露。
StackOverflowError
线程栈空间不足。通常是因为方法调用的层次太多,导致栈帧太多。可先通过栈异常信息观察是否存在错误的递归调用,因为每次递归都会使嵌套方法调用更深入一层。如果调用是正常的,可以尝试调整 -Xss 参数来增加栈空间大小。
程序运行卡顿延迟
可通过 jstat 命令查看垃圾回收器的运行状态,判断是否存在较长时间的 FullGC,然后调整垃圾回收器的相关参数,使垃圾回收对程序支宪的影响尽可能小。
JVM执行引擎解释执行
执行引擎在执行字节码指令的时候是解释执行的,也就是每个字节指令都会被解释成一个底层的 CPU 指令,但这样执行效率比较差,JVM对此进行了优化,将频繁执行的代码编译为底层CPU指令存储起来,后面再执行的时候直接执行编译号的指令,不再解释执行,这就是JVM的即时编译(JIT)。
Web 应用程序通常是长时间运行的,使用 JIT 编译会有很好的优化效果,可以通过 -server 参数打开 JIT 的 C2 编译器进行优化。
相关参考
JVM虚拟机(3):概念、构造、垃圾回收