1. class装载验证流程
1.1. 加载 装载类的第一个阶段, 取得类的二进制流,转为方法区数据结构,在Java堆中生成对应的java.lang.Class对象
1.2. 链接
-
1.2.1 验证
保证Class流的格式是正确的
- 文件格式验证
- 是否以0xCAFEBABE开头
- 版本号是否合理
- 1.2.2 元数据验证
- 是否有父类
- 继承了final类?
- 非抽象类实现了所有的抽象方法
- 1.2.3 字节码验证 (很复杂)
通过验证的class也不一定是没有问题的
- 运行检查
- 栈数据类型和操作码数据参数吻合
- 跳转指令指定到合理的位置
- 1.2.4 符号引用验证
- 常量池中描述类是否存在
- 访问的方法或字段是否存在且有足够的权限
- 文件格式验证
-
准备
- 分配内存,并为类设置初始值 (方法区中)
- public static int v=1;
- 在准备阶段中,v会被设置为0
- 在初始化的<clinit>中才会被设置为1
- 对于static final类型,在准备阶段就会被赋上正确的值(常量在准备时就会被赋上正确的值)
public static final int v=1;
- 分配内存,并为类设置初始值 (方法区中)
-
解析
- 符号引用
字符串引用对象不一定被加载
替换为直接引用指针或者地址偏移量引用对象一定在内存
- 符号引用
1.3. 初始化
- 执行类构造器<clinit>
- static变量 赋值语句
- static{}语句
- 子类的<clinit>调用前保证父类的<clinit>被调用
- <clinit>是线程安全的
- 思考:Java.lang.NoSuchFieldError错误可能在什么阶段抛出
什么是类装载器
- ClassLoader是一个抽象类
- ClassLoader的实例将读入Java字节码将类装载到JVM中
- ClassLoader可以定制,满足不同的字节码流获取方式
- ClassLoader负责类装载过程中的加载阶段
JDK中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
启动类加载器
Extension ClassLoader
扩展类加载器
App ClassLoader
应用/系统类加载器
Custom ClassLoader
自定义类加载器
启动类加载器没有parent,因为它是最顶层,其他每个ClassLoader都有一个Parent作为父亲
参考
自底向上:检查类是否已被加载
自顶向下:尝试加载类
-Xbootclasspath
可以手动指定 boot classpath
类加载例子:
位于:D:\helloJVM\HelloClassLoader.java:
public class HelloClassLoader {
public void print(){
System.out.println("I am in bootloader");
}
}
应用中的HelloClassLoader.java:
public class HelloClassLoader {
public void print(){
System.out.println("I am in apploader");
}
}
及定位class是被哪个loader定位的应用程序:
public class LookForClassLoader {
public static void main(String[] args) {
HelloClassLoader loader=new HelloClassLoader();
loader.print();
}
-
直接执行main方法(即用app loader来加载 HelloClassLoader )
loader.print()结果.png -
添加参数
-Xbootclasspath/a:D:\helloJVM
,并编译HelloClassLoader.java
。执行main方法
D:\>cd helloJVM
D:\helloJVM>javac HelloClassLoader.java
要记得编译哦.png
loader.print()结果
- I am in apploader 在classpath中却没有被加载,说明类的加载是从上往下
- 保持2的条件不变,强制在app 类加载器中加载classPath中的 HelloClassLoader
public class LookForClassLoader {
public static void main(String[] args) {
try {
forceLoadInAppLoader();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void forceLoadInAppLoader() throws Exception {
// 强制在apploader中加载 HelloClassLoader
ClassLoader cl = LookForClassLoader.class.getClassLoader();
byte[] bHelloClassLoader = loadClassBytes("jvmstudy.HelloClassLoader");
Method md_defineClass = ClassLoader.class.getDeclaredMethod("defineClass", byte[].class, int.class, int.class);
md_defineClass.setAccessible(true);
md_defineClass.invoke(cl, bHelloClassLoader, 0, bHelloClassLoader.length);
md_defineClass.setAccessible(false);
HelloClassLoader loader = new HelloClassLoader();
System.out.println(loader.getClass().getClassLoader());
loader.print();
}
private static byte[] loadClassBytes(String className) throws IOException {
//获取class文件路径
String classFile = getClassFile(className);
FileInputStream fis = null;
try {
fis = new FileInputStream(classFile);
} catch (FileNotFoundException e) {
System.out.println(e);
return null;
}
byte[] bytes = new byte[fis.available()];
fis.read(bytes);
fis.close();
return bytes;
}
private static String getClassFile(String name) {
StringBuffer sb = new StringBuffer("E:\\myspace\\local-git-hub\\Steping\\src\\");
name = name.replace('.', File.separatorChar) + ".class";
sb.append(File.separatorChar + name);
return sb.toString();
}
}
loader.print()结果.png
- 在查找类的时候,先在底层的Loader查找,是从下往上的。app Loader能找到,就不会去上层加载器加载
即双亲委派模式,但是其问题也就是在于顶层classLoader 无法加载底层classLoader中的类
- Java框架(rt.jar)如何加载应用的类?
在app Loader中包含一个rt接口的实例
javax.xml.parsers包中定义了xml解析的类接口,Service Provider Interface SPI 位于rt.jar 。即接口在启动ClassLoader中,而SPI的实现类,在AppLoader。
Thread. setContextClassLoader() 即上下文加载器
是一个角色
/职责
,任何加载器都可以来承担这个角色,就像班长。
用以解决顶层ClassLoader无法访问底层ClassLoader的类的问题
基本思想是,在顶层ClassLoader中,传入底层ClassLoader的实例
rt.jar 中javax.xml.parsers.FactoryFinder
展示如何在启动类加载器加载AppLoader的类 ,即上下文ClassLoader可以突破双亲模式的局限性
/**
* Attempt to load a class using the class loader supplied. If that fails
* and fall back is enabled, the current (i.e. bootstrap) class loader is
* tried.
*
* If the class loader supplied is <code>null</code>, first try using the
* context class loader followed by the current (i.e. bootstrap) class
* loader.
*
* Use bootstrap classLoader if cl = null and useBSClsLoader is true
*/
static private Class<?> getProviderClass(String className, ClassLoader cl,
boolean doFallback, boolean useBSClsLoader) throws ClassNotFoundException
{
try {
if (cl == null) {
if (useBSClsLoader) {
return Class.forName(className, false, FactoryFinder.class.getClassLoader());
} else {
cl = ss.getContextClassLoader();
if (cl == null) {
throw new ClassNotFoundException();
}
else {
return Class.forName(className, false, cl);
}
}
}
else {
return Class.forName(className, false, cl);
}
}
catch (ClassNotFoundException e1) {
if (doFallback) {
// Use current class loader - should always be bootstrap CL
return Class.forName(className, false, FactoryFinder.class.getClassLoader());
}
else {
throw e1;
}
}
}
打破常规模式
- 双亲模式是默认的模式,但不是必须这么做
- Tomcat的WebappClassLoader 就会先加载自己的Class,找不到再委托parent
- OSGi的ClassLoader形成网状结构,根据需要自由加载Class
破坏双亲模式例子:
OrderClassLoaders
热替换`当一个class被替换后,系统无需重启,替换的类立即生效
`
很不错的Java类加载器(ClassLoader) 推荐
网友评论