<返回更多

从异常堆栈信息丢失,探究jvm的性能优化

2020-04-18    
加入收藏

从异常堆栈信息丢失,探究jvm的性能优化

异常堆栈丢失

今天登陆服务器进行例行的检查,发现异常日志文件里有很多nullPointException,只有简单的异常名称,却没有堆栈信息。没有异常堆栈,无法定位错误,也就不能修改了。到网上搜索信息,原来大家也遇到过这样的问题。

正确的解决方法是增加一个VM Options:-XX:-OmitStackTraceInFastThrow。这个参数的好处如下:

“JVM对一些特定的异常类型做了Fast Throw优化,如果检测到在代码里某个位置连续多次抛出同一类型异常的话,C2会决定用Fast Throw方式来抛出异常,而异常Trace即详细的异常栈信息会被清空。这种异常抛出速度非常快,因为不需要在堆里分配内存,也不需要构造完整的异常栈信息。”

这个参数,支持的异常类型如下:

翻阅了大量的关于此问题的文章,介绍信息大同小异。通过这个方案,开启输出空指针错误,很快就定位问题,并解决了。

解决问题后,进行了一番的测试和验证,如下:

问题验证

众多网络文章,都指出在异常出现5000次以上时,才会丢失堆栈信息。按照网络文章的测试代码,在6600多次的时候丢失堆栈信息,但是并不稳定。代码如下:

public class JAVANPE extends Thread {
    private static int count = 0;
    @Override
    public void run() {
        try {
            System.out.println("getSimpleName is:"+this.getClass().getSimpleName() + " execute count:" + (++count));
            String str = null;
            System.out.println(str.length());
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}

public class TestFastThrow {
    public static void main(String[] args) throws InterruptedException {
        JavaNPE javaNPE = new JavaNPE();
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < Integer.MAX_VALUE; i++) {
            executorService.execute(javaNPE);
            //防止打出的日志太快
            Thread.sleep(2);
        }
    }
}

这是一个多线程的程序,写单线程的测试程序,是否可行呢?于是就尝试了第一版,代码如下:

   public static void main(String args[]) {
        for (int i = 0; i < 10000; i++) {
            try {
                String value = null;
                value.substring(0, 100);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

但是,很遗憾,这个程序的异常堆栈信息并不会丢失。程序修改如下:

   public void method2() {
        String value = null;
        value.substring(0, 100);
    }

    public static void main(String args[]) {
        ExceptionTest exceptionTest = new ExceptionTest(1);
        for (int i = 0; i < 10000; i++) {
            try {
                exceptionTest.method2();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

这个程序在第5530~5550次的时候,会丢失异常堆栈信息,是不稳定的。分析上述两段单线程的测试代码,第一段因为异常信息没有到方法的外面,jvm不能追踪到异常堆栈信息,所以并不会起作用。

性能优化明显吗?

首先,在网络上的文章,都着重说C2编译器的优化,并没有说明C1编译器。通过我的实验,C1和C2编译器的默认行为是一样的,都是开启OmitStackTraceInFastThrow的。C1和C2编译器,是指Java及时编译技术(JIT),在启动应用程序是,java -client是启动C1编译器;java -server是启动C2编译器。

这个参数真的有性能优化吗?测试代码没有IO输出,纯测试代码的执行速度,测试代码如下:

    public void testPerformance() {
        long start = System.currentTimeMillis();
        System.out.println("start");
        for (int i = 0; i < 1000 * 1000; i++) {
            try {
                this.method();
            } catch (Exception ex) {

            }
        }
        System.out.println("used " + (System.currentTimeMillis() - start));
    }

    public void method() {
        String value = null;
        value.substring(0, 100);
    }

测试结果如下:

从异常堆栈信息丢失,探究jvm的性能优化

 

测试结果发现,关闭堆栈后性能确实有了很大的提升,在输出堆栈的情况下,性能随着调用次数的增多,呈线性增长。

测试代码中的堆栈信息,只有一层,把调用异常堆栈的深度增加到20,是不是创建的对象而更多,耗时时间更长呢?关闭参数的情况下,确实是,调用1M次,时间增加到由13秒增加到20秒;启用参数的情况下,耗时却从400毫秒减少到150毫秒!

经测试发现:同样都是在5500多次的时候,丢掉异常的堆栈信息。并且20层异常堆栈,执行这5500多次,耗时120毫秒;层数较少的只用了70毫秒。也就是说,堆栈层数越多,丢掉堆栈后的性能越好!具体是什么原因,就需要阅读源代码了!如果你知道,欢迎留言探讨。

 

 

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>