vlambda博客
学习文章列表

03-从JDK源码级别彻底剖析JVM类加载机制

明月望西楼
分享Java后端技术栈,记录自己在学习工作过程中的感悟
3篇原创内容
Official Account

下面让我们实现一个自定义类加载器,实现自定义类加载器的思路如下:


  1. 继承 java.lang.ClassLoader 类(此类也是其它几个类加载器的父类)

  2. 重写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; } @Override 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; } @Override 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; } } @Override 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\myclassloader 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()); // 路径:E:\test1\com\enn\myclassloader MyClassLoader 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