十、来写一个自定义类加载器
在写代码之前,先看一下类加载loadClass的源码:
Class<?> clazz = loader1.loadClass("com.lxl.jvm.MyTest01");
还是调用自己的loadClass,第二个参数resolve :If <tt>true</tt> then resolve the class
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
最终的调用:
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
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
其实很好理解,先对加载同一个类进行同步,再看是否已经加载过这个类findLoadedClass。
如果加载过就返回类,如果resolve为true,就调用native方法进行resolveClass,这个就暂时忽略。
如果没有加载过类,就会调用父加载器的loadClass,也就是第八篇文章中的双亲委托模型。继续加载。
一直到父加载器为根加载器为止,才开始调用findClass进行类加载。
调用父加载器的loadClass后,如果还没加载到就自己加载,就是有父加载器,父加载器加载。父加载器不行,再自己上~就是这么简单。
另外重点说的是,父加载器和父类不是同一个概念!!!自己甚至可以成为自己的父加载器,没有类的继承关系!
***这个findClass也是我们这回的重点,我们自定义类加载器重新的就是这个方法。代码如下
public class MyTest16 extends ClassLoader{
private String classLoaderName;
private final String fileExtension = ".class";
private final String filePath = "C:\\Users\\l8934\\Desktop\\";
public MyTest16(String classLoaderName) {
super(); // 将系统类加载器当做该类加载器的父加载器
this.classLoaderName = classLoaderName;
}
public MyTest16(ClassLoader parent ,String classLoaderName) {
super(parent); // 显式指定该类加载器的父加载器
this.classLoaderName = classLoaderName;
}
@Override
public String toString() {
return "Mytest16 [classLoaderName=" + classLoaderName + "]";
}
/**
* 这是查找非classpath下的类(自定义目录),并未打破双亲规则
* @param name
* @return
* @throws ClassNotFoundException
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] classBytes = this.loadClassData(name);//通过class文件获取数组
if(null == classBytes || classBytes.length == 0)
throw new ClassNotFoundException("load this class is failed");
//这样还是加载不到java.*的类,因为在ClassLoader里面是写死的
Class c = AccessController.doPrivileged(new PrivilegedAction<Class>() {
@Override
public Class run() {
return defineClass(name, classBytes, 0, classBytes.length);
}
});
System.out.println("find class successfully");
return c;
}
private byte[] loadClassData(String name) {
InputStream is = null;
byte[] data = null;
ByteArrayOutputStream baos = null;
try {
name = filePath + name.replace(".", "//") + fileExtension;
is = new FileInputStream(new File(name));
baos = new ByteArrayOutputStream();
int ch = 0;
while(-1 != (ch = is.read())) {
baos.write(ch);
}
data = baos.toByteArray();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
baos.close();
is.close();
} catch (Exception e2) {
e2.printStackTrace();
}
}
return data;
}
public static void main(String[] args) throws Exception {
MyTest16 loader1 = new MyTest16("load1");
Class<?> clazz = loader1.loadClass("com.lxl.jvm.MyTest01");
Object object = clazz.newInstance();
System.out.println(object);
System.out.println(object.getClass().getClassLoader());
MyTest16 loader2 = new MyTest16("load2");
Class<?> clazz2 = loader2.loadClass("com.lxl.jvm.MyTest01");
Object object2 = clazz2.newInstance();
System.out.println(object2);
System.out.println(object2.getClass().getClassLoader());
}
}
这里就是我们的自定义类加载器了,我们重写了findClass方法,findClass方法中,调用了我们的loadClassData方法,将class文件转为byte数组返回。不难理解,我们定义了filePath,路径为桌面。我们运行一下~~
结果为:
com.lxl.jvm.MyTest01@15db9742
sun.misc.Launcher$AppClassLoader@73d16e93
com.lxl.jvm.MyTest01@6d06d69c
sun.misc.Launcher$AppClassLoader@73d16e93
0.0~~~~~~~~~~~~~~~~~不错,让你们失望了,依然是应用类加载器!!!原因很简单了,双亲模型帮我们把类给加载了么~
至于为啥,那就是我们idea会将我们工作空间地址配置给应用类加载器,所以我们的代码都是应用类加载器完成的!
解决这个问题有两个办法:
1:打破双亲委托~不让父加载器去加载就行了~~~
2:把我们工作空间的类剪切到桌面去,父加载器加载不到就只能自定义类加载器去加载了!!!
这里我选择第二种来演示一下,剪切后的运行结果:
find class successfully
com.lxl.jvm.MyTest01@330bedb4
Mytest16 [classLoaderName=load1]
find class successfully
com.lxl.jvm.MyTest01@7ea987ac
Mytest16 [classLoaderName=load2]
舒服~~这回就是我们的自定义类加载器加载的了
另外文章前面说了,idea会将我们工作空间地址配置给应用类加载器。具体每个类加载器加载的地址是什么呢
我们一起在看一下
public class MyTest18 {
public static void main(String[] args) {
System.out.println(System.getProperty("sun.boot.class.path"));
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println(System.getProperty("java.class.path"));
}
}
我这里的输入是
D:\Program Files\Java\jdk1.8.0_111\jre\lib\resources.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\rt.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\sunrsasign.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\jsse.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\jce.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\charsets.jar;D:\Program Files\Java\jdk1.8.0_111\jre\lib\jfr.jar;D:\Program Files\Java\jdk1.8.0_111\jre\classes
D:\Program Files\Java\jdk1.8.0_111\jre\lib\ext;C:\Windows\Sun\Java\lib\ext
E:\workspace\jvm_learning\bin\main;E:\maven\repository\caches\modules-2\files-2.1\commons-collections\commons-collections\3.2\f951934aa5ae5a88d7e6dfaa6d32307d834a88be\commons-collections-3.2.jar;E:\maven\repository\caches\modules-2\files-2.1\cglib\cglib\3.2.8\331c90f8cd3be5b2ee225354bed68862ba0fd3c8\cglib-3.2.8.jar;E:\maven\repository\caches\modules-2\files-2.1\com.google.guava\guava\29.0-jre\801142b4c3d0f0770dd29abea50906cacfddd447\guava-29.0-jre.jar;E:\maven\repository\caches\modules-2\files-2.1\org.ow2.asm\asm\6.2.1\c01b6798f81b0fc2c5faa70cbe468c275d4b50c7\asm-6.2.1.jar;E:\maven\repository\caches\modules-2\files-2.1\org.apache.ant\ant\1.10.3\88becdeb77cdd2457757b7268e1a10666c03d382\ant-1.10.3.jar;E:\maven\repository\caches\modules-2\files-2.1\com.google.guava\failureaccess\1.0.1\1dcf1de382a0bf95a3d8b0849546c88bac1292c9\failureaccess-1.0.1.jar;E:\maven\repository\caches\modules-2\files-2.1\com.google.guava\listenablefuture\9999.0-empty-to-avoid-conflict-with-guava\b421526c5f297295adef1c886e5246c39d4ac629\listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar;E:\maven\repository\caches\modules-2\files-2.1\com.google.code.findbugs\jsr305\3.0.2\25ea2e8b0c338a877313bd4672d3fe056ea78f0d\jsr305-3.0.2.jar;E:\maven\repository\caches\modules-2\files-2.1\org.checkerframework\checker-qual\2.11.1\8c43bf8f99b841d23aadda6044329dad9b63c185\checker-qual-2.11.1.jar;E:\maven\repository\caches\modules-2\files-2.1\com.google.errorprone\error_prone_annotations\2.3.4\dac170e4594de319655ffb62f41cbd6dbb5e601e\error_prone_annotations-2.3.4.jar;E:\maven\repository\caches\modules-2\files-2.1\com.google.j2objc\j2objc-annotations\1.3\ba035118bc8bac37d7eff77700720999acd9986d\j2objc-annotations-1.3.jar;E:\maven\repository\caches\modules-2\files-2.1\org.apache.ant\ant-launcher\1.10.3\9dd5189e7f561ca19833b4e3672720b9bc5cb2fe\ant-launcher-1.10.3.jar
还是非常的长的,但是很明显的就有我的工作空间。可以看下自己的
sun.boot.class.path
java.ext.dirs
java.class.path
分别对应根加载器,扩展类加载器和应用类加载器的地址
JVM的类加载器大概就是这些东西,参考了一些视频与文章,具体哪个就不说了。其实自己已经学完挺久了,感觉以后怕忘了,就随手写了一些。自己看完也很容易再回忆起来、
分了10小篇文章,前几篇都是一个实例加几行注释,这篇稍微多一点。以前感觉jvm离自己挺远的,深入学习之后感觉以前不理解的东西一下子都清晰了很多,可能在工作中写业务代码并没啥用~~但是多了解一些没啥坏处
类加载就结束了,下面会分享jvm字节码相关的一些内容。我也忘得差不多了,就当复习了!