03-从JDK源码级别彻底剖析JVM类加载机制
明月望西楼
分享Java后端技术栈,记录自己在学习工作过程中的感悟
Official Account
下面让我们实现一个自定义类加载器,实现自定义类加载器的思路如下:
继承 java.lang.ClassLoader 类(此类也是其它几个类加载器的父类)
重写findClass方法,这里分为2步:
a. 写一个读取.class文件的方法
b. 返回 defineClass 方法
另外,这个实现思路不是笔者自己臆想出来的,而是参考JDK源码里的注释。
那么,实现代码如下:
public class MyClassLoaderTest {static class MyClassLoader extends ClassLoader{private String classPath;public MyClassLoader(String classPath){this.classPath=classPath;}private byte[] loadClassData(String name) throws IOException {String path = name.replaceAll("\\.", "/");FileInputStream fileInputStream=new FileInputStream(classPath+"/"+path+".class");int len = fileInputStream.available();byte[] bytes=new byte[len];fileInputStream.read(bytes);fileInputStream.close();return bytes;}protected Class<?> findClass(String name) {try {byte[] bytes = loadClassData(name);return defineClass(name,bytes,0,bytes.length);}catch (Exception e){}return null;}}public static void main(String[] args) {try{MyClassLoader myClassLoader=new MyClassLoader("E:/test");Class<?> clazz = myClassLoader.loadClass("com.enn.myclassloader.User1");Object obj = clazz.newInstance();Method method = clazz.getDeclaredMethod("sout");method.invoke(obj,null);System.out.println(clazz.getClassLoader());}catch (Exception e){}}}
在E盘创建路径 E:\test\com\enn\myclassloader,将User.java的复制类User1.java放到此路径,并执行javac生成User1.class文件:
E:\test\com\enn\myclassloader>javac User1.java -encoding UTF-8
运行结果如下:
=======User1自己的加载器加载类调用方法=======com.enn.myclassloader.MyClassLoaderTest$MyClassLoader@1540e19d
附User1.java的代码:
package com.enn.myclassloader;import java.io.Serializable;public class User1 implements Serializable {public static final long serialVersionUID = 4992L;private int id;private String name;public User1() {}public User1(int id, String name) {super();this.id = id;this.name = name;}public int getId() {return id;}public void setId(int id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public void sout() {System.out.println("=======User1自己的加载器加载类调用方法=======");}}
打破双亲委派机制
首先梳理一下思路,实现双亲委派机制的核心代码在CloassLoader类的loadClass方法中,那么我们肯定需要重写loadClass方法,在此方法的核心代码位置做改造。话不多说,上代码:
public class MyClassLoaderTest {static class MyClassLoader extends ClassLoader{private String classPath;public MyClassLoader(String classPath){this.classPath=classPath;}private byte[] loadClassData(String name) throws IOException {String path = name.replaceAll("\\.", "/");FileInputStream fileInputStream=new FileInputStream(classPath+"/"+path+".class");int len = fileInputStream.available();byte[] bytes=new byte[len];fileInputStream.read(bytes);fileInputStream.close();return bytes;}public Class<?> loadClass(String name,boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {Class<?> c = findLoadedClass(name);if (c == null) {long t1 = System.nanoTime();// 在此处做判断,如果是需要打破双亲委派机制进行加载的类,// 那么直接调用本类的findClass方法进行加载,// 其它类走双亲委派机制if(name.startsWith("com.enn.myclassloader.User1")){c = findClass(name);}else {c = this.getParent().loadClass(name);}sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);sun.misc.PerfCounter.getFindClasses().increment();}if (resolve) {resolveClass(c);}return c;}}protected Class<?> findClass(String name) {try {byte[] bytes = loadClassData(name);return defineClass(name,bytes,0,bytes.length);}catch (Exception e){}return null;}}public static void main(String[] args) {try{// 路径:E:\test\com\enn\myclassloaderMyClassLoader myClassLoader=new MyClassLoader("E:/test");Class<?> clazz = myClassLoader.loadClass("com.enn.myclassloader.User1");Object obj = clazz.newInstance();Method method = clazz.getDeclaredMethod("sout");method.invoke(obj,null);System.out.println(clazz.getClassLoader());// 路径:E:\test1\com\enn\myclassloaderMyClassLoader classLoader=new MyClassLoader("E:/test1");Class<?> clazz2 = classLoader.loadClass("com.enn.myclassloader.User1");Object obj2 = clazz2.newInstance();Method method2 = clazz2.getDeclaredMethod("sout");method2.invoke(obj2,null);System.out.println(clazz2.getClassLoader());}catch (Exception e){}}}
把User1.java分别放在 test 和 test1 两个路径下边,在User1.java的sout方法里,当在test路径下时,打印=======test=======, 在test1路径下时,打印=======test1=====,然后在2个目录各自编译成User1.class文件。
程序运行结果如下
=======test=======com.enn.myclassloader.MyClassLoaderTest$MyClassLoader@1540e19d=======test1=====com.enn.myclassloader.MyClassLoaderTest$MyClassLoader@7f31245a
