参考博客
https://www.cnblogs.com/KingIceMou/p/7208319.html
https://blog.csdn.net/BIAOBIAOqi/article/details/6864567
概述
- ExtclassLoader的parent是null
所以在ExtclassLoader中会去调用findBootstrapClassOrNull()
findBootstrapClassOrNull会去调用native Class<?> findBootstrapClass(String name);
到jvm中去查找BootstrapClass,交给Bootstrap来解决 - ClassLoader关键方法
public Class<?> loadClass(String name) throws ClassNotFoundException {}
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{}
protected Class<?> findClass(String name) throws ClassNotFoundException {}
protected final void resolveClass(Class<?> c) {}
1.类加载体系
1.Bootstrap classLoader jvm 虚拟机自供(我们接触不到,也看不到其java文件)
2.ExtclassLoader jdk自供 扩展类加载器主要加载ext包下面的文件
3.Application Classloader jdk自供 系统上下文cass文件项目里面所有的class
4.Custom ClassLoader: 自定义类加载器 通过继承ClassLoader
他们不是通过extends 来指定父类的
通过属性 指针指明父类的
private final ClassLoader parent;
2.loadClass()方法
debug运行发现以下3种方式最终会走到ClassLoader.loadClass()方法内(jvm内部逻辑实现)
public class HelloWorld {
public static void main(String[] args) throws
//方式1
//Class<?> aClass = Class.forName("com.yiwei.model.Anima");
//方式2
Class<Animal> animalClass = Animal.class;
//方式3
//Animal animal = new Animal();
}
}
3.类加载器流程
1 findLoadedClass(name) //从jvm本地classLoader的缓存中找 class
2找不到就用双亲委派机制去加载class //递归调用父类
3父类中都处理不了,再调用自身写好的findClass(name)方法
public abstract class ClassLoader {
xx
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
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();
c = findClass(name);
// this is the defining class loader; record the stats
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
xx
}
4.各个类加载器的加载范围
Bootstrap Loader
负责搜寻JRE所在目录的classes和lib目录下的.jar
ExtClassLoader
负责搜寻JRE所在目录的lib/ext 目录下的classes或.jar
AppClassLoader
搜寻 Classpath中是否有指定的classes并加载
红线框框的是被压缩了,里面指定了appClassloader的加载范围
😄:\Java\jdk1.8.0_212\bin\java.exe
“-javaagent:D:\IntelliJ IDEA 2020.1\lib\idea_rt.jar=64518:D:\IntelliJ IDEA 2020.1\bin” -Dfile.encoding=GBK
-classpath D:\Java\jdk1.8.0_212\jre\lib\charsets.jar;
D:\Java\jdk1.8.0_212\jre\lib\deploy.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\access-bridge-64.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\cldrdata.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\dnsns.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\jaccess.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\jfxrt.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\localedata.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\nashorn.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\sunec.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\sunjce_provider.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\sunmscapi.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\sunpkcs11.jar;
D:\Java\jdk1.8.0_212\jre\lib\ext\zipfs.jar;
D:\Java\jdk1.8.0_212\jre\lib\javaws.jar;
D:\Java\jdk1.8.0_212\jre\lib\jce.jar;
D:\Java\jdk1.8.0_212\jre\lib\jfr.jar;
D:\Java\jdk1.8.0_212\jre\lib\jfxswt.jar;
D:\Java\jdk1.8.0_212\jre\lib\jsse.jar;
D:\Java\jdk1.8.0_212\jre\lib\management-agent.jar;
D:\Java\jdk1.8.0_212\jre\lib\plugin.jar;
D:\Java\jdk1.8.0_212\jre\lib\resources.jar;
D:\Java\jdk1.8.0_212\jre\lib\rt.jar;
E:\servlet\out\production\servlet com.yiwei.HelloWorld
5.类加载流程保证了
- 一个class文件 只会被所有类加载器当中的一个类加载器去加载
BootstrapclassLoader jvm虚拟机自供
ExtclassLoader jdk自供 扩展类加载器主要加载ext包下面的文件
Application Classloader jdk自供 系统上下文cass文件项目里面所有的class
Custom ClassLoader: - 同一个 ClassLoader里面只会存在一个相同class名宇 Class缓存
- 因为双亲委派,所以最优先处理class文件最优先的都是jdk自供的类加载器保证了核心的class都是jdk自己去加载
能够保证保证系统核心的class文件不被改
假设黑客在你的项目里面也写了个java.lang.String类,在equals方法里面写了System.exit
,在加载java.lang.String类的时候,父类加载器会优先加载jdk自身的java.lang.String类
那么黑客写的破坏类String就不会再被加载,破坏代码不会生效的
jvm运行的时候String类还是原生的jdk中的String类
6.自定义类加载器破坏双亲委派
有两种方式
1.可重写findLoadedClass(name); 方法,让子类的缓存永远有返回值,那么就不会再委托
父类去加载class
2.可重写loadClass方法,不到父类中去加载class
7.类加载器使用回顾
public class MyClassLoader1 extends ClassLoader{
/*
1.项目的直接上级默认类加载器为AppClassloader
* */
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);
Class<?> animalClass = systemClassLoader.loadClass("com.yiwei.model.Animal");
System.out.println(animalClass);
System.out.println(animalClass.getClassLoader());
}
}
8.自定义破坏双亲委派版本1
public class Animal {
public void eat(){
System.out.println("吃肉");
}
}
此处会因为preDefineClass()的安全验证失败,
感兴趣的可以去看此方法源码
public class MyClassLoader2 extends ClassLoader{
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
byte[] data1=getbytes("E:\\javaSE\\AboutClassLoader\\target\\classes\\com\\yiwei\\model\\Animal.class");
Class<?> aClass = this.defineClass(name, data1, 0, data1.length);
return aClass;
}
public byte[] getbytes(String path) {
File file = new File(path);
FileInputStream inputStream = null;
byte[] bytes=null;
try {
inputStream = new FileInputStream(file);
bytes = new byte[inputStream.available()];
int read = inputStream.read(bytes);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return bytes;
}
/*
* 1.破坏双亲委派,重写looadClass()方法
* 1.读流需要读class文件的流 而不是java文件的流 所以需要去进行提前编译
* 2.父类的类(object类)没加载的话 会再用当前类加载器去加载object
* 3.在用MyClassLoader2加载Object的时候 会调preDefineClass进行安全验证,会抛出一个安全异常
* */
public static void main(String[] args) throws ClassNotFoundException {
MyClassLoader2 myClassLoader2 = new MyClassLoader2();
Class<?> aClass = myClassLoader2.loadClass("com.yiwei.model.Animal");
ClassLoader classLoader = aClass.getClassLoader();
System.out.println(classLoader);
}
}
Exception in thread “main” java.lang.SecurityException: Prohibited package name: java.lang
at java.base/java.lang.ClassLoader.preDefineClass(ClassLoader.java:889)//加载object类
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1005)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:868)
at com.yiwei.MyClassLoader2.loadClass(MyClassLoader2.java:12)
at java.base/java.lang.ClassLoader.defineClass1(Native Method)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1007)//加载animal
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:868)
at com.yiwei.MyClassLoader2.loadClass(MyClassLoader2.java:12)
at com.yiwei.MyClassLoader2.main(MyClassLoader2.java:37)
9.自定义破坏双亲委派版本2
自己加载不出来Object类,那咱们就拿父加载器来加载Object类
Animal就由自己
public class MyClassLoader31 extends ClassLoader{
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
byte[] data1=getbytes("E:\\javaSE\\AboutClassLoader\\target\\classes\\com\\yiwei\\model\\Animal.class");
Class<?> aClass = null;
try {
aClass = this.defineClass(name, data1, 0, data1.length);
} catch (Exception exception){
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
aClass=systemClassLoader.loadClass(name);
}
return aClass;
}
public byte[] getbytes(String path) {
File file = new File(path);
FileInputStream inputStream = null;
byte[] bytes=null;
try {
inputStream = new FileInputStream(file);
bytes = new byte[inputStream.available()];
int read = inputStream.read(bytes);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return bytes;
}
/*
* 1.破坏双亲委派,重写looadClass()方法
* 1.读流需要读class文件的流 而不是java文件的流 所以需要去进行提前编译
* 2.父类的类(object类)没加载的话 会再用当前类加载器去加载object
* 3.在用MyClassLoader2加载Object的时候 会调preDefineClass进行安全验证,会抛出一个安全异常
* 4.这时候捕获异常 在catch里面用appClassLoader来加载Object类
* */
public static void main(String[] args) throws ClassNotFoundException {
MyClassLoader31 aloader = new MyClassLoader31();
Class<?> aClass = aloader.loadClass("com.yiwei.model.Animal");
ClassLoader paclassloader = aClass.getClassLoader();
System.out.println(paclassloader);
}
}
10.自定义破坏双亲委派版本3+模拟热部署
真正的热部署是需要结合文件改变的监听器实现的,此处写的是模拟
用while(true)间隔时间热加载class文件,模拟热部署实现
public class MyClassLoader4 extends ClassLoader {
//目的 让缓存里面永远能返回一个Class对象 这样就不需要走父类加载器了
//在构造方法里面加载类 loadClass
//项目的根路径
public String rootPath;
//所有需要由我这个类加载器加载的类存在这个集合
public List<String> clazzs;
//classPaths: 需要被热部署的加载器去加载的目录
public MyClassLoader4(String rootPath, String... classPaths) throws Exception {
this.rootPath = rootPath;
this.clazzs = new ArrayList();
for (String classPath : classPaths) {
scanClassPath(new File(classPath));
}
}
//扫描项目里面传进来的一些class
public void scanClassPath(File file) throws Exception {
if (file.isDirectory()){
for (File file1 : file.listFiles()) {
scanClassPath(file1);
}
}else{
String fileName = file.getName();
String filePath = file.getPath();
String endName = fileName.substring(fileName.lastIndexOf(".")+1);
if (endName.equals("class")){
//现在我们加载到的是一个Class文件
//如何吧一个Class文件 加载成一个Class对象????
InputStream inputStream = new FileInputStream(file);
byte[] bytes = new byte[(int) file.length()];
inputStream.read(bytes);
String className = fileNameToClassName(filePath);
//文件名转类名
//类名
super.defineClass(className, bytes, 0, bytes.length);
clazzs.add(className);
//loadClass 是从当前ClassLoader里面去获取一个Class对象
}
}
}
public String fileNameToClassName(String filePath){
//d: //project//com//luban//xxxx
String str1=filePath.replace(rootPath,"");
String className = str1.replaceAll("\\\\",".");
//com.luban.className.class
className = className.substring(1,className.lastIndexOf("."));
return className;
//com.luban.classNamexxxx
}
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> loadClass = findLoadedClass(name);
//第一情况 这个类 不需要由我们加载
//第二种情况 这个类需要由我们加载 但是 确实加载不到
if (loadClass ==null){
if (!clazzs.contains(name)){
loadClass = getSystemClassLoader().loadClass(name);
}else{
throw new ClassNotFoundException("没有加载到类");
}
}
return loadClass;
}
//先做热替换 先加载单个Class
public static void main(String[] args) throws Exception {
String rootPath =
MyClassLoader4.class.getResource("").getPath().replaceAll("%20"," ");
//E:/Myhotdeployment/target/classes/
while (true){
MyClassLoader4 myClassLoader4 = new
MyClassLoader4(rootPath,rootPath+"com");
//该对象是用自定义类加载器反射创建的,热替换会生效
Class<?> aClass = myClassLoader4.loadClass("com.yiwei.model.Animal");
aClass.getMethod("eat").invoke(aClass.newInstance());
//new Test().test();new关键字用的是main的类加载器,热替换不会生效
Thread.sleep(2000);
}
//1.用了while死循环,去刷新每次的MyClassLoader
// MyClassLoader中的class缓存其实是最新的class
// 确保反射创建Test对象的时候,使用的class都是最新的
//2.new Test().xxx
//jvm 用new关键字创建Test对象的时候不会走我们写好的classLoader
//原因全盘委托机制
}
}
注意
启动while循环后,修改Animal类为
public class Animal {
public void eat(){
System.out.println("吃福建人");
}
}
重新编译
这样最新的代码才会编译到target的文件夹下才有最新Animal.class
结果
时间有限,类加载的东西挺多的,这里也没整理全,再再深的也没有去研究,希望有帮到大家