首页 > IT职业 > IT图书 > 深入JVM内核6 类装载器
装载类的第一个阶段
取得类的二进制流
转为方法区数据结构
在Java堆中生成对应的java.lang.Class对象
目的:保证Class流的格式是正确的
文件格式的验证
是否以0xCAFEBABE开头
版本号是否合理
元数据验证
是否有父类
继承了final类?
非抽象类实现了所有的抽象方法
字节码验证 (很复杂)
运行检查
栈数据类型和操作码数据参数吻合
跳转指令指定到合理的位置
符号引用验证
常量池中描述类是否存在
访问的方法或字段是否存在且有足够的权限
分配内存,并为类设置初始值 (方法区中)
public static int v=1;
在准备阶段中,v会被设置为0
在初始化的<clinit>中才会被设置为1
对于static final类型,在准备阶段就会被赋上正确的值
public static final int v=1;
符号引用替换为直接引用
字符串引用对象不一定被加载
指针或者地址偏移量引用对象一定在内存
执行类构造器<clinit>
static变量 赋值语句
static{}语句
子类的<clinit>调用前保证父类的<clinit>被调用
<clinit>是线程安全的
很显然是在链接的验证阶段的符号引用验证时会抛出这个异常,或者NoSuchMethodError等异常。
ClassLoader是一个抽象类
ClassLoader的实例将读入Java字节码将类装载到JVM中
ClassLoader可以定制,满足不同的字节码流获取方式
ClassLoader负责类加载过程中的加载阶段
public Class<?> loadClass(String name) throws ClassNotFoundException
载入并返回一个Class
protected final Class<?> defineClass(byte[] b, int off, int len)
定义一个类,不公开调用
protected Class<?> findClass(String name) throws ClassNotFoundException
loadClass回调该方法,自定义ClassLoader的推荐做法
protected final Class<?> findLoadedClass(String name)
BootStrap ClassLoader (启动ClassLoader)
Extension ClassLoader (扩展ClassLoader)
App ClassLoader (应用ClassLoader/系统ClassLoader)
Custom ClassLoader(自定义ClassLoader)
每个ClassLoader都有一个Parent作为父亲

路径:
Bootstrap ClassLoader rt.jar /-Xbootclasspath
Extension ClassLoader %JAVA_HOME%/lib/ext/*.jar
App ClassLoader Classpath完全自定义路径
Custom ClassLoader 完全自定义路径
底向上检查类是否被加载,一般情况下,首先先从AppClassLoader中调用findLoadedClass查看是否已经加载,如果没有加载,则会交给父类,ExtensionClassLoader去查看是否加载,还没加载,则再调用其父类,BootstrapClassLoader查看是否已经加载,如果仍然没有,自顶向下尝试加载类,那么从 BootstrapClassLoader到 AppClassLoader依次尝试加载。
从代码上可以很容易看出来,首先先自己查看这个类是否被调用,如果没有找到,则调用父亲的loadClass,直到BootstrapClassLoader(没有父亲)。
我们把这个加载的过程叫做双亲模式。
双亲委托机制的作用是防止系统jar包被本地替换
这种模式下会有一个问题:
顶层ClassLoader,无法加载底层ClassLoader的类
比如:javax.xml.parsers包中定义了xml解析的类接口
Service Provider Interface SPI 位于rt.jar
即接口在启动ClassLoader中。
而SPI的实现类,在AppLoader。
这样就无法用BootstrapClassLoader去加载SPI的实现类。
JDK中提供了一个方法:
Thread. setContextClassLoader()
上下文加载器
是一个角色
用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题
基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例
上下文ClassLoader可以突破双亲模式的局限性
static private Class getProviderClass(String className, ClassLoader cl, boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException{ try { if (cl == null) { if (useBSClsLoader) { return Class.forName(className, true, FactoryFinder.class.getClassLoader());
} else {
cl = ss.getContextClassLoader(); if (cl == null) { throw new ClassNotFoundException();
} else { return cl.loadClass(className); //使用上下文ClassLoader
}
}
} else { return cl.loadClass(className);
}
} catch (ClassNotFoundException e1) { if (doFallback) { // Use current class loader - should always be bootstrap CL
return Class.forName(className, true, FactoryFinder.class.getClassLoader());
}
…..
代码来自于
javax.xml.parsers.FactoryFinder
展示如何在启动类加载器加载AppLoader的类
上下文ClassLoader可以突破双亲模式的局限性
双亲模式是默认的模式,但不是必须这么做
Tomcat的WebappClassLoader 就会先加载自己的Class,找不到再委托parent
OSGi的ClassLoader形成网状结构,根据需要自由加载Class
OrderClassLoader的部分实现
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded
Class re=findClass(name); if(re==null){
System.out.println(“无法载入类:”+name+“ 需要请求父加载器");
return super.loadClass(name,resolve);
}
return re;
}
protected Class<?> findClass(String className) throws ClassNotFoundException {
Class clazz = this.findLoadedClass(className);if (null == clazz) { try {
String classFile = getClassFile(className);
FileInputStream fis = new FileInputStream(classFile);
FileChannel fileC = fis.getChannel();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
WritableByteChannel outC = Channels.newChannel(baos);
ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
省略部分代码
fis.close(); byte[] bytes = baos.toByteArray();
clazz = defineClass(className, bytes, 0, bytes.length);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}return clazz;
}
作者:香沙小熊
链接:https://www.jianshu.com/p/52674b8e7da2
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/2323/viewspace-2821559/ ,如需转载,请注明出处,否则将追究法律责任。