JVM 是如何工作的——JVM 架构?
JVM(Java虚拟机)充当运行Java应用程序的运行时引擎。 JVM 是实际调用Java代码中存在的main方法的那个。 JVM 是 JRE(Java运行时环境)的一部分。
Java应用程序称为 WORA(一次编写,随处运行)。这意味着程序员可以在一个系统上开发Java代码,并且可以期望它在任何其他支持 Java 的系统上运行而无需任何调整。由于 JVM,这一切皆有可能。
当我们编译一个. Java文件, .class文件(包含字节码)具有相同的类名存在于. Java文件由Java编译器生成。当我们运行这个.class文件时,它会进入各个步骤。这些步骤一起描述了整个 JVM。
类加载器子系统
它主要负责三项活动。
- 正在加载
- 链接
- 初始化
加载:类加载器读取“. class”文件,生成相应的二进制数据并保存在方法区。对于每个“ .class”文件,JVM 将以下信息存储在方法区域中。
- 已加载类及其直接父类的完全限定名称。
- “ .class”文件是否与 Class 或 Interface 或 Enum 相关。
- 修饰符、变量和方法信息等。
加载“ .class”文件后,JVM 在堆内存中创建一个 Class 类型的对象来表示该文件。请注意,此对象是Java.lang包中预定义的 Class 类型。程序员可以使用这些 Class 对象来获取类级别的信息,例如类的名称、父名称、方法和变量信息等。要获取此对象引用,我们可以使用 Object 类的getClass()方法。
Java
// 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;
}
}
Java
// 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());
}
}
Student
getName
setName
getRoll_no
setRoll_no
name
roll_No
注意:对于每个加载的“ .class”文件,只创建一个类的对象。
Student s2 = new Student();
// c2 will point to same object where
// c1 is pointing
Class c2 = s2.getClass();
System.out.println(c1==c2); // true
链接:执行验证、准备和(可选)解决方案。
- 验证:它确保.class文件的正确性,即检查该文件是否正确格式化并由有效的编译器生成。如果验证失败,我们会得到运行时异常Java.lang.VerifyError 。此活动由组件 ByteCodeVerifier 完成。一旦这个活动完成,那么类文件就可以编译了。
- 准备工作:JVM 为类变量分配内存并将内存初始化为默认值。
- 解决方案:这是用直接引用替换类型中的符号引用的过程。它是通过搜索方法区域来定位引用的实体来完成的。
初始化:在这个阶段,所有的静态变量都被分配了它们在代码和静态块(如果有的话)中定义的值。这在类中从上到下执行,在类层次结构中从父到子执行。
一般来说,有三种类加载器:
- 引导类加载器:每个 JVM 实现都必须有一个引导类加载器,能够加载受信任的类。它加载存在于“ JAVA_HOME/jre/lib”目录中的核心Java API 类。这条路径通常被称为引导路径。它以 C、C++ 等本地语言实现。
- 扩展类加载器:它是引导类加载器的子类。它加载扩展目录“ JAVA_HOME/jre/lib/ext” (扩展路径)或Java.ext.dirs 系统属性指定的任何其他目录中存在的类。它由sun.misc.Launcher$ExtClassLoader类在Java中实现。
- System/Application 类加载器:它是扩展类加载器的子类。它负责从应用程序类路径加载类。它在内部使用映射到Java.class.path 的环境变量。它也由sun.misc.Launcher$AppClassLoader类在Java中实现。
Java
// 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());
}
}
null
jdk.internal.loader.ClassLoaders$AppClassLoader@8bcc55f
注意: JVM 遵循Delegation-Hierarchy 原则来加载类。系统类加载器将加载请求委托给扩展类加载器,扩展类加载器将请求委托给引导类加载器。如果在引导路径中找到类,则加载该类,否则请求再次传输到扩展类加载器,然后再传输到系统类加载器。最后,如果系统类加载器无法加载类,那么我们会得到运行时异常Java.lang.ClassNotFoundException 。
JVM内存
- 方法区:在方法区,存储了类名、直接父类名、方法和变量信息等所有类级别的信息,包括静态变量。每个JVM只有一个方法区,它是一个共享资源。
- 堆区:所有对象的信息都存放在堆区。每个 JVM 也有一个堆区。它也是一种共享资源。
- 堆栈区:对于每个线程,JVM 创建一个存储在这里的运行时堆栈。该堆栈的每个块都称为激活记录/堆栈帧,其中存储方法调用。该方法的所有局部变量都存储在它们相应的框架中。线程终止后,其运行时堆栈将被 JVM 销毁。它不是共享资源。
- PC寄存器:存放线程当前执行指令的地址。显然,每个线程都有单独的 PC 寄存器。
- 本机方法堆栈:对于每个线程,都会创建一个单独的本机堆栈。它存储本机方法信息。
执行引擎
执行引擎执行“ .class” (字节码)。它逐行读取字节码,使用各种内存区域中存在的数据和信息并执行指令。它可以分为三个部分:
- 解释器:逐行解释字节码,然后执行。这里的缺点是当一个方法被多次调用时,每次都需要解释。
- 即时编译器(JIT) :用于提高解释器的效率。它编译整个字节码并将其更改为本机代码,因此每当解释器看到重复的方法调用时,JIT 会为该部分提供直接的本机代码,因此不需要重新解释,从而提高了效率。
- 垃圾收集器:它销毁未引用的对象。有关垃圾收集器的更多信息,请参阅垃圾收集器。
Java本机接口(JNI):
它是与本地方法库交互并提供执行所需的本地库(C、C++)的接口。它使 JVM 能够调用 C/C++ 库并被可能特定于硬件的 C/C++ 库调用。
本机方法库:
它是执行引擎所需的本机库(C、C++)的集合。