七段小代碼解決Java程序常見的崩潰場景
前言
Java程序是基于GC的,在啟動初始,就申請了足量的內(nèi)存池,再加上JIT等編譯器的實(shí)時優(yōu)化,速度并不比直接用C++語言寫的慢。Java語言同時由于反射和可觀測等特點(diǎn),再加上JFR這種神器,在發(fā)生問題的時候比二進(jìn)制文件更容易找到它的根源。
最近在看RCA(Root Cause Analysis)的東西,不小心發(fā)現(xiàn)了yCrash這么個東西。它的幾段問題小代碼寫的非常典型,我們可以稍微看一下,來看看Java應(yīng)用程序常見的幾個崩潰場景。
1.堆空間溢出
OOM 一般是內(nèi)存泄漏引起的,表現(xiàn)在 GC 日志里,一般情況下就是 GC 的時間變長了,而且每次回收的效果都非常一般。GC 后,堆內(nèi)存的實(shí)際占用呈上升趨勢。
下面的代碼是死循環(huán),持續(xù)向HashMap里塞數(shù)據(jù),由于myMap屬于GCRoots,始終得不到釋放,所以它最終的結(jié)果就是OOM。
import java.util.HashMap; public class OOMDemo { static HashMap<Object, Object> myMap = new HashMap<>(); public static void start() throws Exception { while (true) { myMap.put("key" + counter, "Large stringgggggggggggggggggggggggggggg" + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg" + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg" + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg" + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg" + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg" + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg" + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg" + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg" + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg" + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg" + "ggggggggggggggggggggggggggggggggggggggggggggggggggggg" + counter); ++counter; } } }
2.內(nèi)存泄漏
內(nèi)存泄漏和內(nèi)存溢出是一個道理,不同的是它的語意。
內(nèi)存溢出可能是由于請求量過高,或者真實(shí)的業(yè)務(wù)需求需要所造成的后果,而內(nèi)存溢出屬于未知的、超出期望的OOM情況。
我們可以使用上面同樣的代碼達(dá)到這個目的。
在現(xiàn)實(shí)情況中,內(nèi)存泄漏通常都非常的隱蔽,需要借助Mat等工具才能找到根本原因。jmap、pmap等是常用的工具。
比如,如果你忘記了重寫對象的hashCode和equals方法,就會產(chǎn)生內(nèi)存泄漏。
//leak example : created by xjjdog 2022 import java.util.HashMap; import java.util.Map; public class HashMapLeakDemo { public static class Key { String title; public Key(String title) { this.title = title; } } public static void main(String[] args) { Map<Key, Integer> map = new HashMap<>(); map.put(new Key("1"), 1); map.put(new Key("2"), 2); map.put(new Key("3"), 2); Integer integer = map.get(new Key("2")); System.out.println(integer); } }
3.CPU飆升
直接一個死循環(huán),就可以把CPU干死。
public class CPUSpikeDemo { public static void start() { new CPUSpikerThread().start(); new CPUSpikerThread().start(); new CPUSpikerThread().start(); new CPUSpikerThread().start(); new CPUSpikerThread().start(); new CPUSpikerThread().start(); System.out.println("6 threads launched!"); } } public class CPUSpikerThread extends Thread { @Override public void run() { while (true) { // Just looping infinitely } } }
獲取問題代碼通??梢允褂孟旅娴姆椒ā?/p>
- (1)使用 top 命令,查找到使用 CPU 最多的某個進(jìn)程,記錄它的 pid。使用 Shift + P 快捷鍵可以按 CPU 的使用率進(jìn)行排序。
- (2)再次使用 top 命令,加 -H 參數(shù),查看某個進(jìn)程中使用 CPU 最多的某個線程,記錄線程的 ID。
- (3)使用 printf 函數(shù),將十進(jìn)制的 tid 轉(zhuǎn)化成十六進(jìn)制。
- (4)使用 jstack 命令,查看 Java 進(jìn)程的線程棧。
- (5)使用 less 命令查看生成的文件,并查找剛才轉(zhuǎn)化的十六進(jìn)制 tid,找到發(fā)生問題的線程上下文。
4.線程泄漏
線程資源是昂貴的。如果你不停的創(chuàng)建線程,系統(tǒng)資源很快就會被耗盡。下面的代碼一直不停的創(chuàng)建線程,如果同時請求壓力比較大的話,多數(shù)能搞死宿主機(jī)。
public class ThreadLeakDemo { public static void start() { while (true) { new ForeverThread().start(); } } } public class ForeverThread extends Thread { @Override public void run() { // Put the thread to sleep forever, so they don't die. while (true) { try { // Sleeping for 10 minutes repeatedly Thread.sleep(10 * 60 * 1000); } catch (Exception e) {} } } }
這是暴力啊,這和每個請求創(chuàng)建一個線程,或者創(chuàng)建一個線程池的后果是一樣的。xjjdog這里還有兩篇關(guān)聯(lián)的線程泄漏文章。
java.lang.OutOfMemoryError: unable to create new native thread
5.死鎖
死鎖代碼一般不會發(fā)生,但一旦發(fā)生還是非常嚴(yán)重的,相關(guān)的業(yè)務(wù)可能就跑不動了。
public class DeadLockDemo { public static void start() { new ThreadA().start(); new ThreadB().start(); } } public class ThreadA extends Thread { @Override public void run() { CoolObject.method1(); } } public class ThreadB extends Thread { @Override public void run() { HotObject.method2(); } } public class CoolObject { public static synchronized void method1() { try { // Sleep for 10 seconds Thread.sleep(10 * 1000); } catch (Exception e) {} HotObject.method2(); } } public class HotObject { public static synchronized void method2() { try { // Sleep for 10 seconds Thread.sleep(10 * 1000); } catch (Exception e) {} CoolObject.method1(); } }
死鎖屬于比較嚴(yán)重的一種情況,jstack 會以明顯的信息進(jìn)行提示。當(dāng)然,關(guān)于線程的 dump,也有一些線上分析工具可以使用。比如fastthread,但也需要你先了解這些情況發(fā)生的意義。
6.棧溢出
棧溢出不會造成 JVM 進(jìn)程死亡,危害“相對較小”。下面是一個簡單的模擬棧溢出的代碼,只需要遞歸調(diào)用就可以了。
public class StackOverflowDemo { public void start() { start(); } }
通過 -Xss 參數(shù)可以設(shè)置虛擬機(jī)棧的大小。比如下面的命令就是設(shè)置棧大小為 128K。
-Xss128K
如果你的應(yīng)用經(jīng)常發(fā)生這種情況,可以試著調(diào)大這個值。但一般都是因?yàn)槌绦蝈e誤引起的,最好檢查一下自己的代碼。
7.Blocked線程
BLOCKED是一個比較嚴(yán)重的線程狀態(tài),當(dāng)后端的服務(wù)處理時間非常長,請求的線程就會進(jìn)入等待狀態(tài)。這時候通過jstack來獲取堆棧,就會發(fā)現(xiàn)線程處于阻塞狀態(tài)。它阻塞在對鎖的獲取上(wating to lock)
public class BlockedAppDemo { public static void start() { for (int counter = 0; counter < 10; ++counter) { // Launch 10 threads. new AppThread().start(); } } } public class AppThread extends Thread { @Override public void run() { AppObject.getSomething(); } } public class AppObject { public static synchronized void getSomething() { while (true) { try { Thread.sleep(10 * 60 * 1000); } catch (Exception e) {} } } }
一旦頻繁發(fā)生這種情況,就證明你的程序相應(yīng)太慢了。如果CPU資源還有剩余,可以嘗試著增加請求的線程數(shù),比如tomcat的最大線程數(shù)。
End
以上就是對于Java常見故障的幾段小代碼分析,大部分的故障都逃不出這些場景。故障的排查通常都非常耗費(fèi)精力,而且你得有線上權(quán)限。怎樣做一些好用的工具,把這些復(fù)雜性屏蔽在后面,才是我們所想要的,更多關(guān)于Java程序崩潰解決代碼的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
淺談Spring Boot 微服務(wù)項目的推薦部署方式
這篇文章主要介紹了淺談Spring Boot 微服務(wù)項目的推薦部署方式,具有一定參考價值,需要的朋友可以了解下。2017-09-09Java面試題沖刺第二十六天--實(shí)戰(zhàn)編程
這篇文章主要為大家分享了最有價值的三道java實(shí)戰(zhàn)面試題,涵蓋內(nèi)容全面,包括數(shù)據(jù)結(jié)構(gòu)和算法相關(guān)的題目、經(jīng)典面試編程題等,感興趣的小伙伴們可以參考一下2021-08-08JAVA中的deflate壓縮實(shí)現(xiàn)方法
下面小編就為大家?guī)硪黄狫AVA中的deflate壓縮實(shí)現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-09-09MyBatis-Plus邏輯刪除和字段自動填充的實(shí)現(xiàn)
本文主要介紹了MyBatis-Plus邏輯刪除和字段自動填充的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08springboot 2.0 mybatis mapper-locations掃描多個路徑的實(shí)現(xiàn)
這篇文章主要介紹了springboot 2.0 mybatis mapper-locations掃描多個路徑的實(shí)現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-07-07深入了解SpringBoot中的統(tǒng)一返回和統(tǒng)一異常處理
這篇文章主要為大家詳細(xì)介紹了SpringBoot項目中常用的統(tǒng)一返回結(jié)果和統(tǒng)一異常處理,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下2024-01-01Mybatis-Plus多表關(guān)聯(lián)查詢的使用案例解析
這篇文章主要介紹了Mybatis-Plus多表關(guān)聯(lián)查詢的使用,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-05-05