背景
之前在集成第三方即时通信系统-融云的时候,我直接clone它的服务端源码,然后导入我的项目,我在测试它连接融云服务器案例时,发现一直不成功,始终报一个 ExceptionInInitializerError 的异常。后来通过网上查资料才发现,这个异常是静态变量初始化时出现异常时,JVM会抛出java.lang.ExceptionInInitializerError的异常。由此,我对这个异常做了进一步探究。
抛出ExceptionInInitializerError异常的原因
因为这个异常时在静态变量初始化发生异常时抛出的,所以首先我们了解一下静态变量初始化的问题。
静态变量初始化
提到静态变量初始化,又不得不提JVM的类加载机制,把描述类的数据从class文件加载到内存,并对数据进行校验,转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。
类加载的生命周期包括:加载,验证,准备,解析,初始化,使用和卸载这7个阶段。静态变量的初始化相关操作主要在准备和初始化阶段。
准备阶段
对于我们的静态变量来说,首先在准备阶段进行类变量内存的分配以及设置类变量初始值,(类变量是指被static修饰的变量),例如:
1 | public static int value = 123. |
初始化阶段
初始化阶段是执行类构造器clinit()方法的过程:
- clinit方法是有编译器自动收集类中的所有类变量(类变量就是静态变量)的赋值动作和静态语句块(static{})块中的语句合并产生的,编译器收集顺序是 由语句在源文件中出现的顺序所决定的,静态语句块只能访问到定义在静态语句块之前的变量。
- clinit()方法不需要显式调用父类构造器,虚拟机会保证在子类clinit()方法执行之前,父类的clinit()方法已经执行完毕。所以虚拟机第一个执行的肯定是java.lang.object.
- 由于父类的clinit()方法先执行,也就意味着父类中定义的静态语句块要优于子类的变量赋值操作。
- 如果一个类中没有静态语句块和堆变量赋值语句,编译器可以不为这个类生成clinit()方法。
所以由以上可以看出类变量的初始化顺序由它们在源文件中出现的顺序决定的,如果第一行声明了一个静态变量,它在第二行被使用,实际上它却在第三行被初始化,这样的情况就会出现变量初始化异常,具体异常就是NullPointerException异常
引起ExceptionInInitializerError异常的真凶
分析完了静态变量初始化问题,再来看具体引起ExceptionInInitializerError异常产生的原因是什么。在没碰到ExceptionInInitializerError异常的时候,我也不知道具体能引起它的原因有什么,所以我还是利用了搜索引擎Google了一下,发现任何异常都有可能引起ExceptionInInitializerError异常的发生,比如说:java.lang.ArrayIndexOutOfBound或者java.lang.NullPointerException。
再来具体看看我项目中的实际情况是什么:
我运行代码发现,控制台打印出了空指针的错误
1 | Exception in thread "main" java.lang.ExceptionInInitializerError |
空指针的错误具体位置是在:
1 | public class JsonUtil { |
即也就是在静态变量初始化的时候抛出的空指针异常。那么为什么会抛出空指针呢。接下来就要说JsonUtil.class.getClassLoader().getResource(“jsonsource”)的问题
class.getClassLoader()解析问题
getClassLoader()获取的是这个 类对象的加载器,只有Class类才有getClassLoader()方法,当一个类被虚拟机加载完毕过后,也就是上面所说的类加载机制,然后会创建一个Class类实例,用于虚拟机对类的管理,所以JsonUtil.class就是获得它的Class类,。
而JsonUtil.class.getClassLoader().getResource(“jsonsource”)就是在JsonUtil当前位置查找“jsonsource”这个资源文件。这是一个相对路径,实际上它返回的是一个URL.
1 | public URL getResource(String name) { |
然后getPath(),就是获取这个URL的路径地址。但是却报出 java.lang.NullPointerException空指针异常。说明资源没找到。想想第三方平台肯定提供的资源的,结果仔细一看,是我没有把它SDK中的resources资源文件导入,所以找不到“jsonsource”资源,所以才报出空指针错误,从而报出ExceptionInInitializerError异常。这才找到了最终的真凶。