类加载器从是否是双亲委派机制的角度来讲一种加载器是支持双亲委派机制的类加载器系统,一类是破坏双亲委派机制的类加载器。
然后就从这两个角度讲述记录一下对这个类加载器的学习
类加载器的原理
:
- 一个类的加载过程如上图所示,这个类的类加载器加载类的时候会去他的父类询问是否可以加载,父类再往父类走,直到找个可以加载他的父类,返回找个类,找个过程所有的类加载器的命构成了一个
命名空间
,命名空间中的类是唯一的。- 此外寻找父类的过程是通过代码里设计父类实现的类似链表,而不是通过继承实现的父类
- 此外引导类加载器是C++实现的,java中无法访问到,而其他的类都是通过继承Classloader实现的自定义类加载器。
类加载器的代码实现
由上面的原理可以知道,java里的所有类加载器都是通过继承classloder类实现的,这个类是一个抽象类,重要的实现方法为loadclass()方法,该方法系统默认实现好了,也可以继承重写,但是不推荐,下面看一下这个方法的信息
具体说明已经写在注释里
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
}
//父类没有找到,当前类加载器调用findClass亲自加载
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;
}
}
自定义一个类加载器
public class MyClassLoader extends ClassLoader {
private String rootDir;
public MyClassLoader(String rootDir) {
this.rootDir = rootDir;
}
protected Class<?> findClass(String className) throws ClassNotFoundException {
Class clazz = this.findLoadedClass(className);
FileChannel fileChannel = null;
WritableByteChannel outChannel = null;
if (null == clazz) {
try {
String classFile = getClassFile(className);
FileInputStream fis = new FileInputStream(classFile);
fileChannel = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
outChannel = Channels.newChannel(baos);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
while (true) {
int i = fileChannel.read(buffer);
if (i == 0 || i == -1) {
break;
}
buffer.flip();
outChannel.write(buffer);
buffer.clear();
}
byte[] bytes = baos.toByteArray();
clazz = defineClass(className, bytes, 0, bytes.length);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileChannel != null)
fileChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (outChannel != null)
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return clazz;
}
/**
* 类文件的完全路径
*/
private String getClassFile(String className) {
return rootDir + "\\" + className.replace('.', '\\') + ".class";
}
}
代码很长,但是只看核心 clazz = defineClass(className, bytes, 0, bytes.length);
这一句就够了,defineClass通过参数类名和已编译好的class文件的字节流就可以帮我们把class文件加载到内存里了。这个注意类名的寻找是看这个类的package的命名空间的
,该加载器的使用如下:
public class LoopRun {
public static void main(String args[]) {
while (true) {
try {
//1. 创建自定义类加载器的实例
MyClassLoader loader = new MyClassLoader("F:\\BaiduSyncdisk\\WEB\\project_java\\TjavaBase\\src\\main\\java");
//2. 加载指定的类
Class clazz = loader.findClass("src.Tjvm.chapter04.src.com.atguigu.java1.Demo1");
//3. 创建运行时类的实例
Object demo = clazz.newInstance();
//4. 获取运行时类中指定的方法
Method m = clazz.getMethod("hot");
//5. 调用指定的方法
m.invoke(demo);
Thread.sleep(5000);
} catch (Exception e) {
System.out.println("not find");
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
}
使用中loader.findClass的类就是更这个类的包名加类名实现的,这里根据自己项目路径修改
什么情况下需要或者会破坏双亲委派机制
- 自己重写了loadclass方法,没有遵循这个双亲委派机制。
不要改写loadclass方法,要使用自定findclass方法
- 当前的接口是通过引导类加载器隐式加载的,而当前
接口的实现类
无法通过引导类加载器加载。(这里说明的意思就是一个类实现了接口,但是实例化后的类型仍是接口对应的类型)
使用线程上下文类加载器来自定义一个类加载器自定义类的加载,不定义的话默认是应用程序类加载器
- 为了程序的动态性可以实现热插拔的功能,也就是文件更新之后可以立马重新加载使用,而不需要重新启动程序再次加载全部的类
OSGI环境,OSGI框架,这个是需要用这个框架来构建项目的,构建打包后的项目就可以支持热插拔了
通过不停的循环,以及自定义classloader实现模拟热插拔效果,作为对热插拔的小的理解,实际项目中不会这么用的
public class LoopRun {
public static void main(String args[]) {
while (true) {
try {
//1. 创建自定义类加载器的实例
MyClassLoader loader = new MyClassLoader("F:\\BaiduSyncdisk\\WEB\\project_java\\TjavaBase\\src\\main\\java");
//2. 加载指定的类
Class clazz = loader.findClass("src.Tjvm.chapter04.src.com.atguigu.java1.Demo1");
//3. 创建运行时类的实例
Object demo = clazz.newInstance();
//4. 获取运行时类中指定的方法
Method m = clazz.getMethod("hot");
//5. 调用指定的方法
m.invoke(demo);
Thread.sleep(5000);
} catch (Exception e) {
System.out.println("not find");
try {
Thread.sleep(5000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
}
}