七段小代碼,玩轉(zhuǎn)Java程序常見的崩潰場景!-環(huán)球視點

發(fā)布時間:2023-05-07 19:21:46  |  來源:清一色財經(jīng)  

最近在看RCA(Root Cause Analysis)的東西,不小心發(fā)現(xiàn)了yCrash這么個東西。它的幾段問題小代碼寫的非常典型,我們可以稍微看一下,來看看Java應(yīng)用程序常見的幾個崩潰場景。

Java程序是基于GC的,在啟動初始,就申請了足量的內(nèi)存池,再加上JIT等編譯器的實時優(yōu)化,速度并不比直接用C++語言寫的慢。Java語言同時由于反射和可觀測等特點,再加上JFR這種神器,在發(fā)生問題的時候比二進制文件更容易找到它的根源。


(資料圖)

最近在看RCA(Root CauseAnalysis)的東西,不小心發(fā)現(xiàn)了yCrash這么個東西。它的幾段問題小代碼寫的非常典型,我們可以稍微看一下,來看看Java應(yīng)用程序常見的幾個崩潰場景。

1. 堆空間溢出

OOM 一般是內(nèi)存泄漏引起的,表現(xiàn)在 GC 日志里,一般情況下就是 GC 的時間變長了,而且每次回收的效果都非常一般。GC后,堆內(nèi)存的實際占用呈上升趨勢。

下面的代碼是死循環(huán),持續(xù)向HashMap里塞數(shù)據(jù),由于myMap屬于GCRoots,始終得不到釋放,所以它最終的結(jié)果就是OOM。

import java.util.HashMap;public class OOMDemo {   static HashMap 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)存溢出可能是由于請求量過高,或者真實的業(yè)務(wù)需求需要所造成的后果,而內(nèi)存溢出屬于未知的、超出期望的OOM情況。

我們可以使用上面同樣的代碼達到這個目的。

在現(xiàn)實情況中,內(nèi)存泄漏通常都非常的隱蔽,需要借助Mat等工具才能找到根本原因。jmap、pmap等是常用的工具。

比如,如果你忘記了重寫對象的hashCode和equals方法,就會產(chǎn)生內(nèi)存泄漏。

//leak example : created by xjjdog 2022import 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 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 最多的某個進程,記錄它的 pid。使用 Shift + P 快捷鍵可以按 CPU的使用率進行排序。

(2)再次使用 top 命令,加 -H 參數(shù),查看某個進程中使用 CPU 最多的某個線程,記錄線程的 ID。

(3)使用 printf函數(shù),將十進制的 tid 轉(zhuǎn)化成十六進制。

(4)使用 jstack 命令,查看 Java 進程的線程棧。

(5)使用較少命令查看生成的文件,并查找剛才轉(zhuǎn)化的十六進制 tid,找到發(fā)生問題的線程上下文。

4. 線程泄漏

線程資源是昂貴的。如果你不停的創(chuàng)建線程,系統(tǒng)資源很快就會被耗盡。下面的代碼一直不停的創(chuàng)建線程,如果同時請求壓力比較大的話,多數(shù)能搞死宿主機。

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)建一個線程池的后果是一樣的。

java.lang.OutOfMemory錯誤:無法創(chuàng)建新的本機線程

5. 死鎖

死鎖代碼一般不會發(fā)生,但一旦發(fā)生還是非常嚴重的,相關(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();   } }

死鎖屬于比較嚴重的一種情況,jstack 會以明顯的信息進行提示。當(dāng)然,關(guān)于線程的dump,也有一些線上分析工具可以使用。比如fastthread,但也需要你先了解這些情況發(fā)生的意義。

6. 棧溢出

棧溢出不會造成 JVM 進程死亡,危害“相對較小”。下面是一個簡單的模擬棧溢出的代碼,只需要遞歸調(diào)用就可以了。

public class StackOverflowDemo {   public void start() {      start();   }}

通過 -Xss 參數(shù)可以設(shè)置虛擬機棧的大小。比如下面的命令就是設(shè)置棧大小為 128K:

-Xss128K

如果你的應(yīng)用經(jīng)常發(fā)生這種情況,可以試著調(diào)大這個值。但一般都是因為程序錯誤引起的,最好檢查一下自己的代碼。

7. 被阻止的線程

BLOCKED是一個比較嚴重的線程狀態(tài),當(dāng)后端的服務(wù)處理時間非常長,請求的線程就會進入等待狀態(tài)。這時候通過jstack來獲取堆棧,就會發(fā)現(xiàn)線程處于阻塞狀態(tài)。它阻塞在對鎖的獲取上(watingto 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ù)。

結(jié)束

以上就是對于Java常見故障的幾段小代碼分析,大部分的故障都逃不出這些場景。故障的排查通常都非常耗費精力,而且你得有線上權(quán)限。怎樣做一些好用的工具,把這些復(fù)雜性屏蔽在后面,才是我們所想要的。

關(guān)鍵詞:

 

網(wǎng)站介紹  |  版權(quán)說明  |  聯(lián)系我們  |  網(wǎng)站地圖 

星際派備案號:京ICP備2022016840號-16 營業(yè)執(zhí)照公示信息版權(quán)所有 郵箱聯(lián)系:920 891 263@qq.com