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\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