JavaSE丨深入剖析:从JVM类加载到反射编程的核心机制

发布于:2025-09-12 ⋅ 阅读:(26) ⋅ 点赞:(0)

一、JVM虚拟机

        JVM(Java Virtual Machine) 是Java平台的核心组件,它提供了跨平台的能力,使得Java程序可以在不同的操作系统上运行。JDK中的JVM负责解释和执行Java字节码文件,同时还提供了内存管理、垃圾回收等功能,使得Java程序能够高效、安全地运行。

JVM内存结构:

类加载器(Class Loader):类加载器负责加载Java字节码文件(.class文件), 并将其转换为可执行的代码。它将类加载到JVM的运行时数据区域中,并解析类的依赖关系

运行时数据区(Runtime Data Area):运行时数据区域是JVM用于存储程序运时数据的区域。它包括以下几个部分:

  • 方法区(Method Area):用于存储类的结构信息、常量池、静态变量等
  • 堆(Heap):用于存储对象实例和数组内存
  • 栈(Stack):也叫做虚拟机栈,方法调用执行、局部变量所需内存由它提供
  • 本地方法栈(Native Method Stack):本地方法栈与虚拟机栈所发挥的作用非常相似, 其区别是虚拟机栈为虚拟机执行Java方法服务, 而本地方法栈则是为虚拟机使用到的本地(Native)方法服务
  • 程序计数器(Program Counter):用于存储当前线程执行的字节码指令的地址

执行引擎(Execution Engine):执行引擎负责执行编译后的字节码指令,将其转换为机器码并执行。它包括解释器和即时编译器(Just-In-Time Compiler, JIT)两个部分,用于提高程序的执行效率。

垃圾回收器(Garbage Collector):垃圾回收器负责自动回收不再使用的对象释放内存空间。它通过标记-清除、复制、标记-整理等算法来进行垃圾回收。

本地方法接口(Native Method Interface):本地方法接口允许Java程序调用本地方法,即使用其他语言编写的代码。

二、类加载

通过上文大家已大致了解JVM内部构成,下面我们来讨论类加载具体细节及JVM详细构成。

JVM架构及执行流程如下:

解释执行

class文件内容,需要交给JVM进行解释执行,简单理解就是JVM解释一行就执行一行代码。所以如果Java代码全是这样的运行方式的话,效率会稍低一 些。

JIT(Just In Time)即时编译

执行代码的另一种方式,JVM可以把Java中的热点代码直接编译成计算机可以运行的二进制指令,这样后续再调用这个热点代码的时候,就可以直接运行编译好的指令,大大提高运行效率。

2.1 类加载器

类加载器可以将编译得到的 .class文件 (存储在磁盘上的物理文件)加载在到内存中。

2.2 加载时机

当第一次使用到某个类时,该类的class文件会被加载到内存方法区。

  • 使用 java 命令来运行某个主类
  • 创建类的实例(对象)
  • 调用类的 static方法
  • 访问类或接口的 static成员 ,或者为该类static成员赋值
  • 初始化某个类时,其父类会被自动加载
  • 使用反射方式(下文讲解)来获取类的字节码对象时,会加载某个类或接口的 class文件

2.3 加载过程

类加载的过程:加载、验证、准备、解析、初始化

具体加载步骤:

类加载小结:

JVM的类加载过程包括加载、验证、准备、解析和初始化等阶段,它们共同完成将Java类加载到内存中,并为类的静态变量分配内存、解析符号引用、执行静态代码块等操作,最终使得类可以被正确地使用和执行。

2.4 加载器分类

JDK8类加载器可以分为以下四类:

  • Bootstrap ClassLoader 根类加载器

也被称为引导类加载器,通常表示为null 。

它是Java虚拟机的一部分,负责加载Java核心类库,比如 rt.jar 等,根加载器是所有类加载器的顶级加载器,它不是一个Java对象,而是由 JVM 实现的一部分。

类一般存在 %JAVA_HOME%\jre\lib\rt.jar 中

  • Extension ClassLoader 扩展类加载器

负责加载Java的扩展类库,也可以通过 java.ext.dirs 系统属性来指定扩展类库的路径

这些类一般存在 %JAVA_HOME%\jre\lib\ext\ 下的jar包中

  • System ClassLoader 系统类加载器

它负责加载应用程序的类,包括用户自定义的类和第三方库等。

它是大多数 Java应用程序默认的类加载器。

系统类加载器的搜索路径包括当前工作目录和CLASSPATH环境变量指定的路径

  • User ClassLoader 自定义类加载器

自定义类加载器可以用于加载特定的类或实现类加载的特殊需求,目前应用很少,可忽略。

2.5 双亲委托

        双亲委托机制是Java类加载器的一种工作机制,通过层级加载和委托父类加载器来加载类,确保类的唯一性、安全性和模块化。

        如果一个类加载器收到类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,最终加载请求会到达顶层的启动类加载器 Bootstrap ClassLoader 。

        如果顶层类加载器可以完成加载任务,则进行class文件类加载,加载成功后返回。如果当前类加载器无法加载,则向下委托给子类加载器,此时子类加载器才会尝试加载,成功则返回,失败则继续往下委托,如果所有的加载器都无法加载该类,则会抛出ClassNotFoundException,这就是双亲委托机制。

三、反射

3.1 反射概述

Java反射机制是指在Java程序在运行状态下,动态地获取、检查和操作类的信息和对象的能力。

反射机制作用:

对于任意一个类,都能够知道这个类的所有属性和方法

对于任意一个对象,都能够调用它的任意一个方法和属性

这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。

当一个类被使用的时候,类加载器会把该类的字节码文件装入内存(类加载), 同时在堆空间创建一个字节码对象( Class 类对象) ,这个对象是Java反射机制的核心,它包含了一个类运行时信息。

3.2 反射核心类

在Java中, Class 类是一个重要的核心类,它用于表示一个类或接口的运行时信息。每个类在Java虚拟机中都有一个对应的 Class 对象,可以通过该对象获取类的构造函数、方法、属性等信息,并且可以进行实例化对象、方法调用和数据成员访问等操作。

Class核心类JavaSE源码:
package java.lang;

// 字节码类
public final class Class<T> implements java.io.Serializable,
        GenericDeclaration,
        Type,
        AnnotatedElement {

    // 省略...

    // 获取类的所有构造方法
    @CallerSensitive
    public Constructor<?>[] getConstructors() throws SecurityException {
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        return copyConstructors(privateGetDeclaredConstructors(true));
    }

    // 获取类的所有数据成员
    @CallerSensitive
    public Field[] getFields() throws SecurityException {
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        return copyFields(privateGetPublicFields(null));
    }

    // 获取类的所有成员方法
    @CallerSensitive
    public Method[] getMethods() throws SecurityException {
        checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
        return copyMethods(privateGetPublicMethods());
    }
}

在Java反射中,Class 、Constructor 、Method 和 Field 是表示类的不同部分的关键类。它们提供了访问和操作类的构造函数、方法和字段的方法。

Class 类:表示一个类的运行时信息。通过 Class 类可以获取类的构造函数、方法和字段等信息。可以使用 Class.forName() 方法获取一个类的 Class 对象,也可以通过对象的 getClass() 方法获取其对应的 class 对象。

Constructor 类:表示一个类的构造函数。通过 Constructor 类可以创建类的实例。可以使用 Class 对象的 getConstructors() 或 getConstructor() 方法获取构造函数的对象。

Method 类:表示一个类的方法。通过 Method 类可以调用类的方法。可以使用 Class 对象的 getMethods() 或 getMethod() 方法获取方法的对象。

Field 类:表示一个类的字段。通过 Field 类可以访问和修改类的字段的值。可以使用 Class 对象的 getFields() 或 getField() 方法获取字段的对象。

3.3 字节码对象

JVM虚拟机对类进行加载时,会在堆空间创建一个字节码对象( Class 类对象) 。

通过该字节码对象程序员可以获取类的构造函数、方法、属性等信息,并且可以进行实例化、方法调用和属性访问等操作。

简单来说:如果要用反射机制,则必须先获取类的字节码对象 ,那么如何获取呢?

获取 Class 对象方式:

1.使用类字面常量: 类名.class

2.Object类中方法: 对象.getClass()

public final native Class getClass();

3.借助Class类中方法:`Class.forName("类的全包名")``

public static Class forName(String className);

案例展示1:

使用前两种方式获取同一个类的字节码对象,并验证一个类的字节码对象是否唯一。

public class Test_Class {
    public static void main(String[] args) {
        String s = "hello";
        //1. 对象.getClass()

        Class<? extends String> c1 = s.getClass();
        //2. 类.class
        Class c2 = String.class;

        System.out.println("c1: " + c1);
        System.out.println("c2: " + c2);

        //验证同一个类的字节码对象是否唯一
        System.out.println("c1 == c2 : " + (c1 == c2));
    }
}

//结果为true
案例展示2:

自定义Student类,然后使用三种方式获取该类的字节码对象,并验证字节码对象是否唯一。

自定义Student类:

public class Student {
    public String id;
    private String name;
    private int age;

    public Student() {}

    private Student(String id) {
        System.out.println("in private Student(id) ...");
        this.id = id;
    }

    public Student(String id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public String getId() {
        return id;
    }

    private void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "Student [id=" + id + ", name=" + name + ", age=" + age + "]";
    }
}

测试类:

//使用3种不同方式,获取自定义类的字节码对象,并验证是否唯一
public class Test_Class {
    public static void main(String[] args) throws Exception {

        //第三种方式获取字节码对象,注意:参数为类的全包名
        Class<?> c1 = Class.forName("com.briup.chap13.bean.Student");

        Class c2 = Student.class;

        Student s = new Student();
        Class<? extends Student> c3 = s.getClass();

        System.out.println("c1: " + c1);
        System.out.println("c1 == c2 : " + (c1 == c2));
        System.out.println("c2 == c3 : " + (c3 == c2));
    }
}

注意事项:一个类的字节码对象,有且只有一个!

4.4 构造方法

通过反射可以获取类的构造方法(含private)对象,并借助其实例化对象。

1)构造器相关方法

2)Constructor类创建对象方法

4.5 成员变量

通过反射可以获取类的所有数据成员(含private)对象,进而实现数据成员值的获取与设置

1)Filed相关方法

2)属性获取及设置方法

4.6 成员方法

通过反射可以获取类里面所有的成员(含私有)方法,并调用。

1)Method获取相关方法

2)Method对象调用方法

4.7 综合案例

现有一个集合定义如下:

List list = new ArrayList<>();

要求,往list集合中添加元素:"hello"、123、3.14 请编码实现。

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class Test047_Question {
    public static void main(String[] args) throws Exception {
        List<Integer> list = new ArrayList<>();

        //1.获取字节码对象
        Class<? extends List> clazz = list.getClass();

        //2.获取add方法
        Method m = clazz.getDeclaredMethod("add", Object.class);

        //3.设置可以访问,添加元素
        m.setAccessible(true);
        m.invoke(list, "hello");
        m.invoke(list, 123);
        m.invoke(list, 3.14);

        //4.遍历集合
        Iterator<Integer> it = list.iterator();
        while(it.hasNext())
            System.out.println(it.next());
    }
}

注意:泛型只在编译阶段做语法检查,运行期间会被自动忽略


网站公告

今日签到

点亮在社区的每一天
去签到