Java 虚拟机(JVM)是一个能够执行 Java 字节码的虚拟机器,它是 Java 技术的核心,提供了跨平台、安全性和自动内存管理等特性。JVM 架构包括以下几个主要组成部分:

类加载器(ClassLoader): 类加载器负责将字节码文件加载到内存中,并生成相应的类对象。JVM 提供了三种类加载器:BootstrapClassLoader、ExtensionClassLoader 和 AppClassLoader,它们分别负责加载 Java 核心类库、扩展类库和应用程序类。 运行时数据区(Runtime Data Area): 运行时数据区包括方法区、堆、虚拟机栈、本地方法栈和程序计数器等,用于存储程序运行过程中的数据。其中,堆用于存储对象实例,方法区用于存储类信息和静态变量,虚拟机栈用于存储方法调用和局部变量,本地方法栈用于执行本地方法,程序计数器用于记录当前线程执行的字节码指令地址。 执行引擎(Execution Engine): 执行引擎负责解释和执行字节码指令,将 Java 字节码转换为机器码并执行。JVM 提供了多种执行引擎,包括解释器、即时编译器和混合模式执行引擎,用于提高程序的执行效率。 本地方法接口(Native Interface): 本地方法接口允许 Java 程序调用本地方法,即由其他语言编写的原生代码。JVM 提供了 JNI(Java Native Interface)作为 Java 程序与本地代码交互的标准接口。 垃圾回收器(Garbage Collector): 垃圾回收器负责自动管理内存,通过回收不再使用的对象实例来释放内存空间,防止内存泄漏和溢出。JVM 提供了多种垃圾回收算法和垃圾回收器实现,如标记-清除算法、复制算法、标记-整理算法和分代垃圾回收等。 即时编译器(Just-In-Time Compiler,JIT): 即时编译器将字节码动态编译为本地机器码,以提高程序的执行效率。JIT 编译器可以将频繁执行的字节码指令优化为机器码,减少解释器的执行开销,提高程序的运行速度。

综上所述,JVM 架构由类加载器、运行时数据区、执行引擎、本地方法接口、垃圾回收器和即时编译器等组成,它们共同协作实现了 Java 程序的执行和内存管理。

类加载器(ClassLoader)

是 Java 虚拟机(JVM)的一部分,负责加载 Java 类文件到内存中,以便在程序运行时创建相应的类对象。类加载器是 JVM 实现动态类加载和运行时多态的关键组件之一。

Java 中的类加载器属于反射机制的一部分,它主要负责以下几个任务:

加载(Loading)

类加载器通过查找类文件并将其字节码加载到 JVM 内存中。加载过程可以是从本地文件系统、网络或其他来源加载类文件。 类加载器的加载过程主要包括以下几个步骤:

定位: 类加载器首先需要根据类的全限定名(包括包名和类名)来定位类文件的位置。在标准的 Java 应用程序中,类文件通常位于类路径(Classpath)中,可以是文件系统中的路径,也可以是 JAR 文件中的路径,甚至可以是网络资源的路径。

JAR 文件中的路径,示例 import java.net.URL;

import java.net.URLClassLoader;

public class JarClassLoaderExample {

public static void main(String[] args) throws Exception {

// 定义JAR文件的路径

String jarFilePath = "/path/to/example.jar";

// 创建URLClassLoader,指定JAR文件路径

URL jarFileUrl = new URL("file:" + jarFilePath);

URLClassLoader classLoader = new URLClassLoader(new URL[] { jarFileUrl });

// 加载JAR文件中的类

Class jarClass = classLoader.loadClass("com.example.ExampleClass");

// 使用加载的类

Object instance = jarClass.newInstance();

// 调用类的方法

// ...

}

}

网络资源的路径,示例 import java.net.URL;

import java.net.URLClassLoader;

public class NetworkClassLoaderExample {

public static void main(String[] args) throws Exception {

// 定义远程类文件的URL

URL url = new URL("http://example.com/classes/");

// 创建URLClassLoader,指定远程资源路径

URLClassLoader classLoader = new URLClassLoader(new URL[] { url });

// 加载远程类

Class remoteClass = classLoader.loadClass("com.example.RemoteClass");

// 使用加载的类

Object instance = remoteClass.newInstance();

// 调用类的方法

// ...

}

}

读取: 一旦定位到类文件的位置,类加载器就会读取类文件的字节码数据到内存中。这通常涉及到文件 I/O 操作或者网络请求,以便获取类文件的内容。

示例: import java.io.ByteArrayOutputStream;

import java.io.InputStream;

public class ClassLoaderExample {

public static void main(String[] args) throws Exception {

// 获取类加载器

ClassLoader classLoader = ClassLoaderExample.class.getClassLoader();

// 类文件路径

String className = "com.example.ExampleClass";

String classFilePath = className.replace('.', '/') + ".class";

// 通过类加载器读取类文件的字节码数据

InputStream inputStream = classLoader.getResourceAsStream(classFilePath);

ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

byte[] buffer = new byte[1024];

int bytesRead;

while ((bytesRead = inputStream.read(buffer)) != -1) {

outputStream.write(buffer, 0, bytesRead);

}

byte[] classBytes = outputStream.toByteArray();

// 打印类文件的字节码数据(以16进制字符串形式)

System.out.println(bytesToHex(classBytes));

}

// 辅助方法:将字节数组转换为16进制字符串

private static String bytesToHex(byte[] bytes) {

StringBuilder sb = new StringBuilder();

for (byte b : bytes) {

sb.append(String.format("%02X ", b));

}

return sb.toString();

}

}

定义: 读取到类文件的字节码数据后,类加载器将这些数据转换为 JVM 内部能够识别的数据结构,并创建一个代表该类的 java.lang.Class 对象。这个过程称为类的定义(Define)。 链接: 类加载器在链接阶段会执行三个子阶段:验证(Verification)、准备(Preparation)和解析(Resolution)。验证阶段确保类文件的字节码符合 JVM 规范和安全性要求;准备阶段为类的静态变量分配内存并初始化默认值;解析阶段将符号引用解析为直接引用。

验证(Verification): 确保字节码文件的格式符合Java虚拟机规范,并且不包含安全漏洞。验证阶段会检查字节码的静态结构、类型安全性等。

文件格式验证(File Format Verification): 首先对类文件的字节码格式进行验证,确保其符合Java虚拟机规范,这是加载过程中的第一步。 元数据验证(Metadata Verification): 在文件格式验证通过后,对类文件中的元数据信息进行验证,包括类的继承关系、方法的参数和返回值类型等。 字节码验证(Bytecode Verification): 在元数据验证通过后,对字节码指令序列进行验证,确保其语义合法,不会导致虚拟机执行时出现安全漏洞或错误。 符号引用验证(Symbolic Reference Verification): 在字节码验证通过后,对类文件中的符号引用进行验证,确保其能够正确解析,并指向有效的目标。 访问权限验证(Access Control Verification): 最后对类文件中的访问权限设置进行验证,包括访问私有成员的合法性等。 准备(Preparation): 为类的静态变量分配内存空间,并设置默认初始值。在这个阶段,JVM会为每个类的静态变量分配内存,并初始化为默认值(零值)。 解析(Resolution): 将类、方法、字段等符号引用解析为直接引用。解析阶段会将符号引用替换为直接引用,例如将类名解析为对应的类实例,将方法名解析为方法的入口地址等。 初始化: 在类的初始化阶段,类加载器会执行类的静态代码块和静态变量的赋值操作,完成类的初始化工作。这一阶段通常在类第一次被使用时触发,发生在类加载完成后。 连接: 最后,类加载器将加载的类与已加载的类进行连接,形成类之间的关联关系,以便 JVM 可以正确执行程序中的类和方法调用。 Java 类加载器可以分为三个层次:引导类加载器、扩展类加载器和系统类加载器。它们按照父子关系依次串联,由根加载器(Bootstrap ClassLoader)作为顶层加载器,负责加载 Java 核心类库,而应用程序类加载器(Application ClassLoader)作为子加载器,负责加载应用程序的类。 总之,类加载器是 Java 虚拟机的核心组件之一,它实现了 Java 的动态加载机制,为 Java 程序提供了灵活的类加载和运行时多态性支持。 类加载流程图:

Java 中的类加载器主要分为以下几种类型:

Bootstrap ClassLoader(引导类加载器): 也称为根类加载器,负责加载 Java 核心类库,是 JVM 的一部分,由 C++ 实现。它是所有其他类加载器的父加载器,没有父加载器。 Extension ClassLoader(扩展类加载器): 负责加载 Java 的扩展类库,通常位于 jre/lib/ext 目录下。 Application ClassLoader(应用程序类加载器): 也称为系统类加载器,负责加载应用程序类路径(Classpath)中的类文件。

除了上述标准类加载器,Java 还支持自定义类加载器,通过继承 java.lang.ClassLoader 类并实现自定义加载逻辑,可以实现特定的类加载需求,如动态加载、热部署等。

类加载器在 Java 中具有重要的作用,它使得 Java 程序具有了灵活的动态加载和运行时多态性,并支持了一些高级特性,如反射、代理、SPI(Service Provider Interface)等。

示例:

公共类

public class MyClass {

public String message = "mes";

private String privateField = "privateField";

public int ii = 0;

public void method() {

System.out.println("Hello from MyClass!");

}

public void hello(String str){

System.out.println("hello "+str);

}

public String returnMethod(String str,int i){

System.out.println("returnMethod: "+str+" int:"+i);

return "returnMethod返回参数了";

}

private void privateMethod(){

System.out.println("privateMethod");

}

public void thAddII(){

new Thread(()->{

while (true){

try {

Thread.sleep(100);

ii ++;

} catch (InterruptedException e) {

throw new RuntimeException(e);

}

}

}).start();

}

}

Java自定义类加载器(反射) 类加载:Class clazz = classLoader.loadClass(“jvm.MyClass”); 反射: clazz.getMethod(“method”).invoke(obj); Method method = clazz.getMethod(“hello”, String.class); method.invoke(obj, “World”);

package jvm;

import java.io.ByteArrayOutputStream;

import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.lang.reflect.Field;

import java.lang.reflect.InvocationTargetException;

import java.lang.reflect.Method;

public class CustomClassLoader extends ClassLoader {

private String classPath;

public CustomClassLoader(String classPath) {

this.classPath = classPath;

}

@Override

protected Class findClass(String name) throws ClassNotFoundException {

byte[] classData = loadClassData(name);

if (classData == null) {

throw new ClassNotFoundException();

}

return defineClass(name, classData, 0, classData.length);

}

private byte[] loadClassData(String className) {

String path = classPath + File.separatorChar + className.replace('.', File.separatorChar) + ".class";

try (FileInputStream fis = new FileInputStream(path);

ByteArrayOutputStream bos = new ByteArrayOutputStream()) {

byte[] buffer = new byte[1024];

int bytesRead;

while ((bytesRead = fis.read(buffer)) != -1) {

bos.write(buffer, 0, bytesRead);

}

return bos.toByteArray();

} catch (IOException e) {

e.printStackTrace();

}

return null;

}

public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {

// 实例化自定义类加载器,指定类文件所在的目录

CustomClassLoader classLoader = new CustomClassLoader("path/to/classes");

// 使用自定义类加载器加载指定类

Class clazz = classLoader.loadClass("jvm.MyClass");

// 实例化加载的类

Object obj = clazz.newInstance();

// 调用加载的类的方法

clazz.getMethod("method").invoke(obj);

// 获取方法

Method method = clazz.getMethod("hello", String.class);

// 调用方法

method.invoke(obj, "World");

// 获取字段

Field field = clazz.getField("message");

// 设置字段值

field.set(obj, "Hello World!");

// 获取字段值

String message = (String) field.get(obj);

System.out.println("Message: " + message);

// 获取私有字段

Field privateField = clazz.getDeclaredField("privateField");

// 设置字段可访问

privateField.setAccessible(true);

// 获取私有字段的值

Object fieldValue = privateField.get(obj);

System.out.println("原始私有字段的值: " + fieldValue);

// 修改私有字段的值

privateField.set(obj, "New Value");

// 获取修改后的私有字段的值

Object newFieldValue = privateField.get(obj);

System.out.println("修改后的私有字段的值: " + newFieldValue);

// 获取私有方法

Method privateMethod = clazz.getDeclaredMethod("privateMethod");

// 设置方法可访问

privateMethod.setAccessible(true);

// 调用私有方法

privateMethod.invoke(obj);

// 获取方法对象

Method privateMethodr = clazz.getDeclaredMethod("returnMethod", String.class, int.class);

// 设置方法可访问性

privateMethodr.setAccessible(true);

// 调用方法并传递参数

Object result = privateMethodr.invoke(obj, "Parameter1", 123);

// 获取返回值

System.out.println("Method returned: " + result);

MyClass myClass = new MyClass();

myClass.thAddII();

new Thread(()->{

for (int i = 0; i < 10; i++) {

try {

Thread.sleep(1000);

Field fii = MyClass.class.getField("ii");

// 获取字段值

Object messages = fii.get(myClass);

System.out.println(i+" ii: " + messages);

} catch (InterruptedException e) {

throw new RuntimeException(e);

} catch (NoSuchFieldException e) {

throw new RuntimeException(e);

} catch (IllegalAccessException e) {

throw new RuntimeException(e);

}

}

}).start();

}

}

Bootstrap ClassLoader(引导类加载器) 由于 Bootstrap ClassLoader 是用本地代码实现的,因此在 Java 中无法直接获得它的 Class 对象。但是,我们可以通过一些间接的方式来观察 Bootstrap ClassLoader 的效果。 一种间接的方式是通过查看某个 Java 核心类的类加载器,例如 String 类。由于 String 类属于 Java 核心类库,因此它的类加载器应该是 Bootstrap ClassLoader。我们可以通过以下方式来验证: Copy code

public class BootstrapClassLoaderExample {

public static void main(String[] args) {

// 查看String类的类加载器

ClassLoader stringClassLoader = String.class.getClassLoader();

System.out.println("String class is loaded by: " + stringClassLoader);

// 查看Bootstrap ClassLoader加载的类

URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();

System.out.println("Bootstrap ClassLoader loaded classes:");

for (URL url : urls) {

System.out.println(url);

}

}

}

请注意,sun.misc.Launcher.getBootstrapClassPath().getURLs()方法是 Java 内部 API,可能会在未来的 Java 版本中被移除或更改。但是,它可以用于观察 Bootstrap ClassLoader 加载的类路径。 Extension ClassLoader(扩展类加载器) Extension ClassLoader 的工作流程类似于 Bootstrap ClassLoader,它也是通过双亲委派模型来加载类。当加载一个类时,Extension ClassLoader 首先会检查自己是否能够加载该类,如果不能,则会委托给父类加载器 Bootstrap ClassLoader 加载。

以下是 Extension ClassLoader 的一个简单示例,展示了如何获取 Extension ClassLoader 对象以及它加载的类:

Copy code

public class ExtensionClassLoaderExample {

public static void main(String[] args) {

// 获取 Extension ClassLoader 对象

ClassLoader extensionClassLoader = ClassLoader.getSystemClassLoader().getParent();

System.out.println("Extension ClassLoader: " + extensionClassLoader);

// 查看 Extension ClassLoader 加载的类路径

String extDirs = System.getProperty("java.ext.dirs");

System.out.println("Extension ClassLoader loaded classes:");

for (String extDir : extDirs.split(File.pathSeparator)) {

File dir = new File(extDir);

if (dir.isDirectory()) {

File[] files = dir.listFiles();

if (files != null) {

for (File file : files) {

System.out.println(file.getName());

}

}

}

}

}

}

请注意,由于 Extension ClassLoader 是 Java 内部 API 的一部分,因此有可能在未来的 Java 版本中发生变化。

Application ClassLoader(应用类加载器) 它的工作流程类似于 Extension ClassLoader 和 Bootstrap ClassLoader,也是通过双亲委派模型来加载类。当加载一个类时,Application ClassLoader 首先会检查自己是否能够加载该类,如果不能,则会委托给父类加载器进行加载。 以下是 Application ClassLoader 的一个简单示例,展示了如何获取 Application ClassLoader 对象以及它加载的类:Copy code

public class ApplicationClassLoaderExample {

public static void main(String[] args) {

// 获取 Application ClassLoader 对象

ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();

System.out.println("Application ClassLoader: " + appClassLoader);

// 查看 Application ClassLoader 加载的类路径

String classpath = System.getProperty("java.class.path");

System.out.println("Application ClassLoader loaded classes:");

for (String path : classpath.split(File.pathSeparator)) {

System.out.println(path);

}

}

}

在上面的示例中,我们通过 ClassLoader.getSystemClassLoader() 方法获取了 Application ClassLoader 对象,并通过 System.getProperty(“java.class.path”) 获取了应用程序类路径,并打印了出来。 请注意,Application ClassLoader 加载的类通常是我们自己编写的应用程序类,如 Main 类和其他自定义类。

文章来源

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: