一、简介

  1. Java虚拟机
    • Java虚拟机(Java Virtual Machine,JVM)是Java语言的核心,是执行Java二进制代码的虚拟计算机。
    • JVM本身是一个进程,负责解析Java程序并将其转换为特定平台可以执行的指令集。
    • 通过JVM,Java程序可以实现“一次编写,到处运行”的特性,使Java具有很强的平台无关特性。
  2. 虚拟机体系结构
    • JVM主要由以下三部分组成:
      1. Class Loader:类加载器,负责将编译后的Java代码转换成JVM能够理解的运行时数据结构–Class对象。
      2. Execution Engine:执行引擎,将指令集转换为操作系统可以理解的机器码,同时也包括了调用本地库和JNI(Java Native Interface)接口等。
      3. Runtime Data Area:包含Method Area,Heap,Java Stack和Native Stack四个组成部分,用于保存Java程序运行期间所需要的各种数据和信息。
  3. JVM标准
    • Java虚拟机规范定义了Java虚拟机的行为规范。(注意:Java语言规范和虚拟机规范不同,前者定义了Java语言的正式语法和语义,后者定义了Java虚拟机的实现标准)。

二、JVM类加载子系统

1. 类加载的过程

类加载是指将类的.class文件中的二进制数据读入内存,并在内存中创建一个Java.lang.Class对象
类加载的过程由三个步骤组成:
1. 加载(Loading):查找并加载类的二进制数据;
2. 链接(Linking):验证、准备和解析类的二进制数据(例如:静态变量赋予默认值);
3. 初始化(Initialization):为类的静态属性初始化,执行静态代码块等。(参考代码中Step 2)

2. 类加载器

类加载器用于从文件系统、Zip包或其他来源中加载.class文件,它只负责class文件的加载,不负责链接和初始化。
VM中有三种类加载器:
1. Bootstrap ClassLoader:负责加载<JAVA_HOME>/lib下的核心类库,是JVM自带的类加载器,无法被Java程序直接引用(因为没有被加载任何Java类)。
2. Extension ClassLoader:负责加载<JAVA_HOME>/lib/ext目录下的类库,为Java标准扩展提供支持。
3. App ClassLoader:负责加载ClassPath指定的所有类库,是Java程序中默认使用的类加载器。

3. 双亲委派模型

双亲委派模型是指当一个类加载器收到类加载请求时,首先会将该请求转发给父类加载器,如果父类加载器无法完成加载,则再由子类加载器自行完成加载(可以防止Java核心类库被篡改)

三、内存管理与垃圾回收

1. 内存模型

  • JVM所管理的内存可分为以下几个部分:
    1. 程序计数器:记录当前线程执行的下一条指令。
    2. Java虚拟机栈(JVM Stack):用于保存方法栈帧,其中局部变量表、操作数栈、动态链接、方法返回地址等内容都是在栈帧中存储的。
    3. 本地方法栈(Native Method Stack):为Java应用程序使用本地(Native)的方法服务。
    4. Java Heap:Java堆区是Java程序中最大的内存块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。
    5. 方法区(Method Area):用于存储已被JVM加载的类信息、常量池、字段描述、方法描述等数据。

2. 堆内存管理

  • 堆内存是JVM所管理的内存区域中最大的一块,主要用于存放Java对象实例和数组等,同时也是垃圾回收器工作的重点。
    • 堆内存又分为新生代和老年代。
      • 新生代:一般占整个堆内存的1/3到1/4左右,其中又分为Eden、Survivor0和Survivor1三个空间。
      • 老年代:存放熬过若干次垃圾回收仍然存活的Java对象,一般占整个堆内存的2/3到3/4之间。
  1. 垃圾回收算法
    • 搜索算法:根据一些合理的搜索策略,在所有可达对象中建立引用关系,找到所有的存活对象。主要有标记-清除法、复制算法、标记-压缩法和分代算法等。
    • 分代算法:根据Java对象生命周期长短的不同,将堆内存分为不同的年龄组,从而对不同年龄组采取不同的垃圾回收算法。Java虚拟机使用分代算法极大地提高了垃圾回收效率。

4. GC工具

  • JVM提供了不同的垃圾回收器(ALgOrithm),可以根据应用程序的性质进行选择、调整(如 -Xms、-Xmx和-XX:+Use G1 GC等参数),从而提高程序的性能与稳定性。一些常见的GC工具如下所示:
    1. Serial GC:一种单线程的垃圾回收器,适用于小型客户端应用。
    2. Parallel GC:使用多线程进行垃圾回收,适用于大型的服务器端应用。
    3. CMS(Concurrent Mark Sweep)GC:使用多线程进行垃圾回收,可以减少中断时间,适用于响应速度要求较高的应用场景。
    4. G1 GC:是JDK1.7才开始引入的一种并发垃圾回收器,可以将堆内存划分为若干小块,每次收集一部分垃圾,减少了全局暂停的时间,适用于超大型应用。

四、JVM运行时数据区

JVM(Java Virtual Machine,Java虚拟机)在运行Java程序的时候会把所管理的内存划分为几个不同的部分,称为JVM运行时数据区。这些区域包括:

1. 程序计数器

是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。字节码解释器的工作就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,使得程序可以实现跳转、循环、异常处理、线程恢复等功能。

2. 虚拟机栈

描述的是Java方法执行的内存模型。每个方法被执行的时候,Java虚拟机都会同步创建一个栈帧用于存储局部变量表、操作数栈、动态连接和方法出口等信息。一个方法被执行的时候对应着一个栈帧在虚拟机栈中的入栈和出栈(栈顶指针向上或向下移动)。在编写递归方法时,如果调用的层数过多,就可能抛出StackOverflowError异常。

3. 本地方法栈

与虚拟机栈类似,区别在于虚拟机栈是为执行Java方法服务的,而本地方法栈则是为执行Native方法服务的。

4. Java堆

是Java虚拟机所管理的内存中最大的一块。Java堆是垃圾收集器管理的主要区域,在虚拟机启动时创建。几乎所有的对象都在这里分配空间。由于现代垃圾收集器都采用分代收集算法,因此Java堆还可以细分为新生代和老年代。

5. 方法区

也称为永久代(Permanent Generation),Method Area与Java堆一样,是各个线程共享的内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然Oracle官方已经在JDK8中正式废弃了永久代,并用一个叫做元空间(Metaspace)的区域来替换它,但是仍然有很多基于JDK8之前版本的应用使用永久代,因此需要了解永久代与元空间的区别和联系。
6. 直接内存:不是JVM运行时数据区的一部分,但是也经常被讨论到。有些场景下,需要直接使用操作系统的内存而不是通过Java堆来进行内存分配。这时候就可以使用“直接内存”,直接内存不属于虚拟机运行时数据区域,而是通过调用操作系统的本地方法来进行分配和管理。

五、性能监控和故障处理工具

在应用程序开发和部署的过程中,经常需要使用一些性能监控和故障处理工具来帮助开发人员进行故障排查和性能优化。以下列举了一些比较常用的工具:

  1. JVM性能监控工具:JDK自带有jstat、jmap、jstack等性能监控和诊断工具,可以帮助开发者实时检测虚拟机各种状态和性能指标,比如堆内存占用、垃圾回收等信息。
  2. 故障排查工具:例如Java Flight Recorder、VisualVM等工具可以帮助开发者记录应用程序运行期间的事件,并提供各类统计数据和日志文件。这些工具可以帮助开发者更加方便的分析和定位问题。
  3. JVM调优原则与实践:JVM性能优化并不是件容易的事情,需要开发者有一定的运维和性能测试经验。在遇到性能瓶颈和应用程序异常报错时,针对性的进行JVM参数调整、垃圾回收器选择、堆大小分配等操作可以有效地提升应用程序的性能与稳定性。同时推荐学习一些JVM优化的实践技巧和案例,例如Java虚拟机规范、HotSpot源码分析、GC Tuning等相关书籍和文章。

六、深入理解JVM执行引擎

1. 指令集与解释器

Java虚拟机(JVM)通过解释器执行Java字节码。执行引擎是JVM最核心的组件之一,它实现了Java虚拟机指令集的解释和处理。

Java虚拟机指令集是一种面向栈的指令集,它的操作数在栈顶上执行。在Java虚拟机规范中定义了200多个指令,可以完成各种操作,如算术运算、逻辑运算、类型转换、对象操作等。

解释器是一个解释执行字节码的程序,其主要作用是将Java字节码解析成机器指令并执行。当Java应用程序启动时,JVM会将Java字节码交由解释器执行。

2. JIT编译器

即时编译器(JIT)是JVM的另一个重要组件,它在程序运行期间实时编译字节码为本地机器码,并将其缓存于内存中以供重复利用。这种方法可以将解释器执行字节码带来的性能损失降到最低,并极大提升程序的性能表现。

JIT编译器会动态地分析应用程序,然后将经常执行的代码编译为本地机器码,使得这段代码的执行速度大幅提升。

3. AOT编译器

AOT编译器是指在运行应用程序之前将Java字节码静态地编译成本地机器码的编译器。这种方式可以避免运行时动态编译的开销,从而提升应用程序启动的性能。此外,编译出的机器码对JVM的依赖低,具有更广泛的平台兼容性。

七、JVM实战

1. JVM与操作系统交互

JVM需要和操作系统交互来实现对系统资源的管理。常见的如内存管理、线程调度等。

JVM通过JNI来交互操作系统。JNI提供了一组Java API以及Native API让Java应用程序可以调用本地方法库。

Java还提供了Runtime类来获取JVM运行时的信息,如系统内存、进程ID等。此外,在Java8及以后的版本中,还提供了ProcessHandle类来列出当前所有进程,并可以获取指定进程的PID和信息等。

2. JVM在容器化环境下的最佳实践

在Docker等容器化环境中,由于容器的隔离性和快速部署,很容易实现应用程序的水平扩容和快速上线。

JVM应用程序在容器中运行时,需要考虑到内存使用和GC表现等问题。建议设置合理的内存上限、进行垃圾回收策略优化以及开启并发GC等操作。

此外,还需要注意JVM在容器中运行时的一些变化,比如CPU使用情况、内存规划等。

3. JVM在大数据场景下的最佳实践

在大数据场景下,JVM一般作为Hadoop、Spark等分布式计算框架的执行引擎。

针对大数据场景,需要对JVM进行优化。可以通过调整JVM参数来优化性能,比如减少GC次数、缩短GC停顿时间等。

此外,为了使JVM发挥更好的性能表现,还需要设计合理的代码结构和算法,并尽可能减少不必要的内存消耗。

4. 实战案例分析

以Tomcat为例可以通过配置JVM参数来优化Tomcat的性能:

  • Xmx:最大堆内存,建议设置为服务器总内存的70%-80%
  • Xms:初始堆内存,建议与Xmx相等
  • XX:+UseConcMarkSweepGC:使用CMS回收,降低GC暂停时间
  • XX:CMSInitiatingOccupancyFraction:CMS触发垃圾回收的占用内存百分比
  • XX:+DisableExplicitGC:禁用System.gc()方法
  • XX:+UseCompressedOops:使用压缩指针

此外还可以通过分析线程数、请求响应时间、堆内存使用率等参数来监控Tomcat的运行状况,并对可能出现的问题进行及时定位和解决。

06-02 09:15