【JVM系列】Java虚拟机体系结构

时间:2022-12-23 15:52:59

Java程序动态装载

Java的体系结构可以在运行时决定使用的类型,装载它们,使用它们。下面由两种方式可以实现java类型的动态装载。

1、Class.forName()

static Class<?> forName(String className,boolean shouldInitialize,ClassLoader classLoader)

className:类型的全限定名
shouldInitialize:true表示类型会在forName()方法返回之前连接并初始化
false表示类型会被装载,可能会被连接但是不会被forName()方法明确的初始化
classLoader:指定装载类型的类装载器

static Class<?> forName(String className)
这个方法总是使用装载执行forName()请求的类的类装载器来装载类型,并且总是初始化该类型。

2、使用自定义ClassLoader对象的loadClass()方法

Class<?> loadClass(String className)

使用自定义的classLoader来装载指定的类型

上面两种方法都可以得到Class类的引用,对于每一个被装载的类型(不管是类还是接口),虚拟机都会相应地为它创建一个java.lang.Class类的实例,并且虚拟机还会以某种方式把这个实例和存储在方法区的类型数据关联起来。

Java虚拟机体系结构

【JVM系列】Java虚拟机体系结构

我们知道java程序分为编译和运行两个过程,对于一个Test.java文件:
编译:javac Test.java
它会将.java文件编译成一个.class文件。

执行:java Test
它会运行一个java程序

JVM是java程序运行的环境,一个JVM实例对应一个独立运行的java程序,它是进程级别的,即一个Java程序运行在一个单独的JVM实例中,一个JVM实例对应的就是操作系统的一个进程。

一个运行时的Java虚拟机的天职就是负责运行一个Java程序,当一个Java程序启动,一个Java虚拟机也就诞生了。

JVM的启动

当执行java Test来运行一个程序的时候,java就会通过下面的一个过程来确定JVM的路径和相关的配置参数。

(1)装载JVM动态库,激活JVM,产生一个JVM运行实例
(2)初始化JVM,产生第一个类装载器——Bootstrap Loader(启动类装载器)
(3)Bootstrap Loader所做的初始化操作中,会加载Launcher.java中的ExtClassLoader(扩展类装载器),设定其parent为null,代表其父加载器为Bootstrap Loader,接着会加载Launcher.java中的AppClassLoader(用户自定义类加载器),并设其parent为ExtClassLoader。这两个加载器都是以静态类的形式存在的。

程序的运行

当JVM启动并且初始化完成之后,相当于产生了一个java程序运行进程,接着就是在这个JVM实例中运行该java程序了。

首先我们来看看JVM的体系结构

【JVM系列】Java虚拟机体系结构

JVM体系由两个内部体系结构组成:三个子系统和两大组件
三个子系统:类装载器子系统、执行引擎子系统、GC子系统
两大组件:内存运行数据区和本地接口

类装载器子系统:每个Java虚拟机都有一个类装载器子系统,它根据给定的全局限定名来装入类型(类或接口)

执行引擎:运行中java程序的每个线程都是一个独立的虚拟机执行引擎实例。从线程生命周期的开始到结束,它要么在执行字节码,要么执行本地方法。java虚拟机的实现可以使用对用户程序不可见的线程,比如:垃圾收集器,这样的线程不需要是实现的执行引擎的实例。所有用户运行程序的线程,都是实际工作的执行引擎。

运行时数据区:当一个Java虚拟机运行一个程序时,它需要内存来存储许多东西,例如,字节码,从已装载的class文件中得到的其他信息,程序创建的对象,传递给方法的参数,返回值,局部变量,以及运行的中间结果等等,这些“运行时数据”都会以某种形式存在于每一个Java虚拟机中。

方法区:用来存放装载的class文件的类型信息。当虚拟机实例装载一个class文件时,它会从这个class文件包含的二进制数据中解析类型信息,并且把这些类型信息存放到方法区中。

:当程序运行时,虚拟机会把所有该程序在运行时创建的对象都放在堆中。一个java虚拟机实例只有一个堆空间,因此所有线程共享这个堆。又因为每个java程序占用一个java虚拟机实例,所以每个java程序都有自己的堆空间。

每个Java虚拟机实例都有一个方法区以及一个堆,它们是由该虚拟机实例中所有线程共享的。

程序计数器PC:对于一个运行中的java程序而言,每个线程都有它自己的PC寄存器,它是在该线程启动时创建的,PC寄存器的大小是一个字节。当线程执行某个java方法时,PC寄存器的内容总是指向将被执行指令的地址。

Java栈:当启动一个线程时,java虚拟机会为它分配一个java栈,java栈以帧为单位保存线程的运行状态,虚拟机只会之间对java栈执行两种操作:以帧为单位的压栈或者出栈。

本地方法栈:当某个线程调用一个本地方法时,它就会进入一个全新的并且不再受虚拟机限制的世界,本地方法可以通过本地方法接口来访问虚拟机运行时数据区。本地方法接口都会使用某个本地方法栈。

本地方法接口:java里面使用了JNI,是为可移植性准备的。

【JVM系列】Java虚拟机体系结构

当我们在JVM运行一个程序的时候,运行上面的Test程序:
1、AppClassLoader装载主类Test的class文件

2、创建初始线程调用这个类的入口方法public static void main(String[] args)。

我们知道一个Java程序的入口是main方法,Java虚拟机实例就是通过调用这个入口main方法来运行一个程序,例如下面程序。

public class TestClass {
public static void main(String[] args) {
System.out.println("Hello World!");
}
}

Java程序初始类中的main()方法,将作为该程序初始线程的起点,任何其他的线程都是由这个初始线程启动的。这个线程就是程序的主线程。

Java虚拟机内部有两种线程:守护线程与非守护线程。

守护线程:通常是由虚拟机自己使用的,比如执行垃圾收集任务的线程。另外,Java程序也可以把它创建的任何线程标记为守护线程。
非守护线程:只要非守护线程还存在,Java程序就会继续执行,当程序中的所有非守护线程都终止时,虚拟机实例就会自动退出。

JVM执行引擎实例对应运行程序的线程,它是线程级别的。