java后端面试题必问的JVM性能调优
目录前言JVM性能调优内存溢出错误堆溢出错误和预判堆溢出的错误虚拟机栈和本地方法栈溢出错误方法区(元数据区)和运行时常量池溢出直接内存区域的溢出实践案例如何正确利用大内存高性能硬件上的程序部署策略如何排查内存溢出错误如何排查系统CPU性能指标异常外部命令导致系统缓慢前言
JVM性能调优是一个很大的话题,很多中小企业的业务规模受限,没有迫切的性能调优需求,但是如果不知道JVM相关的理论知识,写出来的代码或者配置的JVM参数不合理时,就会出现很严重的性能问题,到时候开发就会像热锅上的蚂蚁,等待各方的炙烤。笔者一直在学习JVM相关的理论书籍,看过周志明老师的深入理解Java虚拟机,也学习过葛鸣老师的实战Java虚拟机,但是在实际工作中,只有过寥寥几次的调优经验,几乎无处施展学习到的理论知识,致使知识大部分都存在在笔记和书本中,这次总结面试题,一是希望能够应对性能调优岗位相关的面试;二是希望总结一下具体的实战步骤,并努力吸收书中的实践案例,让自己的经验更丰富一些。JVM性能调优内存溢出错误
学习目的:通过异常信息及时定位到发生内存溢出的运行时数据区域了解什么样的代码会导致内存溢出,防止写出这样的代码出现异常后该如何处理,也就是学习事中的处理手段
内存溢出和内存泄露的区别内存泄露:不该留存在进程中的内存数据,虽然很小,但是在经过多次长期的积累后,会导致内存溢出内存溢出:程序申请内存时,内存不足的现象堆溢出错误和预判堆溢出的错误
如何复现出堆溢出错误?JVM参数部分:最大堆和最小堆设置相同并且设置的比较小,比如只有10M,这样就不会自动扩展堆代码部分:在一个方法中不断地往集合中加入元素
代码实践packageorg。example;importjava。util。ArrayList;importjava。util。List;Xmx10MXms10MXX:HeapDumpOnOutOfMemoryErrorpublicclassApp{staticclassOOMObject{inta1;longb2;floatc2。1f;}publicstaticvoidmain(String〔〕args){ListOOMObjectlistnewArrayList();while(true){list。add(newOOMObject());}}}
正确的出现了我们想要的结果:java。lang。OutOfMemoryError:JavaheapspaceDumpingheaptojavapid24476。hprof。。。Heapdumpfilecreated〔13268403bytesin0。077secs〕Exceptioninthreadmainjava。lang。OutOfMemoryError:Javaheapspaceatjava。util。Arrays。copyOf(Arrays。java:3210)atjava。util。Arrays。copyOf(Arrays。java:3181)atjava。util。ArrayList。grow(ArrayList。java:265)atjava。util。ArrayList。ensureExplicitCapacity(ArrayList。java:239)atjava。util。ArrayList。ensureCapacityInternal(ArrayList。java:231)atjava。util。ArrayList。add(ArrayList。java:462)atorg。example。App。main(App。java:22)Processfinishedwithexitcode1
如果把参数调大,调整20M,那么会报另外的errorjava。lang。OutOfMemoryError:GCoverheadlimitexceededDumpingheaptojavapid8796。hprof。。。Heapdumpfilecreated〔27391983bytesin0。141secs〕Exceptioninthreadmainjava。lang。OutOfMemoryError:GCoverheadlimitexceededatorg。example。App。main(App。java:19)Processfinishedwithexitcode1
这个错误的原因是,JVMGC时间占据了整个运行时间的98,但是回收只得到了2可用的内存,至少出现5次,就会报这个异常。
这个异常是Jdk1。6定义的策略,通过统计GC时间来预测是否要OOM了,提前抛出异常,防止OOM发生。
案例心得:堆内存溢出的识别:java。lang。OutOfMemoryError:Javaheapspace或者java。lang。OutOfMemoryError:GCoverheadlimitexceeded死循环中不断创建对象这种代码应该规避提前设置好自动转储的参数,出现异常能够恢复现场查看问题事后排查思路:先用JvisualVM这样的软件查看具体对象,核查是内存溢出还是内存泄漏,如果确定没有泄露,需要排查堆的参数设置是否合理,从代码上分析对象存活时长比较长是否必要,是否可以优化等等。虚拟机栈和本地方法栈溢出错误
一般我们会遇到两种栈相关的错误:单个线程中,不断的调用方法入栈,当栈深度超过虚拟机所允许的最大深度时,抛出StackOverflowError不断地创建线程,创建线程就需要创建栈,当无法申请到足够的内存,就会报unabletocreatenewnativethread错误
如何复现?JVM参数:Xss128k,每个线程的栈内存大小代码部分:没有出口的递归调用
代码实践Xss128kpublicclassApp{staticintlength0;privatestaticvoidreverse(){length;reverse();}publicstaticvoidmain(String〔〕args){try{reverse();}catch(Throwablee){System。out。println(length:length);throwe;}}}
结果验证:length:1096Exceptioninthreadmainjava。lang。StackOverflowErroratorg。example。App。reverse(App。java:10)atorg。example。App。reverse(App。java:11)atorg。example。App。reverse(App。java:11)atorg。example。App。reverse(App。java:11)太多了,这里只截取部分
关于unabletocreatenewnativethread这个异常,这里就不尝试了,因为可能会导致操作系统假死等问题。
案例心得:栈错误的识别:StackOverflowError或者java。lang。OutOfMemoryError:unabletocreatenewnativethread没有出口的递归调用要避免;默认的JVM栈大小的参数针对一般的方法调用深度是足够的如果必须要创建大量的常驻线程,并且是32位的虚拟机,要测试协调好栈内存和其他内存的大小,防止出现溢出错误事后排查思路:先确定是哪种错误,然后检查递归调用或者检查线程数方法区(元数据区)和运行时常量池溢出方法区和运行时常量池异常
在JDK1。6以及以前的版本中,运行时常量池是放在方法区中的,我们可以通过限制方法区的大小然后增大常量池来模拟溢出。
如何模拟:JDK使用1。6版本,这里注意,要统一idea所有的版本,否则出错具体细节可以参考这里:idea启动时报error:java无效的源发行版11JVM参数:XX:PermSize10MXX:MaxPermSize10M应用代码:使用String。intern方法不断创建新的常量对象到常量池中,并一直用集合保持强引用
代码实践:packageorg。example;importjava。util。ArrayList;importjava。util。List;publicclassApp{publicstaticvoidmain(String〔〕args){inti0;ListlistnewArrayList();while(true){list。add(String。valueOf(i)。intern());}}}
结果:Exceptioninthreadmainjava。lang。OutOfMemoryError:PermGenspaceatjava。lang。String。intern(NativeMethod)atorg。example。App。main(App。javafromInputFileObject:15)Processfinishedwithexitcode1
在JDK1。7以后,常量池就被移动到了堆中,所以如果限制了堆的大小,那么最终会报堆溢出异常或者预判堆异常的错误的。
同样的代码使用JDK1。8版本测试,并指定了堆的最大和初始大小后,果然出现了我预计的异常。参数:XX:PermSize10MXX:MaxPermSize10MXmx10MXms10MExceptioninthreadmainjava。lang。OutOfMemoryError:GCoverheadlimitexceededatjava。lang。Integer。toString(Integer。java:403)atjava。lang。String。valueOf(String。java:3099)atorg。example。App。main(App。java:13)
如果加上不使用预判断限制参数XX:UseGCOverheadLimit,就会直接报堆溢出异常XX:PermSize10MXX:MaxPermSize10MXmx10MXms10MXX:UseGCOverheadLimitExceptioninthreadmainjava。lang。OutOfMemoryError:Javaheapspaceatjava。lang。Integer。toString(Integer。java:401)atjava。lang。String。valueOf(String。java:3099)atorg。example。App。main(App。java:13)
说明,常量池分配在堆中。元数据区异常
JDK1。8之后,元数据区被放在了直接内存中,可以指定下面的参数来模拟溢出情况JVM参数:XX:MetaspaceSize10MXX:MaxMetaspaceSize10MXX:HeapDumpOnOutOfMemoryError代码:通过使用cglib生成大量的动态类
代码实战:
pom文件中添加cglib的引用dependencygroupIdcglibgroupIdcglibartifactIdversion3。2。4versiondependencypackageorg。example;importnet。sf。cglib。proxy。Enhancer;importnet。sf。cglib。proxy。MethodInterceptor;importnet。sf。cglib。proxy。MethodProxy;importjava。lang。reflect。Method;publicclassApp{publicstaticvoidmain(String〔〕args){while(true){EnhancerenhancernewEnhancer();enhancer。setSuperclass(OOMObject。class);enhancer。setUseCache(false);enhancer。setCallback(newMethodInterceptor(){OverridepublicObjectintercept(Objecto,Methodmethod,Object〔〕objects,MethodProxymethodProxy)throwsThrowable{returnmethodProxy。invokeSuper(o,args);}});enhancer。create();}}staticclassOOMObject{}}
运行结果:java。lang。OutOfMemoryError:MetaspaceDumpingheaptojavapid26272。hprof。。。Heapdumpfilecreated〔3395669bytesin0。015secs〕Exceptioninthreadmainnet。sf。cglib。core。CodeGenerationException:java。lang。reflect。InvocationTargetExceptionnullatnet。sf。cglib。core。AbstractClassGenerator。generate(AbstractClassGenerator。java:345)atnet。sf。cglib。proxy。Enhancer。generate(Enhancer。java:492)atnet。sf。cglib。core。AbstractClassGeneratorClassLoaderData。get(AbstractClassGenerator。java:114)atnet。sf。cglib。core。AbstractClassGenerator。create(AbstractClassGenerator。java:291)atnet。sf。cglib。proxy。Enhancer。createHelper(Enhancer。java:480)atnet。sf。cglib。proxy。Enhancer。create(Enhancer。java:305)atorg。example。App。main(App。java:23)Causedby:java。lang。reflect。InvocationTargetExceptionatsun。reflect。GeneratedMethodAccessor1。invoke(UnknownSource)atsun。reflect。DelegatingMethodAccessorImpl。invoke(DelegatingMethodAccessorImpl。java:43)atjava。lang。reflect。Method。invoke(Method。java:498)atnet。sf。cglib。core。ReflectUtils。defineClass(ReflectUtils。java:413)atnet。sf。cglib。core。AbstractClassGenerator。generate(AbstractClassGenerator。java:336)。。。6moreCausedby:java。lang。OutOfMemoryError:Metaspaceatjava。lang。ClassLoader。defineClass1(NativeMethod)atjava。lang。ClassLoader。defineClass(ClassLoader。java:756)。。。11more案例心得元数据区和方法区错误的识别:java。lang。OutOfMemoryError:Metaspace;java。lang。OutOfMemoryError:PermGenspace元数据区的溢出一般和框架代码或者本地代码中大量创建动态类有关核查问题时,也是根据具体的问题分析是哪个动态类被大量创建,是否有必要,是否需要调整方法区的大小。直接内存区域的溢出
直接内存区域,如果内存达到设置的MaxDirectMemorySize后,就会触发垃圾回收,如果垃圾回收不能有效回收内存,也会引起OOM溢出。
如何复现?JVM参数:XX:MaxDirectMemorySize,如果不指定,和Xmx指定的最大堆一样代码部分:使用unsafe不断的分配直接内存
代码实战packageorg。example;importsun。misc。Unsafe;importjava。lang。reflect。Field;publicclassApp{publicstaticvoidmain(String〔〕args)throwsIllegalAccessException{FieldunsafeFiledUnsafe。class。getDeclaredFields()〔0〕;unsafeFiled。setAccessible(true);Unsafeunsafe(Unsafe)unsafeFiled。get(null);while(true){unsafe。allocateMemory(10241024);}}}
运行结果Exceptioninthreadmainjava。lang。OutOfMemoryErroratsun。misc。Unsafe。allocateMemory(NativeMethod)atorg。example。App。main(App。java:15)
案例心得:直接内存溢出的识别:Exceptioninthreadmainjava。lang。OutOfMemoryError,并且dump出的堆栈文件不大核查问题时,根据异常堆栈检查引发error的代码,一般都是NIO代码引起的。实践案例如何正确利用大内存高性能硬件上的程序部署策略
高性能硬件程序部署主要有两种方式:通过64位JDK使用来大内存使用若干个32位的虚拟机建立逻辑集群以利用硬件资源
如果程序是对响应时间敏感的系统,想配置大堆的前提是,要保证应用的FullGC频率足够低,不会影响用户的使用,比如,可以设置深夜定时任务触发fullgc甚至自动重启应用服务器来保证可用空间在一个稳定的水平。控制FullGC频率的关键就是保证大多数对象都是朝生夕灭,不会短时间有大量对象进入老年代,引起频繁的FullGC。
不仅如此,还需要考虑大堆带来的其他问题:内存回收时间变长如果出现OOM,因为堆过大,几乎无法分析dump文件64位的JDK性能测试结果要弱于32位的JDK,并且占用内存也较大
所以建议,如非必要,尽可能使用第二种方式来部署以充分利用高性能硬件资源。
第二种方式就是集群部署方式,采用集群部署就需要考虑额外的问题,比如,如何保留用户的状态,一般有两种解决方案:亲和式集群:同一个用户都转发到同一个服务器去处理优点:实现简单,只需要在apache等负载均衡器中配置即可;网络流量较少,客户端只需要传递sessionID即可缺点:用户和对应服务器绑定,一旦服务器宕机,用户的session状态即消失apache中这样配置:worker。controller。stickysessiontruefalsetrue为亲和式集群worker。controller。stickysessionforcetruefalsefalse为当服务器不可用,转发到其他服务器共享session:集群内服务器共享session优点:服务器宕机,用户也不会丢失session状态缺点:在系统中引入了新的组件,提高了系统的复杂度,实现复杂度
针对第二种方式,和第一种方式对比,也有自己的缺点,我们在设计系统机构时也需要考虑到:同一台物理机器的上的资源竞争(并发写),首先会想到可以使用同步机制,可以学习锁设计中的分段锁和间隙锁,通过锁一部分来提高并发度;或者通过乐观锁的设计,不断循环更新直到成功;还可以考虑建立热访问资源,提前把一部分资源缓存到集中缓存中,通过集中式缓存减少磁盘IO竞争。冗余的本地内存,可以考虑使用集中式内存数据库解决资源池浪费,可以考虑使用JNDI(统一命名服务,我觉得和Springcloud中的统一配置中心核心思想是一致的,都是把配置文件统一放在一个地方,便于引用维护),但是也会带来新的复杂度
总结:高性能硬件的部署策略有两种,考虑到GC时间过长,堆转出日志无法分析等缺点,尽量选择多实例部署的逻辑集群方式逻辑集群的部署方式要考虑状态保持、资源竞争和资源冗余等情况,根据具体业务场景灵活应用。如何排查内存溢出错误
堆外内存溢出一般主要来源于操作系统对进程的内存限制和堆外内存回收的机制。
针对操作系统堆进程的内存限制。比如:32位的windows操作系统对进程的限制为2G,如果堆等其他区域划分的内存过大,那么留给直接内存区域的内存就非常小了。
针对堆外内存的回收机制。堆外内存需要等到满了之后,再在代码中触发System。gc来回收,如果服务器开启XX:DisableExplicitGC参数开关,那么就不会响应这次垃圾回收的请求。
总结:
因为限制以及其他区域不合理的参数配置,直接内存区域只有很小的一块内存;并且垃圾回收需要依靠手动触发System。gc来回收无法保证回收的可靠性,所以溢出就是必然的了。
我这里又查阅了之前看过印象深刻的一个关于美团使用网络框架的一个堆外内存泄漏bug。这里给大家简单介绍下,原文详见这里:Netty堆外内存泄露排查盛宴
首先作者通过nginx不断报5XX异常发现服务不可用,然后核查jvm发现频繁的fullgc导致用户线程阻塞(其实就是netty的nio线程),最后查出是log4j2在某个时点大量频繁的打印堆外内存不足的error日志导致的,所以这个问题的核心在于排查堆外内存为何泄漏。
排查的步骤首先是基于异常的堆栈日志,找到对应的代码,用反射机制每隔N秒观察堆外内存的大小,发现了堆外内存增长的规律。然后猜测原因,模拟测试查看是否可以复现问题,成功复现问题后,就能大约找到出现问题的代码,继续通过debug查找根源代码处,最终通过修改代码,重新build后最终解决问题。
我个人认为这个问题解决的关键在于开发者能够读懂框架自己使用变量统计堆外内存,然后得以跟踪这个变量最终解决问题。我们在排查问题的时候如果也可以多想一些,多去琢磨框架报出异常的原因,也许就能找到解决问题的办法。如何排查系统CPU性能指标异常外部命令导致系统缓慢
案例介绍:在做压力测试时发现系统的CPU指标异常,大量的时间被系统调用fork占用,最后核查代码发现这个fork系统调用是在每一个请求来临时,都会调用以获取系统相关的信息的,具体是使用Runtime。getRuntime()。exec()来执行一段shell脚本,最后修改为通过javaapi调用,问题解决。
案例收获:cpu等系统指标异常,一般都是来源于应用代码的某些操作,需要仔细检查代码中会导致系统调用的部分,采用其他替代方式实现。java中获取操作系统信息,其实是一个很常见的操作,可以通过javaapi实现。
最后给大家分享Spring系列的学习笔记和面试题,包含spring面试题、springcloud面试题、springboot面试题、spring教程笔记、springboot教程笔记、最新阿里巴巴开发手册(63页PDF总结)、2022年Java面试手册。一共整理了1184页PDF文档。私信博主(777)领取,祝大家更上一层楼!!!
原文作者:Ging
原文出处:https:www。cnblogs。comgingp13853741。html
周纪三家分晋(1)周威烈王姬午正式分封晋国大夫魏斯、赵籍、韩虔为诸侯国国君。当时,晋大夫智宣子想要确立儿子智瑶(即智伯瑶)为智氏宗族的继承人。族人智果反对,说:智瑶不如智宵,才过而德不如。……
孝慎皇后传奇第一章乾隆,中国历史上执政时间最长、年寿最高的皇帝。前期平定大小和卓叛乱、灭准噶尔、巩固多民族国家的发展、文治武功兼修,后期穷奢极欲、六次下江南、到处修建别院。为了维持奢……
朱德军事文选致西班牙人民书前言本文原载于《中共抗日论文集》。(一九三七年五月十三日)亲爱的同志们:我代表中国奋斗了十年的全体人民抗日红军,向西班牙英勇战斗的人民及它的军队,致最诚……
解读雍正王朝第十一期上期视频中提到太子给了四爷一个沉痛打击可没想到这个打击不是结束而是开始接下来四爷又遭受了多重打击难道果真是天将降大任于斯人也吗原本计划的追缴国库欠……
解读雍正王朝第十四期上期视频中提到四爷用苦肉计躲过这份清查大清建国70年以来最大冤案的差事之后皇上安排老八和老十三协同办理此事太子听到消息后来找老八打招呼希望他……
200元左右平价耳机性价比高的蓝牙耳机学生党蓝牙耳机推荐1。绿联HiTuneT3蓝牙耳机179这款耳机的性价比还是比较高的,属于各方面性能都比较均衡,优点也比较多。降噪能力很强。对于一款耳机来说,降噪能力还是很重要的,毕……
电竞春晚还是菜鸡互啄?AG超玩会大战XYG,毒瘤久诚终于被换陌陌聊游戏,给您带来精彩激烈的KPL夏季赛赛事资讯!看了常规赛第三轮第一天的赛程之后,相信很多的网友都已经感受到比赛的激烈程度了!在这一轮的比赛中,每一支战队都不会再放水……
中国男篮首战后评分三将高分,锋线集体拉跨,后卫线不如杜峰!新组建的中国男篮首战主场对阵哈萨克斯坦,以12分之差赢了!比赛高开低走,应该说没有达到预期!我给球员打了下分,看看你们认同吗?周琦:9分!有限的球权,有限的时间,1612……
AI的未来往哪走?图灵奖得主未来十年还是要继续追赶人类AI社区中的讨论,正在愈演愈烈。自前段时间的谷歌研究员怀疑AI具备人格的新闻起,海内外整个AI圈都以此展开了多项有关技术、伦理甚至AI未来的讨论,参战的不乏业内大牛,比如……
中国史连载孔子原来不姓孔,祖籍也不是曲阜孔子是我国春秋时代伟大的教育家、思想家。孔子的思想曾影响中国以及中国周边的汉字文化圈国家长达数千年。如今人们都把山东曲阜视为孔子的故乡。我们现在到曲阜旅游还能见到孔府、孔庙、孔……
她是才貌双全的黄埔高材生,18岁嫁给叶剑英,独自培养少将儿子在祖国的中南部腹地,著名的红色精神发源地和传播地区湖南,在革命时期涌现出了不少的英雄人物,他们没有畏惧困难和艰险,高举社会主义的旗帜,带领人民走向了幸福,今天要介绍的这位巾帼女……
迪丽热巴在机场一袭蓝色吊带裙十分吸睛,明星在机场照都超时尚?迪丽热巴现身机场,一袭蓝色吊带裙十分吸睛,明星的机场照都超时尚是因为如下的原因。一、迪丽热巴现身机场迪丽热巴穿着一袭蓝色吊带裙现身机场,迪丽热巴没有化妆,脸上还戴着……