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