面试官问:Java中使用try

您所在的位置:网站首页 javap指令 面试官问:Java中使用try

面试官问:Java中使用try

2024-07-16 05:14:19| 来源: 网络整理| 查看: 265

不知道从何时起,传出了这么一句话:Java中使用try catch 会严重影响性能。

然而,事实真的如此么?我们对try catch 应该畏之如猛虎么?

一、JVM 异常处理逻辑

Java 程序中显式抛出异常由athrow指令支持,除了通过 throw 主动抛出异常外,JVM规范中还规定了许多运行时异常会在检测到异常状况时自动抛出(效果等同athrow), 例如除数为0时就会自动抛出异常,以及大名鼎鼎的 NullPointerException 。

还需要注意的是,JVM 中 异常处理的catch语句不再由字节码指令来实现(很早之前通过 jsr和 ret指令来完成,它们在很早之前的版本里就被舍弃了),现在的JVM通过异常表(Exception table 方法体中能找到其内容)来完成 catch 语句;很多人说try catch 影响性能可能就是因为认识还停留于上古时代。

1、 我们编写如下的类,add方法中计算++x;并捕获异常;

publicclassTestClass{ privatestaticintlen=779; publicintadd(intx){ try{ //若运行时检测到x=0,那么jvm会自动抛出异常,(可以理解成由jvm自己负责athrow指令调用) x=100/x; }catch(Exceptione){ x=100; } returnx; } }

2、 使用javap工具查看上述类的编译后的class文件;

编译 javacTestClass.java 使用javap查看add方法被编译后的机器指令 javap-verboseTestClass.class

忽略常量池等其他信息,下边贴出add 方法编译后的 机器指令集:

publicintadd(int); descriptor:(I)I flags:ACC_PUBLIC Code: stack=2,locals=3,args_size=2 0:bipush100//加载参数100 2:iload_1//将一个int型变量推至栈顶 3:idiv//相除 4:istore_1//除的结果值压入本地变量 5:goto11//跳转到指令:11 8:astore_2//将引用类型值压入本地变量 9:bipush100//将单字节常量推送栈顶 10:istore_1//将int类型值压入本地变量 11:iload_1//int型变量推栈顶 12:ireturn//返回 //注意看from和to以及targer,然后对照着去看上述指令 Exceptiontable: fromtotargettype 058Classjava/lang/Exception LineNumberTable: line6:0 line9:5 line7:8 line8:9 line10:11 StackMapTable:number_of_entries=2 frame_type=72/*same_locals_1_stack_item*/ stack=[classjava/lang/Exception] frame_type=2/*same*/

再来看Exception table:

from=0, to=5。指令 0~5 对应的就是 try 语句包含的内容,而targer = 8 正好对应 catch 语句块内部操作。

个人理解,from 和 to 相当于划分区间,只要在这个区间内抛出了type 所对应的,“java/lang/Exception” 异常(主动athrow 或者 由jvm运行时检测到异常自动抛出),那么就跳转到target 所代表的第八行。

若执行过程中,没有异常,直接从第5条指令跳转到第11条指令后返回,由此可见未发生异常时,所谓的性能损耗几乎不存在;

如果硬是要说的话,用了try catch 编译后指令篇幅变长了;goto 语句跳转会耗费性能,当你写个数百行代码的方法的时候,编译出来成百上千条指令,这时候这句goto的带来的影响显得微乎其微。

如图所示为去掉try catch 后的指令篇幅,几乎等同上述指令的前五条。

综上所述:“Java中使用try catch 会严重影响性能” 是民间说法,它并不成立。如果不信,接着看下面的测试吧。

二、关于JVM的编译优化

其实写出测试用例并不是很难,这里我们需要重点考虑的是编译器的自动优化,是否会因此得到不同的测试结果?

本节会粗略的介绍一些jvm编译器相关的概念,讲它只为更精确的测试结果,通过它我们可以窥探 try catch 是否会影响JVM的编译优化。推荐,公众号Java精选,回复java面试,获取面试资料,支持在线刷题。

前端编译与优化:我们最常见的前端编译器是 javac,它的优化更偏向于代码结构上的优化,它主要是为了提高程序员的编码效率,不怎么关注执行效率优化;例如,数据流和控制流分析、解语法糖等等。

后端编译与优化:后端编译包括 “即时编译[JIT]” 和 “提前编译[AOT]”,区别于前端编译器,它们最终作用体现于运行期,致力于优化从字节码生成本地机器码的过程(它们优化的是代码的执行效率)。

1. 分层编译

PS* JVM 自己根据宿主机决定自己的运行模式, “JVM 运行模式”;[客户端模式-Client、服务端模式-Server],它们代表的是两个不同的即时编译器,C1(Client Compiler) 和 C2 (Server Compiler)。

PS* 分层编译分为:“解释模式”、“编译模式”、“混合模式”;

解释模式下运行时,编译器不介入工作;

编译模式模式下运行,会使用即时编译器优化热点代码,有可选的即时编译器[C1 或 C2];

混合模式为:解释模式和编译模式搭配使用。

如图,我的环境里JVM 运行于 Server 模式,如果使用即时编译,那么就是使用的:C2 即时编译器。

2. 即时编译器

了解如下的几个 概念:

1. 解释模式

它不使用即时编译器进行后端优化

强制虚拟机运行于 “解释模式” -Xint

禁用后台编译 -XX:-BackgroundCompilation

2. 编译模式

即时编译器会在运行时,对生成的本地机器码进行优化,其中重点关照热点代码。

强制虚拟机运行于"编译模式" -Xcomp 方法调用次数计数器阈值,它是基于计数器热点代码探测依据[Client模式=1500,Server模式=10000] -XX:CompileThreshold=10 关闭方法调用次数热度衰减,使用方法调用计数的绝对值,它搭配上一配置项使用 -XX:-UseCounterDecay 除了热点方法,还有热点回边代码[循环],热点回边代码的阈值计算参考如下: -XX:BackEdgeThreshold=方法计数器阈值[-XX:CompileThreshold]*OSR比率[-XX:OnStackReplacePercentage] OSR比率默认值:Client模式=933,Server模式=140 -XX:OnStackReplacePercentag=100

所谓 “即时”,它是在运行过程中发生的,所以它的缺点也也明显:在运行期间需要耗费资源去做性能分析,也不太适合在运行期间去大刀阔斧的去做一些耗费资源的重负载优化操作。

3. 提前编译器:jaotc

它是后端编译的另一个主角,它有两个发展路线,基于Graal [新时代的主角] 编译器开发,因为本文用的是 C2 编译器,所以只对它做一个了解;

第一条路线:与传统的C、C++编译做的事情类似,在程序运行之前就把程序代码编译成机器码;好处是够快,不占用运行时系统资源,缺点是"启动过程" 会很缓慢;

第二条路线:已知即时编译运行时做性能统计分析占用资源,那么,我们可以把其中一些耗费资源的编译工作,放到提前编译阶段来完成啊,最后在运行时即时编译器再去使用,那么可以大大节省即时编译的开销;这个分支可以把它看作是即时编译缓存;

遗憾的是它只支持 G1 或者 Parallel 垃圾收集器,且只存在JDK 9 以后的版本,暂不需要去关注它;JDK 9 以后的版本可以使用这个参数打印相关信息:[-XX:PrintAOT]。

三、关于测试的约束执行用时统计

System.naoTime() 输出的是过了多少时间[微秒:10的负9次方秒],并不是完全精确的方法执行用时的合计,为了保证结果准确性,测试的运算次数将拉长到百万甚至千万次。

编译器优化的因素

上一节花了一定的篇幅介绍编译器优化,这里我要做的是:对比完全不使用任何编译优化,与使用即时编译时,try catch 对的性能影响。

1、 通过指令禁用JVM的编译优化,让它以最原始的状态运行,然后看有无trycatch的影响;2、 通过指令使用即时编译,尽量做到把后端优化拉满,看看trycatch十有会影响到jvm的编译优化;

关于指令重排序

目前尚未可知 try catch 的使用影响指令重排序;Java技术进阶路线:https://www.yoodb.com/

我们这里的讨论有一个前提,当 try catch 的使用无法避免时,我们应该如何使用 try catch 以应对它可能存在的对指令重排序的影响。

指令重排序发生在多线程并发场景,这么做是为了更好的利用CPU资源,在单线程测试时不需要考虑。不论如何指令重排序,都会保证最终执行结果,与单线程下的执行结果相同;

虽然我们不去测试它,但是也可以进行一些推断,参考 volatile 关键字禁止指令重排序的做法:插入内存屏障;

假定 try catch 存在屏障,导致前后的代码分割;那么最少的try catch代表最少的分割。

所以,是不是会有这样的结论呢:我们把方法体内的 多个 try catch 合并为一个 try catch 是不是反而能减少屏障呢?这么做势必造成 try catch 的范围变大。

当然,上述关于指令重排序讨论内容都是基于个人的猜想,犹未可知 try catch 是否影响指令重排序;本文重点讨论的也只是单线程环境下的 try catch 使用影响性能。

四、测试代码

循环次数为100W ,循环内10次预算[给编译器优化预留优化的可能,这些指令可能被合并];

每个方法都会到达千万次浮点计算。

同样每个方法外层再循环跑多次,最后取其中的众数更有说服力。

publicclassExecuteTryCatch{ //100W privatestaticfinalintTIMES=1000000; privatestaticfinalfloatSTEP_NUM=1f; privatestaticfinalfloatSTART_NUM=Float.MIN_VALUE; publicstaticvoidmain(String[]args){ inttimes=50; ExecuteTryCatchexecuteTryCatch=newExecuteTryCatch(); //每个方法执行50次 while(--times>=0){ System.out.println("times=".concat(String.valueOf(times))); executeTryCatch.executeMillionsEveryTryWithFinally(); executeTryCatch.executeMillionsEveryTry(); executeTryCatch.executeMillionsOneTry(); executeTryCatch.executeMillionsNoneTry(); executeTryCatch.executeMillionsTestReOrder(); } } /** *千万次浮点运算不使用trycatch **/ publicvoidexecuteMillionsNoneTry(){ floatnum=START_NUM; longstart=System.nanoTime(); for(inti=0;i

七、结论

trycatch 不会造成巨大的性能影响,换句话说,我们平时写代码最优先考虑的是程序的健壮性,当然大佬们肯定都知道了怎么合理使用try catch了,但是对萌新来说,你如果不确定,那么你可以使用 try catch;

在未发生异常时,给代码外部包上 try catch,并不会造成影响。

举个栗子吧,我的代码中使用了:URLDecoder.decode,所以必须得捕获异常。

privateintgetThenAddNoJudge(JSONObjectjson,Stringkey){ if(Objects.isNull(json)) thrownewIllegalArgumentException("参数异常"); intnum; try{ //不校验key是否未空值,直接调用toString每次触发空指针异常并被捕获 num=100+Integer.parseInt(URLDecoder.decode(json.get(key).toString(),"UTF-8")); }catch(Exceptione){ num=100; } returnnum; } privateintgetThenAddWithJudge(JSONObjectjson,Stringkey){ if(Objects.isNull(json)) thrownewIllegalArgumentException("参数异常"); intnum; try{ //校验key是否未空值 num=100+Integer.parseInt(URLDecoder.decode(Objects.toString(json.get(key),"0"),"UTF-8")); }catch(Exceptione){ num=100; } returnnum; } publicstaticvoidmain(String[]args){ inttimes=1000000;//百万次 longnao1=System.nanoTime(); ExecuteTryCatchexecuteTryCatch=newExecuteTryCatch(); for(inti=0;i

经过这个例子,我想你知道你该如何 编写你的代码了吧?可怕的不是 try catch 而是 搬砖业务不熟练啊。

作者:bokerr

https://blog.csdn.net/bokerr/article/details/122655795

公众号“Java精选”所发表内容注明来源的,版权归原出处所有(无法查证版权的或者未注明出处的均来自网络,系转载,转载的目的在于传递更多信息,版权属于原作者。如有侵权,请联系,笔者会第一时间删除处理!

最近有很多人问,有没有读者交流群!加入方式很简单,公众号Java精选,回复“加群”,即可入群!

Java精选面试题(微信小程序):3000+道面试题,包含Java基础、并发、JVM、线程、MQ系列、Redis、Spring系列、Elasticsearch、Docker、K8s、Flink、Spark、架构设计等,在线随时刷题!

------ 特别推荐 ------

特别推荐:专注分享最前沿的技术与资讯,为弯道超车做好准备及各种开源项目与高效率软件的公众号,「大咖笔记」,专注挖掘好东西,非常值得大家关注。点击下方公众号卡片关注。

点击“阅读原文”,了解更多精彩内容!

文章有帮助的话,点在看,转发吧!



【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭