Java的最大棧深度與JVM核心知識(shí)介紹
一、Java最大支持棧深度有多大?
從Java運(yùn)行時(shí)數(shù)據(jù)區(qū)域我們知道,線(xiàn)程中的 棧結(jié)構(gòu)如下:

每個(gè)棧幀包含:本地變量表,操作數(shù)棧,動(dòng)態(tài)鏈接,返回地址等東西...
也就是說(shuō)棧調(diào)用深度越大,棧幀就越多,就越耗內(nèi)存。
1、測(cè)試案例
1.1、測(cè)試線(xiàn)程棧大小對(duì)棧深度的影響
下面我們用一個(gè)測(cè)試?yán)觼?lái)說(shuō)明:
有如下遞歸方法:
public class StackTest {
private int count = 0;
public void recursiveCalls(String a){
count++;
System.out.println("stack depth: " + count);
recursiveCalls(a);
}
public void test(){
try {
recursiveCalls("a");
} catch (Exception e) {
System.out.println(e);
}
}
public static void main(String[] args) {
new StackTest().test();
}
}
我們?cè)O(shè)置啟動(dòng)參數(shù)
-Xms256m -Xmx256m -Xmn128m -Xss256k
輸出內(nèi)容:
stack depth: 1556
Exception in thread "main" java.lang.StackOverflowError
at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)
可以發(fā)現(xiàn),棧深度為1556的時(shí)候,就報(bào) StackOverflowError了。
接下來(lái)我們調(diào)整-Xss線(xiàn)程棧大小為 512k,輸出內(nèi)容:
stack depth: 3249
Exception in thread "main" java.lang.StackOverflowError
at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:579)
發(fā)現(xiàn)棧深度變味了3249,說(shuō)明了:
隨著線(xiàn)程棧的大小越大,能夠支持越多的方法調(diào)用,也即是能夠存儲(chǔ)更多的棧幀。
1.2、測(cè)試方法參數(shù)個(gè)對(duì)棧深度的影響
這里我們固定設(shè)置-Xss為256k。
我們知道此時(shí)的深度為:1556。
接下來(lái)我們給方法添加參數(shù):
public class StackTest {
private int count = 0;
public void recursiveCalls(String a){
count++;
System.out.println("stack depth: " + count);
recursiveCalls(a);
}
public void test(){
try {
recursiveCalls("a");
} catch (Exception e) {
System.out.println(e);
}
}
public static void main(String[] args) {
new StackTest().test();
}
}
為何要添加參數(shù)呢,因?yàn)樘砑訁?shù)之后,棧幀中的本地變量表就會(huì)增加內(nèi)容,我們可以嘗試使用以下命令查看下Class文件的匯編指令:
javap -v StackTest.class
可以發(fā)現(xiàn)recursiveCalls方法的本地變量表的確增加了,對(duì)應(yīng)方法的入?yún)?a:
LocalVariableTable:
Start Length Slot Name Signature
0 44 0 this Lcom/itzhai/jvm/stacks/StackTest;
0 44 1 a Ljava/lang/String;
這個(gè)時(shí)候我們?cè)趫?zhí)行程序看看結(jié)果:
stack depth: 1318
Exception in thread "main" java.lang.StackOverflowError
at java.nio.Buffer.<init>(Buffer.java:201)
可以發(fā)現(xiàn),棧深度由原來(lái)的1556編程了1318。
可以得出結(jié)論:
局部變量表內(nèi)容越多,那么棧幀就越大,棧深度就越小。
2、結(jié)論
- 隨著線(xiàn)程棧的大小越大,能夠支持越多的方法調(diào)用,也即是能夠存儲(chǔ)更多的棧幀;局部變量表內(nèi)容越多,那么棧幀就越大,棧深度就越小。
- 我們?cè)谠u(píng)審寫(xiě)代碼的時(shí)候,發(fā)現(xiàn)了堆棧溢出,可以查看下對(duì)應(yīng)類(lèi)的本地變量表,是不是太多了,可不可以?xún)?yōu)化下代碼,或者加大下線(xiàn)程棧的大小,以增加棧的深度。
二、重溫JVM知識(shí)1. JDK,JRE,JVM的聯(lián)系是啥?
- JVM Java Virtual Machine
- JDK Java Development Kit
- JRE Java Runtime Environment
直接上官網(wǎng)上的介紹的圖片,一目了然。

2. JVM的作用是啥?

JVM有2個(gè)特別有意思的特性,語(yǔ)言無(wú)關(guān)性和平臺(tái)無(wú)關(guān)性。
- 語(yǔ)言無(wú)關(guān)性:是指實(shí)現(xiàn)了Java虛擬機(jī)規(guī)范的語(yǔ)言對(duì)可以在JVM上運(yùn)行,如Groovy,和在大數(shù)據(jù)領(lǐng)域比較火的語(yǔ)言Scala,因?yàn)镴VM最終運(yùn)行的是class文件,只要最終的class文件復(fù)合規(guī)范就可以在JVM上運(yùn)行。
- 平臺(tái)無(wú)關(guān)性:是指安裝在不同平臺(tái)的JVM會(huì)把class文件解釋為本地的機(jī)器指令,從而實(shí)現(xiàn)Write Once,Run Anywhere
3.JVM運(yùn)行時(shí)數(shù)據(jù)區(qū)
Java虛擬機(jī)在執(zhí)行Java程序的過(guò)程中會(huì)把它所管理的內(nèi)存劃分為若干個(gè)不同的數(shù)據(jù)區(qū)域。這些區(qū)域都有各自的用途,以及創(chuàng)建和銷(xiāo)毀的時(shí)間,有的區(qū)域隨著虛擬機(jī)進(jìn)程的啟動(dòng)而存在,有些區(qū)域則依賴(lài)用戶(hù)線(xiàn)程的啟動(dòng)和結(jié)束而建立和銷(xiāo)毀。
Java虛擬機(jī)所管理的內(nèi)存將會(huì)包括以下幾個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū)域

其中方法區(qū)和堆是所有線(xiàn)程共享的數(shù)據(jù)區(qū)程序計(jì)數(shù)器,虛擬機(jī)棧,本地方法棧是線(xiàn)程隔離的數(shù)據(jù)區(qū),畫(huà)一個(gè)邏輯圖

3.1程序計(jì)數(shù)器
程序計(jì)數(shù)器是一塊較小的內(nèi)存空間,它可以看作是當(dāng)前線(xiàn)程所執(zhí)行的字節(jié)碼的行號(hào)指示器
為什么要記錄當(dāng)前線(xiàn)程所執(zhí)行的字節(jié)碼的行號(hào)?直接執(zhí)行完不就可以了嗎?
因?yàn)榇a是在線(xiàn)程中運(yùn)行的,線(xiàn)程有可能被掛起。即CPU一會(huì)執(zhí)行線(xiàn)程A,線(xiàn)程A還沒(méi)有執(zhí)行完被掛起了,接著執(zhí)行線(xiàn)程B,最后又來(lái)執(zhí)行線(xiàn)程A了,CPU得知道執(zhí)行線(xiàn)程A的哪一部分指令,線(xiàn)程計(jì)數(shù)器會(huì)告訴CPU。
3.2虛擬機(jī)棧
虛擬機(jī)棧存儲(chǔ)當(dāng)前線(xiàn)程運(yùn)行方法所需要的數(shù)據(jù),指令,返回地址等。
虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表,操作數(shù)棧,動(dòng)態(tài)鏈接,方法出口等信息。每個(gè)方法從調(diào)用直至執(zhí)行完成的過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧道出棧的過(guò)程。
局部變量表存儲(chǔ)存儲(chǔ)局部變量,是一個(gè)定長(zhǎng)為32位的局部變量空間。其中64位長(zhǎng)度的long和double類(lèi)型的數(shù)據(jù)會(huì)占用2個(gè)局部變量空間(Slot),其余的數(shù)據(jù)類(lèi)型只占用一個(gè)。引用類(lèi)型(new出來(lái)的對(duì)象)如何存儲(chǔ)?看下圖
public int methodOne(int a, int b) {
Object obj = new Object();
return a + b;}

如果局部變量是Java的8種基本基本數(shù)據(jù)類(lèi)型,則存在局部變量表中,如果是引用類(lèi)型。如String,局部變量表中存的是引用,而實(shí)例在堆中。
假如methodOne方法調(diào)用methodTwo方法時(shí), 虛擬機(jī)棧的情況如下:

當(dāng)虛擬機(jī)棧無(wú)法再放下棧幀的時(shí)候,就會(huì)出現(xiàn)StackOverflowError。
接著解釋一下操作數(shù)棧,還是比較容易理解的假如Test.java中有如下方法,
public int getSum(int a, int b) {
return a + b;
}
反編譯生成的Test.class文件,并輸出到show.txt中
javap -v Test.class > show.txt
show.txt的內(nèi)容如下
public int getSum(int, int);
descriptor: (II)I
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: iload_1
1: iload_2
2: iadd
3: ireturn
LineNumberTable:
line 12: 0
解釋一下上面的語(yǔ)句
iload_1:局部變量1壓棧
iload_2:局部變量2壓棧
iadd:棧頂2個(gè)元素相加,計(jì)算結(jié)果壓棧
簡(jiǎn)單2個(gè)數(shù)相加都會(huì)用到棧,這個(gè)棧就是操作數(shù)棧,更不用說(shuō)復(fù)雜的語(yǔ)法了
3.3本地方法棧
本地方法棧(Native Method Stack)與虛擬機(jī)棧鎖發(fā)揮的作用是非常相似的,他們之間的區(qū)別不過(guò)是虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù),而本地方法棧則為虛擬機(jī)使用到的Native方法服務(wù)。
3.4Java堆
對(duì)于大多數(shù)應(yīng)用來(lái)說(shuō),Java堆(Java Heap)是Java虛擬機(jī)鎖管理的內(nèi)存中最大的一塊。Java堆是所有線(xiàn)程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。此內(nèi)存區(qū)域的唯一目的就是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例都在這里分配內(nèi)存。
3.5方法區(qū)
方法區(qū)(Method Area)與Java堆一樣,是各個(gè)線(xiàn)程共享的內(nèi)存區(qū)域,它用于存儲(chǔ)已被虛擬機(jī)加載的類(lèi)信息,常量,靜態(tài)變量,即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。
4.JVM內(nèi)存模型

由顏色可以看出,jdk1.8之前,堆內(nèi)存被分為新生代,老年代,永久帶,jdk1.8及以后堆內(nèi)存被分成了新生代和老年代。新生代的區(qū)域又分為eden區(qū),s0區(qū),s1區(qū),默認(rèn)比例是8:1:1,元空間可以理解為直接的物理內(nèi)存

以上就是Java的最大棧深度與JVM核心知識(shí)介紹的詳細(xì)內(nèi)容,更多關(guān)于Java最大棧深度與JVM核心知識(shí)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java連接hdfs ha和調(diào)用mapreduce jar示例
這篇文章主要介紹了Java API連接HDFS HA和調(diào)用MapReduce jar包,需要的朋友可以參考下2014-03-03
springboot配置http跳轉(zhuǎn)https的過(guò)程
SSL是為網(wǎng)絡(luò)通信提供安全以及保證數(shù)據(jù)完整性的的一種安全協(xié)議,SSL在網(wǎng)絡(luò)傳輸層對(duì)網(wǎng)絡(luò)連接進(jìn)行加密,這篇文章主要介紹了springboot配置http跳轉(zhuǎn)https的過(guò)程,需要的朋友可以參考下2023-04-04
java使用RSA加密方式實(shí)現(xiàn)數(shù)據(jù)加密解密的代碼
這篇文章給大家分享java使用RSA加密方式實(shí)現(xiàn)數(shù)據(jù)加密解密,通過(guò)實(shí)例代碼文字相結(jié)合給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友參考下2019-11-11
關(guān)于通過(guò)Java連接mysql對(duì)反斜杠”\“轉(zhuǎn)義的測(cè)試詳解
這篇文章主要給大家介紹了關(guān)于通過(guò)Java連接mysql對(duì)反斜杠”\“轉(zhuǎn)義的測(cè)試的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家理解反斜杠”\“轉(zhuǎn)義具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。2017-06-06
淺談Maven鏡像更換為阿里云中央倉(cāng)庫(kù)(精)
本篇文章主要介紹了Maven鏡像更換為阿里云中央倉(cāng)庫(kù)(精),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-12-12
SpringBoot之@ConditionalOnProperty注解使用方法
在平時(shí)業(yè)務(wù)中,我們需要在配置文件中配置某個(gè)屬性來(lái)決定是否需要將某些類(lèi)進(jìn)行注入,讓Spring進(jìn)行管理,而@ConditionalOnProperty能夠?qū)崿F(xiàn)該功能,文中有詳細(xì)的代碼示例,需要的朋友可以參考下2023-05-05
Spring如何集成ibatis項(xiàng)目并實(shí)現(xiàn)dao層基類(lèi)封裝
這篇文章主要介紹了Spring如何集成ibatis項(xiàng)目并實(shí)現(xiàn)dao層基類(lèi)封裝,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
java遍歷途中修改數(shù)據(jù)及刪除數(shù)據(jù)的方法總結(jié)
在使用java的集合類(lèi)遍歷數(shù)據(jù)的時(shí)候,在某些情況下可能需要對(duì)某些數(shù)據(jù)進(jìn)行刪除,下面這篇文章主要給大家介紹了關(guān)于java遍歷途中修改數(shù)據(jù)及刪除數(shù)據(jù)的方法總結(jié),需要的朋友可以參考下2023-10-10
Spring Cloud Config實(shí)現(xiàn)分布式配置中心
這篇文章主要介紹了Spring Cloud Config實(shí)現(xiàn)分布式配置中心,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04

