重温JAVA类加载过程

缕一缕JAVA的类加载过程,附一个近期遇到的案例

一、加载

    1、读取二进制流

    2、将字节流转化为方法区的”运行时数据结构“

    3、生成一个java.lang.Class对象,用来描述这个数据结构

二、连接

    1、验证

        文件格式验证:魔数、版本号、常量类型、编码格式

        元数据验证:父类验证、抽象类验证、各种语言规范验证

        字节码验证:语法规范验证

        符号引用验证:安全性、可达性验证

    2、准备

            静态变量分配内存。

            把静态变量初始化为”零值“。

    3、解析

         把符号引用解析为直接引用

三、初始化

   执行类构造器<clinit>

    <clinit>是由静态变量的赋值动作和静态代码块的语句合并而成,执行顺序根据源文件出现的顺序决定。如果static{}中要访问未定义的静态变量是不行的,但可以赋值。如:

public class Test{

    static{

        i = 123;//可以

        System.out.println(i);//编译错误

     }

        static i;

}

    <clinit>不会显式的调用父类的类构造器,而是由虚拟机保证父类的<clinit>先被调用(extends的父类会被执行,但implements 的接口不会)

类加载的时机

    类第一次被使用的时候会被加载,包括主动使用和被动使用

        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<String, String> 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<String,String> 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.<clinit>(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<String, String> 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<String,String> convertToMap(String cacheSource) {
        return null;
    }
}

使用缓存时直接调用getValueFromCache即可,如果缓存不可用,那也会抛一个异常出来,达到了抛异常的目的。

结论:

    1、虚拟机只在首次主动使用一个类时进行初始化

     2、不要轻易把异常抛给虚拟机来处理,就像工作中别啥事都汇报给领导,尽量自己处理

     3、多读些优秀源码,多涨点经验,代码一定可以写更优雅。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值