一、什麼是堆棧信息
在Java程序中,為了存儲數據而分配的內存分為兩類:堆(Heap)和棧(Stack)。
堆是Java運行時內存中的一塊區域,用於存儲對象及其實例變量。而棧也是內存中的一塊區域,用於存儲方法執行時的局部變量和操作數。在Java程序中,當一個方法被調用時,Java虛擬機會在棧中為該方法創建一個新的棧幀,然後將其放在棧的頂部。當方法執行完畢時,棧幀就會被彈出,方法的返回值也會被壓入棧中。
堆棧信息指的是程序在運行時,產生的一些狀態信息,包括棧幀的大小、離線時間、運行時間。通過對這些信息的分析,可以排查代碼的執行過程中出現的問題。
二、如何獲取堆棧信息
Java虛擬機提供了一些工具和API,用於獲取堆棧信息,比如jstack、jconsole、jvisualvm等。
jstack用於打印出指定Java進程中每個線程的堆棧信息。打印出來的信息包含線程狀態、鎖信息、分配的對象、堆棧跟蹤信息等。
public class Test { public static void main(String[] args) { while (true) { System.out.println("Hello, World!"); } } }
使用jstack命令可以獲取到以下堆棧信息:
"main" #1 prio=5 os_prio=0 tid=0x0000000002c8f800 nid=0x3880 runnable [0x00000000027ff000] java.lang.Thread.State: RUNNABLE at java.io.PrintStream.write(PrintStream.java:480) - locked (a java.io.PrintStream) at sun.nio.cs.StreamEncoder.writeBytes(StreamEncoder.java:221) at sun.nio.cs.StreamEncoder.implFlushBuffer(StreamEncoder.java:291) at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:295) at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:141) - locked (a java.io.OutputStreamWriter) at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:229) at java.util.logging.StreamHandler.flush(StreamHandler.java:242) - locked (a java.util.logging.ConsoleHandler) at java.util.logging.ConsoleHandler.publish(ConsoleHandler.java:106) at java.util.logging.Logger.log(Logger.java:738) at java.util.logging.Logger.doLog(Logger.java:765) at java.util.logging.Logger.log(Logger.java:788) at java.util.logging.Logger.info(Logger.java:1495) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
三、堆棧信息分析的常用場景
1.線程死鎖
線程死鎖指的是兩個或多個線程互相持有對方所需要的鎖,從而導致所有線程都阻塞無法繼續執行。通過分析堆棧信息,可以定位哪些線程被阻塞在了哪些鎖上,從而幫助我們找到死鎖的根源並解決問題。
public class Test { public static final Object lock1 = new Object(); public static final Object lock2 = new Object(); public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { synchronized (lock1) { System.out.println("Thread 1: Holding lock 1..."); try { Thread.sleep(10); } catch (InterruptedException e) { } System.out.println("Thread 1: Waiting for lock 2..."); synchronized (lock2) { System.out.println("Thread 1: Holding lock 1 and 2..."); } } } }).start(); new Thread(new Runnable() { @Override public void run() { synchronized (lock2) { System.out.println("Thread 2: Holding lock 2..."); try { Thread.sleep(10); } catch (InterruptedException e) { } System.out.println("Thread 2: Waiting for lock 1..."); synchronized (lock1) { System.out.println("Thread 2: Holding lock 1 and 2..."); } } } }).start(); } }
使用jstack命令可以獲取到以下堆棧信息:
"Thread-0": waiting to lock Monitor@0x0000000704003840 (Object@0x000000076c4a52a8, a java.lang.Object), which is held by "Thread-1" "Thread-1": waiting to lock Monitor@0x0000000704002b60 (Object@0x000000076c4a52b8, a java.lang.Object), which is held by "Thread-0"
從以上堆棧信息可以看出,Thread-0持有的鎖(Object@0x000000076c4a52a8)正在被Thread-1等待獲取,而Thread-1持有的鎖(Object@0x000000076c4a52b8)正在被Thread-0等待獲取。這就是典型的死鎖場景。
2.性能分析
通過分析堆棧信息,可以了解到程序在執行過程中的性能瓶頸,從而針對性地進行優化。
public class Test { public static void main(String[] args) { long start = System.currentTimeMillis(); for (int i = 0; i < 10000000; i++) { new String("hello"); } long end = System.currentTimeMillis(); System.out.println("Time used: " + (end - start) + " ms."); } }
使用jstack命令可以獲取到以下堆棧信息:
"main" #1 prio=5 os_prio=0 tid=0x00000000022ce800 nid=0x34b8 runnable [0x000000000215f000] java.lang.Thread.State: RUNNABLE at java.lang.String.<init>(String.java:604) at com.example.Test.main(Test.java:7)
從以上堆棧信息可以看出,程序主要的耗時都花在了創建String對象上。因為每次創建String對象都需要在堆中分配內存,所以這部分操作非常消耗性能。如果需要頻繁地創建相同的字符串,可以考慮使用字符串常量池來避免重複創建對象。
3.異常排查
通過分析堆棧信息,可以快速定位程序運行過程中的異常。
public class Test { public static void main(String[] args) { try { System.out.println(3 / 0); } catch (Exception e) { e.printStackTrace(); } } }
使用jstack命令可以獲取到以下堆棧信息:
"main" #1 prio=5 os_prio=0 tid=0x0000000002e61000 nid=0x2370 waiting on condition [0x00000000027fc000] java.lang.Thread.State: WAITING (parking) at sun.misc.Unsafe.park(Native Method) - parking to wait for (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject) at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) at java.util.concurrent.LinkedBlockingQueue.take(LinkedBlockingQueue.java:442) at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)
從以上堆棧信息可以看出,程序在執行3/0操作時拋出了異常。經過分析,發現異常是由於除數為0造成的。
四、結論
Java堆棧信息分析是程序調試和優化的重要手段。通過分析堆棧信息,可以快速定位線程死鎖、性能瓶頸和異常等問題,並針對性地進行優化。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/303855.html