文章目录
1 概述
类加载器是jvm执行类加载机制的前提,所有的class 都是由classloader进行加载的,将class信息二进制数据读入jvm内部,并将其转换为java.lang.class对象实例
1.1 命名空间与唯一性
- 唯一性
要由加载它的类加载器和这个类本身一同确认其在java虚拟机中的唯一性
-
命名空间
-
每个类加载器都有自己的命名空间,由该类加载器及所有父类加载器所加载的类组成
-
同一命名空间,不会出现两个相同名字的两个类
-
1.2 类加载器基本特征
-
双亲委派机制
-
可见性,子类加载器器可以访问父类加载器,但是反过来不行
-
单一性,在父类加载器加载过的类型,不会在子加载器重复加载
2 类加载器的分类
2.1 启动类加载器
-
使用c/c++语言实现
-
用于加载java的核心库
-
并不继承java.lang.ClassLoader
-
只加载包名为 java、javax,sun等开头的类
-
加载扩展类和应用程序类加载器,并指定为他们父类加载器
2.3 扩展类加载器
-
java语言编写
-
继承与classloader
-
父类为启动类加载器
-
会从java.ext.dirs系统属性加载类库
2.4 系统类加载器
-
java 语言编写
-
继承于 classLoader
-
父类加载器为扩展类加载器
-
负载加载java.class.path路径下的类库
-
应用程序的类加载器默认是系统类加载器
-
用户自定义类加载器的默认父加载器
2.5 Class.forname()与classLoader.loadClass()
-
class.fornmae 是一个静态方法,发方法将class文件加载到内存同时,会执行类的初始化
-
classloader.loadclass 是一个实例方法,该方法将class文件加载到内存时,并不会执行类的初始化,直到这个类第一次使用时才进行初始化
2.6 classloader 源码解析
-
loadclass
其里面的逻辑就是双亲委派模式的实现 -
findclass
findcalss 是在loadclass方法中被调用的,当loadclass方法中父加载器加载失败后,会调用自己的findclass方法来完成类机制
一般情况下,在自定义类加载器时,会直接覆盖classloader的findclass方法并编写加载规则,取得加载类的字节码后转换成流,然后调用defineclass方法生成类的class对象
3 双亲委派机制
一个类加载器在接到加载类的请求时,它自己不会尝试去加载它,而是把这个请求委托给父类,依次递归,如果父类加载器可以完成就成功返回,只有父类加载器无法完成时,自己才去加载。
流程
3.1 双亲委派机制优势
-
避免类的重复加载
-
保护程序安全,防止核心api被篡改
3.2 双亲委派模式的弊端
委托2过程是单向的,即应用类访问系统类是没问题的,但是系统类访问应用类就会出现问题
结论 jvm没有明确要求类加载器加载机制一定要使用双亲委派模型,只是建议采用这种方式而已
3.3 双亲委派机制加载原理(loadclass)
// 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
}
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
PerfCounter.getParentDelegationTime().addTime(t1 - t0);
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
-
先在当前的加载器的缓存中查找有无目标类,有则返回
-
判断当前加载器的父类加载器是否为空,不为空,则调用parent.loadClass(name,false)接口进行加载
-
如果当前加载器的父类加载器为空,则调用findBootstrap
ClassorNull(name),让引导类加载器进行加载 -
以上3条路径都没成功加载,则调用findClass(name)进行加载
3.4 双亲委派机制的破坏
越基础的类由上层的加载器进行加载,如果有基础类型要回调用户的代码就需要线程上下文类加载器进行破坏双亲,
3.5 代码的热替换
热替换的关键需求在于服务不能中断,修改必须立即表现正在运行的系统上(java实现此功能就是通过classLoader)
3.6 沙箱安全机制
-
保证程序的安全
-
保护java原始的jdk
java安全模型的核心就是java沙箱,沙箱机制就是将java代码限定在jvm特定的运行范围中,并且严格限制代码对本地系统资源的访问
3.7 自定义类加载器
优点
-
隔离加载类
-
修改类加载方式
-
扩展加载原
-
防止源码泄露
3.7.1 实现自定义类加载器
实现方式
- 用户自定义的类加载器都应该继承classloader类
- 自定义classloader子类时,有两种做法
- 重写loadclass方法
- 重写findclass方法 (推荐)
代码实现,重写findclass方法,其实就是将文件转换成字节流,底层还是调用的defineclass
package java01;
import java.io.*;
/**
* @program: jvmDemo
* @description:
* @author: wfg
* @create: 2021-07-14 16:31
*/
public class MyClassLoader extends ClassLoader{
private String path;
public MyClassLoader (String path){
this .path = path;
}
public MyClassLoader(ClassLoader parent,String path){
super(parent);
this.path=path;
}
/**
* @Description: 用于加载二进制数据流
* @Author: wfg
*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
BufferedInputStream br = null;
ByteArrayOutputStream bos =null;
String filename= path+name+".class";
try{
//获取输入字节流
br = new BufferedInputStream(new FileInputStream(filename));
//获取输出字节流
bos = new ByteArrayOutputStream();
int len=-1;
byte[] b= new byte[1024];
while((len=br.read(b))!=-1){
bos.write(b);
}
//获取字节数组
byte[] bytecodes= bos.toByteArray();
//defineclass方法将二进制数据转化为class对象
Class clazz= defineClass(null,bytecodes,0,bytecodes.length);
return clazz;
}
catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if(bos!=null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
测试案例
package java01;
/**
* @program: jvmDemo
* @description:
* @author: wfg
* @create: 2021-07-14 18:46
*/
public class MyClassLoaderTest {
public static void main(String[] args) {
MyClassLoader myClassLoader = new MyClassLoader("f:/");
try {
Class clazz= myClassLoader.findClass("t");
System.out.println("此类加载器为:"+clazz.getClassLoader().getClass().getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}