官术网_书友最值得收藏!

第一章 Java虛擬機棧

你遇到過StackOverflowError嗎

StackOverflowError這個錯誤常出現在較深的方法調用以及遞歸方法中,平時很少會遇到。我們以一道經典的遞歸算法題為例,求1到n的和。為了查看在發生棧溢出時方法一共遞歸了多少次,我們在方法中打印當前n的值。如代碼清單1-1所示。

代碼清單1-1 使用遞歸算法求1到n的和

  
public class RecursionAlgorithmMain { public static int sigma(int n) { System.out.println("current 'n' value is " + n); return n + sigma(n + 1); } public static void main(String[] args) throws IOException { new Thread(() -> sigma(1)).start(); System.in.read(); } }

在默認棧大小情況下,程序拋出棧溢出錯誤并終止線程時,方法遞歸調用了5961次,如圖1.1所示。

圖1.1 使用默認棧大小時的棧溢出

在默認棧大小的情況下,多次運行代碼清單1-1的代碼,得出的結果是相差不大的。在發生StackOverflowError時,進程并沒有結束,因為一個線程的StackOverflowError并不影響整個進程。

現在我們將配置JVM的啟動參數-Xss,以調整虛擬機棧的大小為256k。如果你是使用idea運行本例代碼,可直接在VM options配置加上-Xss256K。如果你是使用java命令運行,可在java命令后面加上-Xss256k,啟動命令如下。

java -Xss256k -jar RecursionAlgorithmMain.jar

在調整棧的大小后,運行代碼清單1-1的代碼,sigma方法一共遞歸調用了1601次,如圖1.2所示。

圖1.2 棧大小為256K時的棧溢出

這與調整棧大小之前似乎存在著某種關系,用棧大小調整之前程序發生棧溢出時方法的調用次數除以棧大小調整后的,結果約是4。這是不是說明棧的大小默認為1024K呢。當然,以這個測試結果來說明其實并不嚴謹。

我們可以通過打印虛擬機參數查看默認的棧大小。使用jinfo[1]命令行工具可查看某個Java進程當前虛擬機棧的大小,這是jdk提供的工具,不需要額外下載安裝。使用jinfo查看Java進程線程棧大小如下:

wjy$ jinfo -flag ThreadStackSize 29643

其中29643為進程ID。在不修改任何配置的情況下,在64位Linux系統上執行jinfo命令,查詢出來的默認棧大小為1M,如圖1.3所示。

圖1.3 jinfo查看默認線程棧大小

棧大小除了可以使用jinfo命令行工具查看之外,我們還可以通過NAT工具[2]查看。使用NAT還能查看方法區的大小。以使用Java命令啟動Java進程為例,在Java命令后面加上開啟NAT的配置參數NativeMemoryTracking,如下:

java -XX:NativeMemoryTracking=summary -jar xxx.jar

進程啟動后,可通過jcmd[3]命令行工具查看該進程的內存占用信息,如圖1.4所示。

圖1.4 NAT打印內存占用信息

從圖1.4中,我們能看到當前進程Java堆分配的大小、用于存儲類元數據信息使用的內存、線程棧總共占用的內存等。圖中有每個參數的詳細說明,這里不再詳細說明。從線程棧信息來看,被查看的進程當前線程數為63,使用內存為63696K,也就是每個線程棧占用1M內存。

NAT工具也用于排查內存泄露問題,當項目中依賴了一些使用直接內存的第三方jar包時,可能會因為使用不當而造成內存泄露。如堆內存沒有用滿,但top命令[4]查看內存使用率卻接近百分百,這種情況就很有可能是程序使用堆外直接內存造成的。

-Xss參數在多線程項目中常用于JVM調優。假設項目中開啟1024個線程,那么使用默認棧大小的情況下,虛擬機棧將會占用1G的內存,而如果將棧大小調整為256K,虛擬機將只花費256M內存用于1024個棧的分配。

最后,我們也可以在HotSpot源碼中找到關于棧大小的設置[5]。以64位Linux操作系統為例,默認棧大小為1M,編譯線程的棧大小為4M,如代碼清單1-2所示。

代碼清單1-2 獲取默認棧大小的方法

 // return default stack size for thr_type
 size_t os::Linux::default_stack_size(os::ThreadType thr_type) {
      // default stack size (compiler thread needs larger stack)
     #ifdef AMD64
           size_t s = (thr_type == os::compiler_thread ? 4 * M : 1 * M);
     #else
           size_t s = (thr_type == os::compiler_thread ? 2 * M : 512 * K);
     #endif // AMD64
      return s;
}

棧也有最小值,在不同的操作系統及CPU環境下,棧的最小值也不一樣。如在64位的Linxu系統下,使用java命令啟動一個jar包并將-Xss配置為128K,進程將會異常終止,并提示創建Java虛擬機失敗,要求棧最小值為228K。如圖1.5所示。

圖1.5 設置棧大小為128k進程啟動失敗

虛擬機棧的最小值在虛擬機啟動時解析完全局參數之后調用os::init_2方法設置。虛擬機棧的最小值受當前系統是32位還是64位的影響,也受系統頁大小影響。在64位Linxu操作系統下,HotSopt所允許設置的棧的最小值為228K[6],如代碼清單1-3所示。

代碼清單1-3 設置棧的最小值

 // os::init_2(void)
 os::Linux::min_stack_allowed = MAX2(os::Linux::min_stack_allowed,
            (size_t)(StackYellowPages+StackRedPages+StackShadowPages) * Linux::page_size()   + (2*BytesPerWord COMPILER2_PRESENT(+1)) * Linux::vm_default_page_size()); 

注釋:

[1] jinfo是JDK提供的命令行工具,可以用來查看正在運行的java應用程序的擴展參數。

[2] Native Memory Tracking是一個Java Hotspot VM新特性,用于跟蹤熱點VM的內部內存使用情況。

[3] jcmd是JDK提供的命令行工具,使用jcmd可訪問NMT數據。

[4] Linux操作系統提供的top命令可用于查看系統當前每個進程對內存的占用率以及CPU的使用率等信息。

[5] 源碼在hotspot/src/os_cpu/linux_x86/vm/os_linux_x86.cpp文件中,方法名為default_stack_size。

[6] 源碼在hotspot/src/os/linux/vm/os_linux.cpp文件中

主站蜘蛛池模板: 沅江市| 博客| 东阿县| 高淳县| 奉新县| 隆安县| 巴塘县| 大厂| 广安市| 石泉县| 阿克陶县| 广昌县| 互助| 凤翔县| 广元市| 泸溪县| 慈溪市| 岚皋县| 九寨沟县| 黔西县| 新营市| 罗田县| 清原| 德阳市| 梅河口市| 于田县| 东宁县| 碌曲县| 马山县| 望城县| 收藏| 武宣县| 南涧| 榆树市| 长寿区| 唐山市| 万安县| 洛阳市| 蒙自县| 霍林郭勒市| 墨竹工卡县|