jvm15版本源码阅读之补充java异常探究
首先,欢庆一下jdk16版本在今天的发行.
从jvm的源码可以看到,其大致分为四大系统:加载系统,栈执行系统,线程模型和错误与异常系统.
从使用者的角度来说,错误和异常系统离日常开发最近,所以这里也详细解析一下java中的错误和异常在jvm源码中对应着什么以及是如何处理的.
异常都是由jvm来抛出的,所以在jvm.cpp源码中是寻找抛出异常的出发点,这里已常见的java.lang.NullPointerException为例,来看一下在jvm.cpp中有哪些地方抛出了该异常.这里为什么选NullPointerException也是有原因的,之后会详细的说明.
1 jvm中抛出异常NullPointerException的方法
首先进入jvm.cpp,搜索NullPointerException
搜索完成,发现有8处和NullPointerException有关,这里详细列出这8处位置
1 338行
2 556行
3 1354行
4 3106行
5 3315行
6 3462行
7 3797行
8 3897行
这8处除了556行是注释之外,其余要么是使用了THROW方法,要么是使用了THROW_方法,或者使用了THROW_0方法,首先进入THROW方法.
1.1 THROW方法
注意该方法的输入参数是vmSymbols::java_lang_NullPointerException(),是vmSymbols的模版,进入可知
也就是所有的入口都是已经写好的java类源文件,那么就不难推测,是通过加载类的形式来创建异常的.
进入该方法
可见是Exceptions::_throw_msg的方法,进入该方法
进入new_exception方法
首先就是判断了线程是否已有挂载的异常,之前也分析过,java线程的异常是通过threadshadow挂载上去的.这里只看最后的重要方法new_exception.
此方法传入了thread, name, signature, &args, h_cause, h_loader,和h_protection_domain7个参数,意义也很明显,进入该方法
重点是首先解析异常类,使用了SystemDictionary::resolve_or_fail的这个方法,此方法之前也分析过,就是加载类,这里需要注意的是,从最初THROW的参数是vm的符号模版,可知已经给出了加载类的入口,也就是java类源文件,所以这里加载的也是java/lang/NullPointerException.java文件,
位于src的java.base目录下,是共享的classes源文件.
1.2 THROW_方法
进入该方法,可以看到也是调用的Exceptions::_throw_msg方法,只是参数多了一个result而已.
1.3 THROW_0方法
根据方法名,推测是THROW_的一个参数被直接用0代入了,进入该方法
果然,只是把THROW_方法的result参数直接设为了0而已.
2 jvm中抛出error的分析
和异常exception一样,jvm中抛出的error也是直接加载的类文件来进行挂载的,在符号表中,error表示为
可见,java中的任何error也都是通过加载类来挂在到vm的线程上的,本质就是包装了内核线程,挂载了错误或者异常而已.
3 总结
目前,在cpp中,空指针本身就是一种类型.如果指针的值设定为null或0,也意味着该指针为空,就是不指向任何内存地址而已.Cpp的空指针不是任何错误,java只是在cpp中判断了指针为空,给出了一个异常.
Cpp的标准异常和标准错误不多,有一些主要是和系统调用相关,通常的除0异常在cpp中是不存在的.而jvm则详细的设定了出现在符号表中的异常类,所以jvm的异常通常与cpp的标准异常或错误没有直接的关系,都是由jvm自身结合java类来定义和挂载到内核线程上的.
总结起来jvm的异常,有如下三点:
1 jvm的异常和cpp的标准异常和标准错误关系不大
2 jvm自定义异常,通过挂载到内核线程上进行输出,这种自定义异常在cpp层面也是正常的程序
3 排查jvm的异常通常不会延伸到cpp源码这一层面,因为执行的是字节码,所以如果延伸到cpp这一层,一定是系统调用出了问题,通常的异常在java语言层面就能排除
所以jvm的异常还是比较简单的,有详细的调用栈信息和异常信息,为开发人员排查问题提供了很好的支持.