问题:

  1. JVM安全点是什么概念?
  2. finally是如何实现的?finally中抛出异常会怎么样?

 

1.JVM安全点是什么概念?

安全点就是某些记录线程此时调用栈、寄存器等一些重要的数据区域里什么地方包含了GC要管理的指针(对象引用),而这些对象引用是通过OopMaps结构进行记录的,可以直接通过对OopMaps结构的访问来获得对象的引用。

安全点意味着在这个点时,所有工作线程的状态是确定的,JVM 就可以安全地执行 GC 。

当JVM发生GC时,正在执行Java code的线程必须全部停下来,才可以进行垃圾回收,也就是所谓的STW(stop the world),但是STW的背后实现原理,比如这些线程如何暂停、又如何恢复,机制是什么等,都需要涉及到一个重要的概念,那就是安全点。

从线程角度看,安全点(safepoint)可以理解成是在代码执行过程中的一些特殊位置,当线程执行到这些位置的时候,说明虚拟机当前的状态是安全的,如果有需要,可以在这个位置暂停。比如发生GC时,需要暂停所有活动线程,但是线程在这个时刻,还没有执行到一个安全点,该线程需要继续执行,直到到达下一个安全点的时候再暂停,等待GC结束。

其实,安全点的本质作用就是保证所有线程都进入安全状态的时候再进行GC操作,以确保不会把还在使用中的对象给回收掉。

 

实现原理:
    当线程访问到被保护的内存地址时,会触发一个SIGSEGV信号,进而触发JVM的signal handler来阻塞这个线程。
安全点位置:
   已经挂起的线程处于安全点(安全区域),如WAIT、BLOCK状态
   被调用的方法执行之前
   循环体进入下一次之前
   执行native code
   线程正在转换状态
如何恢复:
   重新设置pooling page为可读
   设置解释器为ignore_safepoints
   唤醒所有挂起等待的线程
如何诊断安全点影响:
   jdk9以下,jvm参数设置 -XX:+PrintGCApplicationStoppedTime打印系统停止的时间;
   jdk9及以上,jvm参数设置 -Xlog:safepoint打印系统停止的时间。

2.finally是如何实现的?finally中抛出异常会怎么样?

java中的finally不可单独使用,需配合try关键字一起使用,一般是以try...catch...finally或者try...finally的形式使用。

finally中抛出异常会怎么样?

会导致一些关键逻辑无法正常执行,比如资源无法正确释放或者流无法正常关闭等,假如try块或者catch块中有返回值需要返回,finally抛异常还会导致try或catch中的返回值无法正常返回,然后程序就异常中断了。

finally 块中抛出的任何异常都会覆盖掉在其前面由 try 或者 catch 块抛出异常。与包含 return 语句的情形相似。

除必要情况下,应尽量避免在 finally 块中抛异常或者包含 return 语句。

 

finally是如何实现的:

finally实现方式其实是类似于try-catch;java在编译的时候对于try-catch维护一张表,制定从第几行到第几行代码发生什么类型的异常时,跳转到哪一行继续执行;

finally在编辑的时候,就是增加表中两行记录,制定了try代码块和catch代码块中发生任何异常都跳转到finally代码块中;用程序实现类似于:

源代码:

java全局token验证请求拦截_java全局token验证请求拦截

 

 

 

模拟finally的改写代码:

java全局token验证请求拦截_JVM_02

 

 

 

如果我们在finally中抛出异常的话,我们可以看如下示例:

源代码:

 

java全局token验证请求拦截_System_03

 

 

 

javap -v -p Test.class 之后的编译语句:

 

java全局token验证请求拦截_System_04

 

 

继续看编译后的字节码,先看异常表,有3条

第一行表示指令0发生Exception类型异常跳转到指令13。

第二行表示指令0发生任何类型异常(也就是Throwable类型)跳转到指令25,前提是前面没有捕获该异常。

第三行表示指令13~17发生任何类型异常则跳转到指令25。

总结:

1.try...catch是通过异常表实现的。

2.java确保finally一定会被执行,是通过复制finally代码块到每个分支实现的。

3.通常return会被编译成2个指令,当return后还有finally,return就会被编译成4个指令。

4.return后还有finally,return会将返回值存储起来,finally中并不能改变返回值(当返回值是引用类型是另外一种情况,自行研究下)。

5.return后的finally里有return,则finally里的return会结束方法。

6.try中抛出异常后,finally里有return,则方法并不会抛出异常,会正常返回

 

 return 返回值当引用类型和值类型

private static int testReturn() {
    int i = 3;
    try {
        return i;
    } catch (Exception e) {
        return i;
    } finally {
        i++;
        System.out.println("finally 块中 i=" + i);
    }
}
int result = testReturn();

//输出结果 result= 3

System.out.println("result=" + result);


“=”号赋值是常量赋值。但是,方法的存放地址和常量的存放地址是不一样的,方法的存放在方法区的。上面我们把一个方法赋值给一个int型也没有报错。那是因为在声明方法是我们声明了返回值类型。那么编译器就会在代码的最前端预留一段返回值类型的内存。执行return的时候,就会把返回的内容写入到这段内存中 ,在执行了return之后,返回的值已经被写入到那段内存中了,finally再修改i的值,只是修改了后面代码段的i值,对返回段内存没有影响。

private static HashMap<String, String> testObjestReturn() {

    HashMap<String, String> map = new HashMap<>();

    map.put("aa", "bb");

    try {

        map.put("aa", "bb");

        System.out.println("first");

        return map;

    } catch (Exception e) {

        return map;

    } finally {

        map.put("aa", "cc");

        System.out.println("second");

    }

}
Map<String, String> map = testObjestReturn();

//输出结果 map=cc

System.out.println("map=" + map.get("aa"));


当返回值不是基本数据类型的时候,其是指向一段内存的,return将返回段指向一段内存,但是代码段的依然是指向的同一段内存地址,所以当修改它指向内存中的值的时候,其实也就修改了返回段指向内存中的值,所以最终的值改变了。