当前位置: 首页 > 科技观察

七段小代码,玩转Java程序常见崩溃场景!

时间:2023-03-15 13:39:21 科技观察

Java程序是基于GC的。启动之初申请足够的内存池,再加上JIT等编译器的实时优化,速度不比直接用C++语言写的慢。同时,由于Java语言的反射性和可观察性的特点,再加上JFR这个神器,当出现问题时,比二进制文件更容易找到根源。最近在看RCA(RootCauseAnalysis),无意中发现了yCrash之类的东西。它的几个有问题的小代码非常典型。我们可以看一下Java应用的几种常见的崩溃场景。1.堆空间溢出OOM一般是内存泄漏引起的,在GC日志中有体现。一般GC时间变长,每次回收的效果都很一般。GC之后,堆内存的实际使用量呈上升趋势。下面的代码是一个无限循环,不断往HashMap中塞数据。由于myMap属于GCRoots,永远不会释放,所以它的最终结果是OOM。importjava.util.HashMap;publicclassOOMDemo{staticHashMapmyMap=newHashMap<>();publicstaticvoidstart()throwsException{while(true){myMap.put("key"+counter,"Largestringgggggggggggggggggggggggggggg"+"ggggggggggggggggggggggggggggggggggggggggggggggggggggg"+"ggggggggggggggggggggggggggggggggggggggggggggggggggggg"+"ggggggggggggggggggggggggggggggggggggggggggggggggggggg"+"ggggggggggggggggggggggggggggggggggggggggggggggggggggg"+"ggggggggggggggggggggggggggggggggggggggggggggggggggggg"+"gggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg"+"gggggggggggggggggggggggggggggggggggggggggggggggg"+counter);++counter;}}}2.内存泄漏内存泄漏和内存溢出是一样的,不同的是它的语义内存溢出可能是由于请求量过大,或者是真实的业务需求requiretheconsequences,而内存溢出是一种未知的、意想不到的OOM情况,我们可以使用上面相同的代码来达到这个目的,现实中,内存泄漏通常是非常隐蔽的,需要Mat等工具来查找根源。jmap、pmap等都是常用的工具,比如忘记重写对象的hashCode和equals方法,就会出现内存泄漏。//泄漏示例:createdbyxjjdog2022importjava.util.HashMap;importjava.util.Map;publicclassHashMapLeakDemo{publicstaticclassKey{Stringtitle;publicKey(Stringtitle){this.title=title;}}publicstaticvoidmain(String[]args){Mapmap=newHashMap<>();map.put(new钥匙(“1”),1);map.put(新键("2"),2);map.put(新键("3"),2);整数integer=map.get(newKey("2"));System.out.println(整数);}}3.CPU飙升直接进入死循环,可以把CPU给死掉。公共类CPUSpikeDemo{publicstaticvoidstart(){newCPUSpikerThread().start();新的CPUSpikerThread().start();新的CPUSpikerThread().start();新的CPUSpikerThread().start();新的CPUSpikerThread().start();新的CPUSpikerThread().start();System.out.println("6个线程启动!");}}publicclassCPUSpikerThreadextendsThread{@Overridepublicvoidrun(){while(true){//只是无限循环}}}获取问题代码通常可以使用以下方法:(1)使用top命令找到一个使用最多CPU的进程并记录它的pid。使用Shift+P快捷键按CPU使用率排序。(2)再次使用top命令,加上-H参数,可以查看一个进程中占用CPU最多的线程,记录线程ID。(3)用printf函数将十进制的tid转为十六进制。(4)使用jstack命令查看Java进程的线程栈。(5)使用less命令查看生成的文件,找到刚刚转换的16进制的tid,找到出现问题的线程上下文。4.线程泄漏线程资源是昂贵的。如果一直创建线程,系统资源会很快耗尽。下面的代码一直在创建线程。如果同时请求压力比较大,大部分都可以kill宿主机。公共类ThreadLeakDemo{publicstaticvoidstart(){while(true){newForeverThread().start();}}}publicclassForeverThreadextendsThread{@Overridepublicvoidrun(){//让线程永远休眠,这样它们就不会死掉。while(true){try{//重复睡眠10分钟Thread.睡眠(10*60*1000);}catch(Exceptione){}}}}这很暴力,这个每个请求创建一个线程,或者创建一个线程池的后果是一样的。java.lang.OutOfMemoryerror:Unabletocreateanewnativethread5.Deadlock死锁代码一般不会发生,但是一旦发生,还是很严重的,相关业务可能无法运行。publicclassDeadLockDemo{publicstaticvoidstart(){newThreadA().start();新ThreadB().start();}}publicclassThreadAextendsThread{@Overridepublicvoidrun(){CoolObject.method1();}}publicclassThreadBextendsThread{@Overridepublicvoidrun(){HotObject.method2();}}publicclassCoolObject{publicstaticsynchronizedvoidmethod1(){try{//休眠10秒Thread.sleep(10*1000);}赶上(异常e){}HotObject.method2();}}publicclassHotObject{publicstaticsynchronizedvoidmethod2(){try{//休眠10秒Thread.sleep(10*1000);}赶上(异常e){}CoolObject.method1();}}死锁是比较严重的情况,jstack会提示明显的信息。当然,也有一些在线的线程转储分析工具可以使用。比如fastthread,但是你也需要先了解这几种情况的含义。6、栈溢出栈溢出不会导致JVM进程死掉,危害“比较小”。下面是一段简单的模拟栈溢出的代码,只需要递归调用即可。公共类StackOverflowDemo{publicvoidstart(){start();}}虚拟机栈的大小可以通过-Xss参数设置。例如,以下命令是将堆栈大小设置为128K:-Xss128K如果在您的应用程序中经常出现这种情况,您可以尝试增大该值。但一般是程序错误导致的,最好检查一下自己的代码。7、阻塞线程BLOCKED是一种严重的线程状态。当后端服务处理时间很长时,请求线程会进入等待状态。这时候如果通过jstack获取栈,就会发现线程被阻塞了。它在获取锁时阻塞(等待锁定)publicclassBlockedAppDemo{publicstaticvoidstart(){for(intcounter=0;counter<10;++counter){//启动10个线程。新的AppThread().start();}}}publicclassAppThreadextendsThread{@Overridepublicvoidrun(){AppObject.getSomething();}}publicclassAppObject{publicstaticsynchronizedvoidgetSomething(){while(true){try{Thread.sleep(10*60*1000);}catch(Exceptione){}}}}一旦经常出现这种情况,就证明你的程序响应太慢了。如果还有剩余的CPU资源,可以尝试增加请求的线程数,比如tomcat中的最大线程数。以上就是Java常见故障的几个小代码分析,大部分故障都逃不过这些场景。故障排除通常非常耗费精力,而且您必须能够在线访问。如何做出一些有用的工具,将这些复杂性屏蔽在背后,才是我们想要的。