java虚拟机 JVM如何加载 .class 文件&反射
- 1. 原理解析
- 1.1 原理图
- 2. 反射机制
- 2.1 解释
- 2.2 写一个反射的例子
- 3. 类从编译到执行的过程
- 3.1 简述
- 3.2 谈谈ClassLoader
- 3.2.1 概念
- 3.2.2 ClassLoader的种类
- 3.2.3 自定义一个ClassLoader
- 3.2.3 类加载器的双亲委派机制
- 3.2.4 为什么要使用双亲委派机制去加载类
- 3.2.5 类的加载方式
- 3.2.5 loadClass 和 forName 的区别
1. 原理解析
1.1 原理图

- Class Loader:依据特定格式 ,加载class文件到内存
- Execution Engine:对命令进行解析
- Native Interface:融合不同开发语言的原生库为java所用
- Runtime Data Area:JVM内存结构空间模型
解释:jvm首先将符合其格式要求的class文件通过ClassLoader加载到内存中,再由Execution Engine对命令解析class中的字节码,并提交给操作系统去执行
2. 反射机制
2.1 解释
Java反射机制是在运行过程中,对任何一个类,都能知道这个类的所有属性和方法;对于任何一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能成为java语言的反射机制。
2.2 写一个反射的例子
1.首先定义一个实体类
public class Robot {
private String name;
public void sayHello(String hello) {
System.out.println(hello + " " + name);
}
public String throwHello(String tag) {
return "hello " + tag;
}
}2.反射实例
package com.wangjzm.jvm.reflect;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectSample {
public static void main(String[] args) throws Exception {
Class<?> rc = Class.forName("com.wangjzm.jvm.reflect.Robot");
Robot r = (Robot) rc.newInstance();
// 获取类名
System.out.println("Class name is " + rc.getName());
// getDeclaredMethod 能够获取private、public、protected,但是获取不到继承的方法和所实现的接口的方法
Method throwHello = rc.getDeclaredMethod("throwHello", String.class);
throwHello.setAccessible(true);
Object wangjzm = throwHello.invoke(r, "wangjzm");
System.out.println("getHello result is " + wangjzm);
Field name = rc.getDeclaredField("name");
name.setAccessible(true);
name.set(r, "wangjzm");
// getDeclaredMethod 能够获取public,继承的public方法和所实现的接口的public方法,但是获取不到private、protected
Method sayHello = rc.getMethod("sayHello", String.class);
sayHello.invoke(r, "hello");
}
}3. 类从编译到执行的过程
3.1 简述
通过上面反射的例子进行说明:
- 编译器将 Robot.java 源文件编译为 Robot.class 的字节码文件;
- ClassLoader 将字节码转换成JVM中的 Class< Robot >对象
- JVM 利用 Class< Robot > 对象实例化为 Robot 对象
3.2 谈谈ClassLoader
3.2.1 概念
主要工作在Class装载的阶段,主要作用是从系统外部获得Class二进制数据流。它是Java的核心组件,所有的Class都是由ClassLoader进行加载的,ClassLoader负责通过将Class文件里的二进制数据流装载进系统,然后交给Java虚拟机进行连接、初始化操作。
3.2.2 ClassLoader的种类
- BootStrapClassLoader:c++编写,加载核心库java.*
- ExtClassLoader:Java编写,加载扩展库javax.*
- AppClassLoader:Java编写,加载程序所在目录
- 自定义ClassLoader:Java编写,定制化加载
3.2.3 自定义一个ClassLoader
/**
* 主要是继承ClassLoader类并实现findClass方法定义自己的规则
* 可以做的事:字节码增强,对class进行加解密操作、aop实现参考
**/
public class MyClassLoader extends ClassLoader {
private String path;
private String classLoaderName;
public MyClassLoader(String path, String classLoaderName) {
this.path = path;
this.classLoaderName = classLoaderName;
}
//用于寻找类文件
@Override
public Class findClass(String name) {
byte[] b = loadClassData(name);
return defineClass(name, b, 0, b.length);
}
//用于加载类文件
private byte[] loadClassData(String name) {
name = path + name + ".class";
InputStream in = null;
ByteArrayOutputStream out = null;
try {
in = new FileInputStream(new File(name));
out = new ByteArrayOutputStream();
int i = 0;
while ((i = in.read()) != -1) {
out.write(i);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
in.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return out.toByteArray();
}
}3.2.3 类加载器的双亲委派机制

3.2.4 为什么要使用双亲委派机制去加载类
避免多份同样字节码的加载3.2.5 类的加载方式
- 隐式加载:new
- 显示加载:loadClass、forName
3.2.5 loadClass 和 forName 的区别
- 类的装载方式
通过查看Class.forName方法可以发现其中forName0方法调用时传入了一个true,表示进行上图第三步初始化操作
@CallerSensitive
public static Class<?> forName(String className)
throws ClassNotFoundException {
Class<?> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}而ClassLoader的loadClass方法调用的liadClass第二个参数传入false ,并没有进行链接和初始化操作
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}验证方式:可以定义一个实体类,其中加上一段static代码段进行测试
总结: Class.forName得到的Class是已经初始化完成的,而ClassLoader.loadClass得到的class没有进行链接和初始化操作
使用总结:
- ClassLoader.loadClass 使用场景:SpringIoc里面在资源加载器获取要读入的资源及读取一些bean的配置文件时就需要使用ClassLoader.loadClass 进行加载,之所有这样做是为了Lazy Load延迟加载,加快spring的加载速度,将实际的初始化工作留给使用时再进行初始化
- Class.forName使用场景:使用mysql-jdbc包时初始化驱动使用
















