JVM如何工作--JVM体系结构?

文章目录
  1. 1. JVM概念
  2. 2. 类加载子系统
  3. 3. JVM运行时内存
  4. 4. 执行引擎
  5. 5. Java本地接口(JNI)
  6. 6. 本地方法库

JVM概念

JVM(Java Virtual Machine)是运行Java应用程序的运行时引擎,运行在操作系统之上的,它与硬件没有直接的交互。JVM是JRE(Java Runtime Environment)的一部分。

Java应用程序被称为WORA(Write Once Run Anywhere)。这意味着程序员可以在一个系统上编写Java代码,然后在任何其他系统上运行,无需进行任何调整。

我们都知道Java源文件,通过编译器,能够生产相应的.Class文件,也就是字节码文件,而字节码文件又通过 Java 虚拟机中的解释器,编译成特定机器上的机器码。

  1. Java 源文件—->编译器—->字节码文件
  2. 字节码文件—->JVM—->机器码

每种平台的解释器是不同的,但实现的虚拟机是相同的,这也是Java为什么能够跨平台的原因。

类加载子系统

主要负责三个活动:

  • 加载(Loading)
  • 连结(Linking)
  • 初始化(Initialization)

Loading:类加载器读取 .class 文件,生成相应的二进制数据并将其保存在方法区(JAVA8后叫元数据区,文章后面统一叫方法区)。

对于每个.class文件,JVM将以下信息存储在方法区域中:

  1. 类及其直接父类的全限定名称
  2. .class文件类型标识,Class、Interface或Enum
  3. 修饰符、变量和方法信息等等

加载.class文件之后,JVM会为 .class文件 创建Class类型的对象并存储在堆内存中。注意,该对象的类型为java.lang包中定义的Class 。我们可以通过这个Class对象来获取类级别的信息,例如类名称、父类名称、方法和变量信息等。可以使用Object类的getClass()方法获取此对象的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// A Java program to demonstrate working of a Class type 
// object created by JVM to represent .class file in
// memory.
import java.lang.reflect.Field;
import java.lang.reflect.Method;

// Java code to demonstrate use of Class object
// created by JVM
public class Test
{
public static void main(String[] args)
{
Student s1 = new Student();

// Getting hold of Class object created
// by JVM.
Class c1 = s1.getClass();

// Printing type of object using c1.
System.out.println(c1.getName());

// getting all methods in an array
Method m[] = c1.getDeclaredMethods();

for (Method method : m)
System.out.println(method.getName());

// getting all fields in an array
Field f[] = c1.getDeclaredFields();
for (Field field : f)
System.out.println(field.getName());
}
}

// A sample class whose information is fetched above using
// its Class object.
class Student {
private String name;
private int roll_No;

public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getRoll_no() { return roll_No; }
public void setRoll_no(int roll_no) {
this.roll_No = roll_no;
}
}

输出:

1
2
3
4
5
6
7
Student
getName
setName
getRoll_no
setRoll_no
name
roll_No

注意:对每个被加载的.class文件,仅创建一个 Class对象。

1
2
3
4
5
Student s2 = new Student();
// c2 will point to same object where
// c1 is pointing
Class c2 = s2.getClass();
System.out.println(c1==c2); // true

Linking:执行验证(verification)、准备(preparation)解析(resolution)。

  • verification:它确保.class文件的正确性,即检查该文件的格式是否正确,是否由有效的编译器生成。如果验证失败,我们将获得运行时异常java.lang.VerifyError
  • preparation:JVM为类变量分配内存,并设置类变量的初始值。

注意这里所说的初始化,比如一个类变量定义为:

1
public static int a = 1314;

实际上变量a准备阶段过后的初始值为0而不是1314,将a赋值为1314put static指令是程序被编译后,存放在类构造器方法之中。

如果声明a变量为final类型:

1
public static final int a = 1314;

在编译阶段为a生成ConstantValue属性,在准备阶段虚拟机会根据ConstantValue属性将a赋值为1314

  • resolution:虚拟机将常量池中的符号引用替换为直接引用的过程。符号引用就是class文件中的CONSTANT_Class_infoCONSTANT_Field_infoCONSTANT_Method_info等类型的常量。

Initialization:在这个阶段,所有的静态变量都会初始化为代码中定义的值。顺序是从父类到子类、从上到下执行。

通常,有三种类加载器:

  • Bootstrap class loader:每个JVM实现都必须具有一个引导类加载器,加载受信任的类。它加载JAVA_HOME/jre/lib目录中的Java API核心类。该路径通常称为引导路径。它以C、C++等本地语言实现。

  • Extension class loader:它是引导类加载器的子级。它将加载扩展目录JAVA_HOME/jre/lib/ext(扩展路径)或java.ext.dirs系统属性指定的任何其他目录中存在的类。它由Java中sun.misc.Launcher$ExtClassLoader类实现。

  • System/Application class loader:它是扩展类加载器的子级。它负责从应用程序类路径(java.class.path环境变量指定的路径)加载类。它由Java中sun.misc.Launcher$AppClassLoader类实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
// Java code to demonstrate Class Loader subsystem 
public class Test
{
public static void main(String[] args)
{
// String class is loaded by bootstrap loader, and
// bootstrap loader is not Java object, hence null
System.out.println(String.class.getClassLoader());

// Test class is loaded by Application loader
System.out.println(Test.class.getClassLoader());
}
}

输出:

1
2
null
sun.misc.Launcher$AppClassLoader@73d16e93

注意: JVM通过双亲委派模型进行类的加载。应用程序类加载器将加载请求委托给扩展类加载器,扩展类加载器将请求委托给引导类加载器。如果在引导路径中找到了类,则将装入该类,否则再次将请求传输到扩展类加载器,然后再传输到应用程序类加载器。最后,如果应用程序类加载器无法加载类,则将获得运行时异常java.lang.ClassNotFoundException

JVM运行时内存

方法区:方法区中,将存储所有类级别信息,例如类名称、父类名称、方法和变量信息等,包括静态变量。它是共享资源。

堆区:所有对象的信息存储在堆区中。它也是共享资源。

虚拟机栈: JVM为每个线程创建一个运行时栈,该栈存储在此处。此栈的每个块都称为栈帧,用于存储方法调用。该方法的所有局部变量都存储在其相应的栈帧中。线程终止后,它的运行时栈将被JVM销毁。它不是共享资源。

程序计数器:存储当前线程执行指令的地址。显然,每个线程都有单独的程序计数器。

本机方法栈:为每个线程创建单独的本机方法栈。它存储本地方法信息。

执行引擎

执行引擎执行.class(字节码)。它逐行读取字节码,使用各个数据区中的数据和信息并执行指令。它可以分为三个部分:

  • 解释器:它逐行解释字节码,然后执行。缺点是,当一个方法被多次调用时,每次都需要解释。
  • 即时编译器(JIT):用于提高解释器的效率。它编译整个字节码并将其更改为本机代码,因此每当解释器看到重复的方法调用时,JIT都会为该部分直接提供本机代码,以便防止重新解释,从而提高了效率。
  • 垃圾收集器:销毁未引用的对象。

Java本地接口(JNI)

与本地方法库交互并为执行提供本地库(C、C++)的接口。它使JVM可以调用C/C++库,并可以被特定硬件的C/C++库调用。

本地方法库

它是执行引擎所需的本地库(C、C++)的集合。