JVM如何工作? - JVM架构

翻译自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文件是否与ClassInterfaceEnum相关
  • 修饰符,变量和方法信息等

加载.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类加载器的子代。 它加载扩展目录JAVA_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++)的集合。

猜你喜欢

转载自blog.csdn.net/hqqqqqqq555/article/details/88051578