• 技术问答集录(七)(JVM安全点,finally)


    问题:

    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代码块中;用程序实现类似于:

    源代码:

    模拟finally的改写代码:

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

    源代码:

     

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

     

    继续看编译后的字节码,先看异常表,有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);
    View Code

    “=”号赋值是常量赋值。但是,方法的存放地址和常量的存放地址是不一样的,方法的存放在方法区的。上面我们把一个方法赋值给一个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"));
    View Code

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

  • 相关阅读:
    树链剖分学习笔记
    [bzoj4445] [SCOI2015]小凸想跑步 (半平面交)
    上下界网络流学习笔记
    LocalMaxima_NOI导刊2009提高(1)(欧拉-马斯刻若尼常数)
    公告
    构造函数
    矩阵线段树
    [SCOI2005]互不侵犯
    牛客网NOIP赛前集训营-提高组(第四场)B区间
    noip提高组模拟赛(QBXT)T2
  • 原文地址:https://www.cnblogs.com/fanBlog/p/13653309.html
Copyright © 2020-2023  润新知