JavaSE丨深入剖析:从JVM类加载到反射编程的核心机制
一、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 {// 省略...// 获取类的所有构造方法@CallerSensitivepublic Constructor<?>[] getConstructors() throws SecurityException {checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);return copyConstructors(privateGetDeclaredConstructors(true));}// 获取类的所有数据成员@CallerSensitivepublic Field[] getFields() throws SecurityException {checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);return copyFields(privateGetPublicFields(null));}// 获取类的所有成员方法@CallerSensitivepublic 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. 类.classClass 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;}@Overridepublic 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());}
}
注意:泛型只在编译阶段做语法检查,运行期间会被自动忽略