- 一,运行原理
- 二,类加载过程
- 2.1加载 -> 验证 -> 准备 -> 解析 -> 初始化
- 2.2 类加载器和双亲委派模型
- 1,加载器
- 2,Launcher
- 2.3双亲委派机制
- 1,原理图
- 2,源码剖析
- 3,双亲委派机制设计好处
- 2.4 自定义类加载器
- 2.5 打破双亲委派机
1, 用c语言程序启动了一个java虚拟机, 创建引导类加载器(c实现)
2,c语言回去调用java代码去创建JVM的启动器
3,通过Launcher类,作为一个引导类,去加载各个类加载器
4,通过类加载的一系列流程将类加载完毕
加载:将字节码文件从磁盘加载到内存中
验证:校验里面的内容是否符合java虚拟机的规范
准备:将一些静态变量进行一个默认值初始值的赋值
解析:将符号引用转化为直接引用,会将一些静态方法转变为一个内存中的地址,就是说原来只是一个类或者一个方法,后面可以在内存中找到具体的位置。
初始化:会将一些静态变量初始化最终的值,如执行静态代码块
jvm是使用懒加载的方式加载这些class,就是说只有使用到了这个类才会进行加载
可以查看一段案例
package zalei;
public class TestLoad {
static {
System.out.println("*************load TestDynamicLoad************");
}
public static void main(String[] args) {
new A();
System.out.println("*************load test************");
B b = null; //B不会加载,除非这里执行 new B()
//B b2 = new B();
}
}
class A {
static {
System.out.println("*************load A************");
}
public A() {
System.out.println("*************initial A************");
}
}
class B {
static {
System.out.println("*************load B************");
}
public B() {
System.out.println("*************initial B************");
}
}
结果如下,如果new了B的话,也会对B进行加载,优先加载静态块-
*************load TestDynamicLoad************ *************load A************ *************initial A************ *************load test************2.2 类加载器和双亲委派模型 1,加载器
1,引导类加载器:加载jre下面的lib目录下的核心类库
2,扩展类加载器:加载jre下面的lib目录下的扩展目录下的jar包、
3,应用程序类加载器:主要负责加载ClassPath路径下的包,加载自定义的那些类
4,自定义类加载器:负责加载用户自定义路径下的类包
//Launcher的构造方法
public Launcher() {
Launcher.ExtClassLoader var1;
try {
//构造扩展类加载器,在构造的过程中将其父加载器设置为null
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//构造应用类加载器,在构造的过程中将其父加载器设置为ExtClassLoader,
//Launcher的loader属性值是AppClassLoader,我们一般都是用这个类加载器来加载我们自己写的应用程序
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
}-
1,通过c语言会初始化一个Luancher类,会构建一个引导类的加载器(bootStrapClassLoader)
2,如上面代码,Luancher构造方法里面构造了两个类加载构造器,分别是扩展类的加载器ExtClassLoader和应用程序类加载器AppClassLoader
3,先加载扩展类加载器,加载完成之后,再加载应用类加载器,一般通过应用类加载器加载我们自己写的程序
这里面的所有的类加载器都是继承ClassLoader
自定义加载器 —> 应用程序类加载器 —> 扩展类加载器 —> 引导类加载器
如果父类没有加载成功,则再从上往下加载一次
1,首先通过应用程序类加载器加载,判断应用程序加载器加载的类集合里面是否加载过该类
2,如果应用程序加载器没有加载过,则会通过扩展类加载器加载该类,从类集合中判断
3,如果扩展类依旧没有加载过,则会去他的父加载器bootStrap引导类加载器,看类集合中是否加载
4,如果引导类加载器 (根类加载器) 依旧查找失败,则依次返回当前类的类加载器,再加载一次
主要是在加载某个类时,会优先使用父加载器来加载该类,找不到则会继续找上层,如果所有父加载器在自己的加载类路径下都找不到目标类,则在自己的类加载路径中查找并载入目标类。大概意思就是:先找父亲加载,不行再由儿子自己加载
2,源码剖析// 检查当前类加载器是否已经加载了该类
Class> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) { //如果当前加载器父加载器不为空则委托父加载器加载该类
c = parent.loadClass(name, false);
} else { //如果当前加载器父加载器为空则委托引导类加载器加载该类
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
//都会调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类
c = findClass(name);
通过源码也可以知道:
1,首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
2,如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载。
3,如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。ClassLoader的loadClass方法,里面实现了双亲委派机制
1,沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
2,避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性
需要继承ClassLoader类,并且重写里面的findClass()方法。可以在本地磁盘里面创建一个类,如何加载的时候直接通过本地磁盘加载,而不需要使用到那几个类加载器加载,这样就完成了自定义类的加载器
package zalei;
import java.io.FileInputStream;
import java.lang.reflect.Method;
public class MyClassLoaderTest {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
protected Class> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
public static void main(String args[]) throws Exception {
//初始化自定义类加载器,会先初始化父类ClassLoader,其中会把自定义类加载器的父加载器设置为应用程序类加载器AppClassLoader
MyClassLoader classLoader = new MyClassLoader("D:/test");
//D盘创建 test/com/zhenghuisheng/jvm 几级目录,将User类的复制类User.class丢入该目录
//需要创建一个User类在这个路径下
Class clazz = classLoader.loadClass("com.zhenghuisheng.jvm.User");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("sout", null);
method.invoke(obj, null);
System.out.println(clazz.getClassLoader().getClass().getName());
}
}
2.5 打破双亲委派机
应用程序里面有这个类,磁盘里面也有这个自定义的类,直接通过磁盘一次性加载,不需要利用到父类加载器,这样就打破了双亲委派机制。主要就是通过重写里面的findClass()方法,将里面的双亲委派机制的逻辑修改即可。让这个findClass直接找磁盘里面的路径,而不需要再写那些层层找父加载器加载即可。



