面试:什么是类的加载器
类装载工作由ClassLoader及其子类负责,ClassLoader是一个重要的java执行时系统组件,它负责在运行时查找和装入Class字节码文件。
JVM在运行时会产生三个ClassLoader:Bootstrap(启动类加载器)、ExtensionClassLoader(扩展类装载器)和AppClassLoader(系统类装载器)。
上代码:
public class TestClassLoad {
public static void main(String[] args) {
System.out.println("1********启动类加载器*********");
//1.启动类加载器,从sun.boot.class.path系统属性,所指定的目录加载类库
String bootclass= System.getProperty("sun.boot.class.path");
for (String path : bootclass.split(":")){
System.out.println(path);
}
//2.从上面的路径中随意选择一个类, 看看它的类加载器是什么?
//Provider位于 /jdk1.8.0_171.jdk/Contents/Home/jre/lib/jsse.jar 下
ClassLoader classLoader = Provider.class.getClassLoader();
System.out.println("启动类加载器的加载类:"+classLoader);//null ,就是加载器取不到,因为顶级启动类加载器为null
System.out.println();
System.out.println("2********拓展类加载器********");
//1.拓展类加载器,从java.ext.dirs系统属性,所指定的目录加载类库
String extDirs = System.getProperty("java.ext.dirs");
for (String path : extDirs.split(":")){
System.out.println(path);
}
//2.从上面的路径中随意选择一个类, 看看它的类加载器是什么?
ClassLoader classLoader1 = CurveDB.class.getClassLoader();
System.out.println("拓展类加载器的加载类:"+classLoader1);//ExtClassLoader
System.out.println();
System.out.println("3********应用程序类加载器********");
//1.应用程序类加载器,从java.class.path系统属性,所指定的目录加载类库
String classpath= System.getProperty("java.class.path");
for (String path : classpath.split(":")){
System.out.println(path);
}
//2.从上面的路径中随意选择一个类, 看看它的类加载器是什么?
ClassLoader classLoader2 = TestClassLoad.class.getClassLoader();
System.out.println("应用程序类加载器的加载类:"+classLoader2);//AppClassLoader
}
}
面试:为什么需要自定义类加载器?什么情况下用到
用户在需要的情况下,可以实现自己的自定义类加载器,一般而言,在以下几种情况需要自定义类加载器:
- 隔离加载类。某些框架为了实现中间件和应用程序的模块的隔离,就需要中间件和引用程序使用不同的类加载器;
- 修改类加载的方式。类的双亲委派模型并不是强制的,用户可以根据需要在某个时间点动态加载类;
- 扩展类加载源,例如从数据库、网络进行类加载
- 防止源代码泄露。Java代码很容易被反编译和篡改,为了防止源码泄露,可以对类的字节码文件进行加密,并编写自定义的类加载器来加载自己的应用程序的类。
如何实现自定义类加载器?
若要自己实现一个类加载器,只需要继承java.lang.ClassLoader类,并且重写其findClass()方法即可。
java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中生成一个java.lang.Class实例。
ClassLoader的核心方法如下:
- getParent()返回该类加载器的父类加载器
- loadClass(String name) 加载名称为 二进制名称为name的类,返回的结果是java.lang.Class类的实例。
- findClass(String name)查找名称为 name的类,返回的结果是java.lang.Class类的实例。
- findLoadedClass(String name)查找名称为name的已经被加载过的类,返回的结果是java.lang.Class类的实例。
talk is cheap,show me the code
自定义类加载器: ```java // // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) //
package com.oyb.jvm.test01.lesson08;
import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException;
public class MyClassLoader extends ClassLoader { private String name; private String path;
public void setPath(String path) {
this.path = path;
}
public MyClassLoader(String name) {
this.name = name;
}
MyClassLoader(ClassLoader parent, String name) {
super(parent);
this.name = name;
}
public Class<?> findClass(String name) {
byte[] data = this.getDatas(name);
return this.defineClass(name, data, 0, data.length);
}
private byte[] getDatas(String name) {
name = name.replace(".", "/");
File file = new File(this.path + name + ".class");
System.out.println("加载二进制文件:" + file.getPath());
FileInputStream in = null;
ByteArrayOutputStream outputStream = null;
if (file.exists()) {
try {
in = new FileInputStream(file);
outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
boolean var6 = false;
int size;
while((size = in.read(buffer)) != -1) {
outputStream.write(buffer, 0, size);
}
} catch (FileNotFoundException var17) {
var17.printStackTrace();
} catch (IOException var18) {
var18.printStackTrace();
} finally {
try {
in.close();
} catch (IOException var16) {
var16.printStackTrace();
}
}
return outputStream.toByteArray();
} else {
return null;
}
}
}
自定义类Hello.class,并编译
```java
package com.oyb.jvm.test01.lesson08;
public class Hello {
public static void main(String[] args){
ClassLoader loader = Hello.class.getClassLoader();
ClassLoader parent = loader.getParent();
ClassLoader parent1 = parent.getParent();
System.out.println("加载器:" + loader);
System.out.println("父类加载器:" + parent);
System.out.println("爷爷加载器:" + parent1);
}
}
如果是maven项目,编译之后在target目录下,有Hello.class文件
定义测试类:
package com.oyb.jvm.test01.lesson08;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ClassLoaderTest {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException {
//新建一个类加载器
MyClassLoader cl = new MyClassLoader("myClassLoader");
//类存放的路径
cl.setPath("E:\\develop\\jvm\\jvm-study\\target\\test-classes\\");
//查找名称为 name 的类,返回的结果是 java.lang.Class 类的实例
Class<?> classTemp = cl.findClass("com.oyb.jvm.test01.lesson08.Hello");
System.out.println("hello的加载器:" + classTemp.getClassLoader());
Method method = classTemp.getDeclaredMethod("main",String[].class);
Object obj = classTemp.newInstance();
String[] strings = {};
method.invoke(obj, (Object)strings);
}
}
面试:为什么需要双亲委派模型?
创建类:
package java.lang;
public class String {
public static void main(String[] args) {
System.out.println("Aaa");
}
}
运行发现:
Error:(40, 20) java: 找不到符号
符号: 方法 replace(java.lang.String,java.lang.String)
位置: 类型为java.lang.String的变量 name
为了解决这个问题,不一致性安全性问题,设计了双亲委派模型
比如,定义了一个类,先在自定义类加载器去自己的内存区域去找,如果找不到,就说明还没有加载,所以要开始加载这个类,但是自定义加载器并不会自己去加载,它先会委派给自己的父加载器去加载,同样的,父类也会交给父类的父类去加载,如果到达了根加载器,根加载器加载失败了,那么就会反馈给子加载器,交给适合加载该类的加载器去加载。
拿我们之前定义的java.lang.String这个类来说,我们定义的类一般都是通过ApplicationClassLoader去加载的,一开始ApplicatonClassLoader先去内存中找,自己是否已经加载过该类了,发现并没有,于是准备去加载,这时候,它会先问问父类加载器有没有加载,就交给扩展类加载器,扩展类加载器并没有加载过,于是扩展类加载器就去跑去问父类加载器,即到达了根加载器,就是Bootstrap加载器,我们知道Bootstrap加载器在JVM启动的时候就会将java.lang下的所有类加载进JVM,所以此时Bootstrap已经加载过java.lang.String了,所以就会抛出下面的异常:
Error:(40, 20) java: 找不到符号
符号: 方法 replace(java.lang.String,java.lang.String)
位置: 类型为java.lang.String的变量 name
错误说明:根类加载器已经加载了系统的java.lang.String