Java-JVM-架构-类加载器

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

  1. 类加载器(ClassLoader): 类加载器负责将字节码文件加载到内存中,并生成相应的类对象。JVM 提供了三种类加载器:BootstrapClassLoader、ExtensionClassLoader 和 AppClassLoader,它们分别负责加载 Java 核心类库、扩展类库和应用程序类。

  2. 运行时数据区(Runtime Data Area): 运行时数据区包括方法区、堆、虚拟机栈、本地方法栈和程序计数器等,用于存储程序运行过程中的数据。其中,堆用于存储对象实例,方法区用于存储类信息和静态变量,虚拟机栈用于存储方法调用和局部变量,本地方法栈用于执行本地方法,程序计数器用于记录当前线程执行的字节码指令地址。

  3. 执行引擎(Execution Engine): 执行引擎负责解释和执行字节码指令,将 Java 字节码转换为机器码并执行。JVM 提供了多种执行引擎,包括解释器、即时编译器和混合模式执行引擎,用于提高程序的执行效率。

  4. 本地方法接口(Native Interface): 本地方法接口允许 Java 程序调用本地方法,即由其他语言编写的原生代码。JVM 提供了 JNI(Java Native Interface)作为 Java 程序与本地代码交互的标准接口。

  5. 垃圾回收器(Garbage Collector): 垃圾回收器负责自动管理内存,通过回收不再使用的对象实例来释放内存空间,防止内存泄漏和溢出。JVM 提供了多种垃圾回收算法和垃圾回收器实现,如标记-清除算法、复制算法、标记-整理算法和分代垃圾回收等。

  6. 即时编译器(Just-In-Time Compiler,JIT): 即时编译器将字节码动态编译为本地机器码,以提高程序的执行效率。JIT 编译器可以将频繁执行的字节码指令优化为机器码,减少解释器的执行开销,提高程序的运行速度。

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


类加载器(ClassLoader)

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

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

加载(Loading)

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

    1. 定位: 类加载器首先需要根据类的全限定名(包括包名和类名)来定位类文件的位置。在标准的 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();
                
                // 调用类的方法
                // ...
            }
        }
        
        
    2. 读取: 一旦定位到类文件的位置,类加载器就会读取类文件的字节码数据到内存中。这通常涉及到文件 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();
            }
        }
        
        
    3. 定义: 读取到类文件的字节码数据后,类加载器将这些数据转换为 JVM 内部能够识别的数据结构,并创建一个代表该类的 java.lang.Class 对象。这个过程称为类的定义(Define)。

    4. 链接: 类加载器在链接阶段会执行三个子阶段:验证(Verification)、准备(Preparation)和解析(Resolution)。验证阶段确保类文件的字节码符合 JVM 规范和安全性要求;准备阶段为类的静态变量分配内存并初始化默认值;解析阶段将符号引用解析为直接引用。

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

        1. 文件格式验证(File Format Verification): 首先对类文件的字节码格式进行验证,确保其符合Java虚拟机规范,这是加载过程中的第一步。

        2. 元数据验证(Metadata Verification): 在文件格式验证通过后,对类文件中的元数据信息进行验证,包括类的继承关系、方法的参数和返回值类型等。

        3. 字节码验证(Bytecode Verification): 在元数据验证通过后,对字节码指令序列进行验证,确保其语义合法,不会导致虚拟机执行时出现安全漏洞或错误。

        4. 符号引用验证(Symbolic Reference Verification): 在字节码验证通过后,对类文件中的符号引用进行验证,确保其能够正确解析,并指向有效的目标。

        5. 访问权限验证(Access Control Verification): 最后对类文件中的访问权限设置进行验证,包括访问私有成员的合法性等。

      • 准备(Preparation): 为类的静态变量分配内存空间,并设置默认初始值。在这个阶段,JVM会为每个类的静态变量分配内存,并初始化为默认值(零值)。

      • 解析(Resolution): 将类、方法、字段等符号引用解析为直接引用。解析阶段会将符号引用替换为直接引用,例如将类名解析为对应的类实例,将方法名解析为方法的入口地址等。

    5. 初始化: 在类的初始化阶段,类加载器会执行类的静态代码块和静态变量的赋值操作,完成类的初始化工作。这一阶段通常在类第一次被使用时触发,发生在类加载完成后。

    6. 连接: 最后,类加载器将加载的类与已加载的类进行连接,形成类之间的关联关系,以便 JVM 可以正确执行程序中的类和方法调用。

    Java 类加载器可以分为三个层次:引导类加载器、扩展类加载器和系统类加载器。它们按照父子关系依次串联,由根加载器(Bootstrap ClassLoader)作为顶层加载器,负责加载 Java 核心类库,而应用程序类加载器(Application ClassLoader)作为子加载器,负责加载应用程序的类。

    总之,类加载器是 Java 虚拟机的核心组件之一,它实现了 Java 的动态加载机制,为 Java 程序提供了灵活的类加载和运行时多态性支持。

    类加载流程图:
    在这里插入图片描述

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

  1. Bootstrap ClassLoader(引导类加载器): 也称为根类加载器,负责加载 Java 核心类库,是 JVM 的一部分,由 C++ 实现。它是所有其他类加载器的父加载器,没有父加载器。

  2. Extension ClassLoader(扩展类加载器): 负责加载 Java 的扩展类库,通常位于 jre/lib/ext 目录下。

  3. 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 类和其他自定义类。

猜你喜欢

转载自blog.csdn.net/qq_32186487/article/details/136384183