缕一缕JAVA的类加载过程,附一个近期遇到的案例
1、读取二进制流
2、将字节流转化为方法区的”运行时数据结构“
3、生成一个java.lang.Class对象,用来描述这个数据结构
1、验证
文件格式验证:魔数、版本号、常量类型、编码格式
元数据验证:父类验证、抽象类验证、各种语言规范验证
字节码验证:语法规范验证
符号引用验证:安全性、可达性验证
2、准备
静态变量分配内存。
把静态变量初始化为”零值“。
3、解析
把符号引用解析为直接引用
执行类构造器
public class Test{
static{
i = 123;//可以
System.out.println(i);//编译错误
}
static i;
}
类第一次被使用的时候会被加载,包括主动使用和被动使用
1、首次主动使用时会被初始化
1)当遇到new、getstatic、putstatic、invokestatic这4个命令
2)进行反射调用时,比如Class.forname、getMethod、getField
3)初始化子类的时候
4)作为启动类的时候,也就是main方法所在的类
5)MethodHandle的情况
2、被动使用时不会初始化
1)使用父类的字段时,未主动使用子类的字段,不会导致子类的初始化
2)new MyClass[10]时不会导致MyClass的初始化
3)引用常量时不会导致类初始化
4)classLoader.loadClass()不会导致初始化
举例:
public class App {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
System.out.println("开始");
Class> aClass = App.class.getClassLoader().loadClass("com.zxl.test.resource.MyCache");
System.out.println("类已经加载,未初始化");
Method invokeStatic = aClass.getMethod("invokeStatic");
invokeStatic.invoke(null);
}
}
执行结果:
开始
类已经加载,未初始化
类初始化
方法执行
/**
* 利用静态代码块缓存数据,但代码块中可能抛出异常
*/
public class MyCache {
static Map cacheMap = new HashMap<>();
static {
String cacheSource = getCacheResource();
if(cacheSource==null){
throw new IllegalArgumentException("无法初始化CacheMap");
}else{
cacheMap = convertToMap(cacheSource);
}
}
private static String getCacheResource() {
return null;
}
private static Map convertToMap(String cacheSource) {
return null;
}
}
public class App {
public static void main(String[] args) throws InterruptedException {
System.out.println("开始");
try {
System.out.println(MyCache.cacheMap);//首次调用
} catch (Exception e) {
System.out.println("捕获Exception异常");
e.printStackTrace();
}catch (Throwable e) {
System.out.println("捕获Throwable异常");
Thread.sleep(1000);
e.printStackTrace();
}
Thread.sleep(1000);
System.out.println("----------分割线-------------");
try {
System.out.println(MyCache.cacheMap);//二次调用
} catch (Exception e) {
System.out.println("捕获Exception异常");
e.printStackTrace();
} catch (Throwable e) {
System.out.println("捕获Throwable异常");
Thread.sleep(1000);
e.printStackTrace();
}
Thread.sleep(1000);
System.out.println("结束");
}
}
输出:
开始
捕获Throwable异常
java.lang.ExceptionInInitializerError
at com.zxl.test.resource.App.main(App.java:10)
Caused by: java.lang.IllegalArgumentException: 无法初始化CacheMap
at com.zxl.test.resource.MyCache.(MyCache.java:14)
... 1 more
----------分割线-------------
捕获Throwable异常
java.lang.NoClassDefFoundError: Could not initialize class com.zxl.test.resource.MyCache
at com.zxl.test.resource.App.main(App.java:22)
结束
以上输出说明几个问题:
1、异常代码块的初始化异常,会导致类加载失败,以至于会出现java.lang.NoClassDefFoundError
2、在外面捕获异常的时候需要Throwable,这时候出现的是Error而不是Exception
3、首次主动使用类时,会执行静态代码块,如果执行失败,会报出java.lang.ExceptionInInitializerError。而第二次使用时会报出:java.lang.NoClassDefFoundError,这时候不会再执行初始化了,直接说找不到
解决方案:
如果需要静态代码块来进行某写初始化工作,而这些工作可能会抛异常,那么就需要自己再static{}中进行异常捕获。然后维护一个isInitial字段,每次调用时需要检查这个字段,例如:
class MyCache2{
static Map cacheMap = new HashMap<>();
static boolean isInitial;
static {
try {
String cacheSource = getCacheResource();
if(cacheSource==null){
throw new IllegalArgumentException("无法初始化CacheMap");
}else{
cacheMap = convertToMap(cacheSource);
isInitial = true;
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
}
public static String getValueFromCache(String key) {
if (isInitial) {
return cacheMap.get(key);
}
throw new IllegalStateException("缓存暂时不可用");
}
private static String getCacheResource() {
return null;
}
private static Map convertToMap(String cacheSource) {
return null;
}
}
使用缓存时直接调用getValueFromCache即可,如果缓存不可用,那也会抛一个异常出来,达到了抛异常的目的。
1、虚拟机只在首次主动使用一个类时进行初始化
2、不要轻易把异常抛给虚拟机来处理,就像工作中别啥事都汇报给领导,尽量自己处理
3、多读些优秀源码,多涨点经验,代码一定可以写更优雅。