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