一. java結構體系
Description of Java Conceptual Diagram(java結構)
![5.java記憶體模型詳細解析](https://i.iter01.com/images/6d5e6c7a39c7dd6bcc7350528c08add1f77a9d52c19a6560b339ca64bd91d655.png)
我們經常說到JVM調優,JVM和JDK到底什麼關係,大家知道麼?這是java基礎。
這幅圖很重要,一定要了解其結構。這是jdk的結構圖。從結構上可以看出java結構體系, JDK主要包含兩部分:
第一部分:是java 工具(Tools&Tool APIs)
比如java, javac, javap等命令. 我們常用的命令都在這裡
第二部分: JRE(全稱:Java Runtime Enveriment), jre是Java的核心,。
jre裡面定義了java執行時需要的核心類庫, 比如:我們常用的lang包, util包, Math包, Collection包等等.這裡還有一個很重要的部分JVM(最後一部分青色的) java 虛擬機器, 這部分也是屬於jre, 是java執行時環境的一部分. 下面來詳細看看:
- 最底層的是Java Virtual Machine: java虛擬機器
- 常用的基礎類庫:lang and util。在這裡定義了我們常用的Math、Collections、Regular Expressions(正規表示式),Logging日誌,Reflection反射等等。
- 其他的擴充套件類庫:Beans,Security,Serialization序列化,Networking網路,JNI,Date and Time,Input/Output等。
- 整合一體化類庫:JDBC資料庫連線,jndi,scripting等。
- 使用者介面工具:User Interface Toolkits。
- 部署工具:Deployment等。
從上面就可看出,jvm是整個jdk的最底層。jvm是jdk的一部分。
二. java語言的跨平臺特性
1. java語言是如何實現跨平臺的?
![5.java記憶體模型詳細解析](https://i.iter01.com/images/d85b4deb76148acf0ada68f35a5d2002ae3023e34792c6705b3f120f77a3a8d8.png)
跨平臺指的是, 程式設計師開發出的一套程式碼, 在windows平臺上能執行, 在linux上也能執行, 在mac上也能執行. 我們知道, 機器最終執行的指令都是二進位制指令. 同樣的程式碼, 在windows上生成的二進位制指令可能是0101, 但是在linux上是1001, 而在mac上是1011。這樣同樣的程式碼, 如果要想在不同的平臺執行, 放到相應的平臺, 就要修改程式碼, 而java卻不用, 那麼java這種跨平臺特性是怎麼做到的呢?
原因在於jdk, 我們最終是將程式編譯成二進位制碼,把他丟在jvm上執行的, 而jvm是jre的一部分. 我們在不同的平臺下載的jdk是不同的. windows平臺要選擇下載適用於windows的jdk, linux要選擇適用於linux的jdk, mac要選擇適用於mac的jdk. 不同平臺的jvm針對該平臺有一個特定的實現, 正是這種特點的實現, 讓java實現了跨平臺。
2. 延伸思考
通過上面的分析,我們知道能夠實現跨平臺是因為jvm封裝了變化。我們經常說進行jvm調優,那麼在不同平臺的調優引數可以通用麼?顯然是不可以的。不同平臺的jvm尤其個性化差異。
封裝變化的部分是JDK中的jvm,JVM的整體結構是怎樣的呢?來看下面一個部分。
三. JVM整體結構和記憶體模型
1.JVM由三部分組成:
- 類裝載子系統
- 執行時資料區(記憶體模型)
- 位元組碼執行引擎
![5.java記憶體模型詳細解析](https://i.iter01.com/images/4375b65c4610fca7e8afa53f62a56b7f06d2b860fdd7a8fd4a7f44da8ddef88b.png)
其中類裝載子系統是C++實現的, 他把類載入進來, 放入到虛擬機器中. 這一塊就是之前分析過的類載入器載入類,採用雙親委派機制,把類載入進來放入到jvm虛擬機器中。
然後, 位元組碼執行引擎去虛擬機器中讀取資料. 位元組碼執行引擎也是c++實現的. 我們重點研究執行時資料區。
2.執行時資料區的構成
執行時資料區主要由5個部分構成: 堆,棧,本地方法棧,方法區,程式計數器。
3.JVM三部分密切配合工作
下面我們來看看一個程式執行的時候, 類裝載子系統, 執行時資料區, 位元組碼執行引擎是如何密切配合工作的?
我們舉個例子來說一下:
package com.lxl.jvm;
public class Math {
public static int initData = 666;
public static User user = new User();
public int compute() {
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
public static void main(String[] args) {
Math math = new Math();
math.compute();
}
}
當我們在執行main方法的時候, 都做了什麼事情呢?
第一步: 類載入子系統載入Math.class類, 然後將其丟到記憶體區域, 這個就是前面部落格研究的部分,類載入的過程, 我們看原始碼也發現,裡面好多程式碼都是native本地的, 是c++實現的
第二步: 在記憶體中處理位元組碼檔案, 這一部分內容較多, 也是我們研究的重點, 後面會對每一個部分詳細說
第三步: 由位元組碼執行引擎執行java虛擬機器中的記憶體程式碼, 而位元組碼執行引擎也是由c++實現的
這裡最核心的部分是第二部分執行時資料區(記憶體模型), 我們後面的調優, 都是針對這個區域來進行的.
下面詳細來說記憶體區域
![5.java記憶體模型詳細解析](https://i.iter01.com/images/04279a984afefe0ca528c3146fbad3926aecd6abeb0e5a421ec7dfa854b71439.png)
這是java的記憶體區域, 記憶體區域幹什麼呢?記憶體區域其實就是放資料的,各種各樣的資料j放在不同的記憶體區域
四. 棧
棧是用來存放變數的
4.1. 棧空間
還是用Math的例子來說,當程式執行的時候, 會建立一個執行緒, 建立執行緒的時候, 就會在大塊的棧空間中分配一塊小空間, 用來存放當前要執行的執行緒的變數
public static void main(String[] args) {
Math math = new Math();
math.compute();
}
比如,這段程式碼要執行,首先會在大塊的棧空間中給他分配一塊小空間. 這裡的math這個區域性變數就會被儲存在分配的小空間裡面.
在這裡面我們執行了math.compute()方法, 我們看看compute方法內部實現
public int compute() {
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
這裡面有a, b, c這樣的區域性變數, 這些區域性變數放在那裡呢? 也放在上面分配的棧小空間裡面.
![5.java記憶體模型詳細解析](https://i.iter01.com/images/abdaa94a703931b7832cc7f6e2ebe8473b6b6fac7fbeba805122293e3b23fd87.png)
效果如上圖, 在棧空間中, 分配一塊小的區域, 用來存放Math類中的區域性變數
如果再有一個執行緒呢? 我們就會再次在棧空間中分配一塊小的空間, 用來存放新的執行緒內部的變數
![5.java記憶體模型詳細解析](https://i.iter01.com/images/b68bfb5de2ca37f123f3d91f8a81f078fce0a4f81522e2db4e87f8c50f0eac7a.png)
同樣是變數, main方法中的變數和compute()方法中的變數放在一起麼?他們是怎麼放得呢?這就涉及到棧幀的概念。
4.2. 棧幀
1.什麼是棧幀呢?
package com.lxl.jvm;
public class Math {
public static int initData = 666;
public static User user = new User();
public int compute() {
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
public static void main(String[] args) {
Math math = new Math();
math.compute();
}
}
還是這段程式碼, 我們來看一下, 當我們啟動一個執行緒執行main方法的時候, 一個新的執行緒啟動,會現在棧空間中分配一塊小的棧空間。然後在棧空間中分配一塊區域給main方法,這塊區域就叫做棧幀空間.
當程式執行到compute()計算方法的時候, 會要去呼叫compute()方法, 這時候會再分配一個棧幀空間, 給compute()方法使用.
2.為什麼要將一個執行緒中的不同方法放在不同的棧幀空間裡面呢?
一方面: 我們不同方法裡的區域性變數是不能相互訪問的. 比如compute的a,b,c在main裡不能被訪問到。使用棧幀做了很好的隔離作用。
另一方面: 方便垃圾回收, 一個方法用完了, 值也返回了, 那他裡面的變數就是垃圾了, 後面直接回收這個棧幀就好了.
![5.java記憶體模型詳細解析](https://i.iter01.com/images/769b6be18fac6a3006a3a49b2b7ccbc212fb961339a3fbc7bca95f61da697bc3.png)
如下圖, 在Math中兩個方法, 當執行到main方法的時候, 會將main方法放到一塊棧幀空間, 這裡面僅僅是儲存main方法中的區域性變數, 當執行到compute方法的時候, 這時會開闢一塊compute棧幀空間, 這部分空間僅存放compute()方法的區域性變數.
不同的方法開闢出不同的記憶體空間, 這樣方便我們各個方法的區域性變數進行管理, 同時也方便垃圾回收.
3.java記憶體模型中的棧演算法
我們學過棧演算法, 棧演算法是先進後出的. 那麼我們的記憶體模型裡的棧和演算法裡的棧一樣麼?有關聯麼?
我們java記憶體模型中的棧使用的就是棧演算法, 先進後出.舉個例子, 還是這段程式碼
package com.lxl.jvm;
public class Math {
public static int initData = 666;
public static User user = new User();
public int compute() {
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
public int add() {
int a = 1;
int b = 2;
int c = a + b;
return c;
}
public static void main(String[] args) {
Math math = new Math();
math.compute();
math.add(); // 注意這裡呼叫了兩次compute()方法
}
}
這時候載入的記憶體模型是什麼樣呢?
![5.java記憶體模型詳細解析](https://i.iter01.com/images/d1b9927cf19d2c1d94f3d291bab07c7c04d025f6d65ad91a3e6ed875ba1e05b3.png)
- 最先進入棧的是main方法, 會首先線上程棧中分配一塊棧幀空間給main方法。
- main方法裡面呼叫了compute方法, 然後會在建立一個compute方法的棧幀空間, 我們知道compute方法後載入,但是他卻會先執行, 執行完以後, compute中的區域性變數就會被回收, 那麼也就是出棧.
- 然後在執行add方法,給add方法分配一塊棧幀空間。add執行完以後出棧。
- 最後執行完main方法, main方法最後出棧. 這個演算法剛好驗證了先進後出. 後載入的方法會被先執行. 也符合程式執行的邏輯。
4.3 棧幀的內部構成
我們上面說了, 每個方法在執行的時候都會有一塊對應的棧幀空間, 那麼棧幀空間內部的結構是怎樣的呢?
棧幀內部有很多部分, 我們主要關注下面這四個部分:
1. 區域性變數表
2. 運算元棧
3. 動態連結
4. 方法出口
4.2.1 區域性變數表: 存放區域性變數
區域性變數表,顧名思義,用來存放區域性變數的。
4.2.2 運算元棧
那麼運算元棧,動態連結, 方法出口他們是幹什麼的呢? 我們用例子來說明運算元棧
![5.java記憶體模型詳細解析](https://i.iter01.com/images/ca13cd8819aad8b7d6966caf235a16f1f1e885c9056ad90b70511ee9a69c105c.png)
那麼這四個部分是如何工作的呢?
我們用程式碼的執行過程來對照分析.
我們要看的是jvm反編譯後的位元組碼檔案, 使用javap命令生成反編譯位元組碼檔案.
javap命令是幹什麼用的呢? 我們可以檢視javap的幫助文件
![5.java記憶體模型詳細解析](https://i.iter01.com/images/3397c1c26d01566415953220ec578e9775fa67729975ca2f11aeba5fed697756.png)
主要使用javap -c和javap -v
javap -c: 對程式碼進行反編譯
javap -v: 輸出附加資訊, 他比javap -c會輸出更多的內容
下面使用命令生成一個Math.class的位元組碼檔案. 我們將其生成到檔案
javap -c Math.class > Math.txt
開啟Math.txt檔案, 如下. 這就是對java位元組碼反編譯成jvm組合語言.
Compiled from "Math.java"
public class com.lxl.jvm.Math {
public static int initData;
public static com.lxl.jvm.User user;
public com.lxl.jvm.Math();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public int compute();
Code:
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: bipush 10
9: imul
10: istore_3
11: iload_3
12: ireturn
public static void main(java.lang.String[]);
Code:
0: new #2 // class com/lxl/jvm/Math
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: aload_1
9: invokevirtual #4 // Method compute:()I
12: pop
13: return
static {};
Code:
0: sipush 666
3: putstatic #5 // Field initData:I
6: new #6 // class com/lxl/jvm/User
9: dup
10: invokespecial #7 // Method com/lxl/jvm/User."<init>":()V
13: putstatic #8 // Field user:Lcom/lxl/jvm/User;
16: return
}
這就是jvm生成的反編譯位元組碼檔案.
要想看懂這裡面的內容, 我們需要知道jvm文件手冊. 現在我們不會沒關係, 參考文章(https://www.cnblogs.com/ITPower/p/13228166.html)最後面的內容, 遇到了就去後面查就行了
我們以compute()方法為例來說說這個方法是如何在在棧中處理的
原始碼
public int compute() {
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
反編譯後的jvm指令
public int compute();
Code:
0: iconst_1
1: istore_1
2: iconst_2
3: istore_2
4: iload_1
5: iload_2
6: iadd
7: bipush 10
9: imul
10: istore_3
11: iload_3
12: ireturn
jvm的反編譯程式碼是什麼意思呢? 我們對照著查詢手冊
0: iconst_1 將int型別常量1壓入運算元棧, 這句話的意思就是先把int a=1;中的1先壓入運算元棧
![5.java記憶體模型詳細解析](https://i.iter01.com/images/1c4588c88f22a2299173ff499dbce8e93ae4524d8852e8beb2379e264a50966f.png)
1: istore_1 將int型別值存入區域性變數1-->意思是將int a=1; 中的a變數存入區域性變數表
注意: 這裡的1不是變數的值, 他指的是區域性變數的一個下標. 我們看手冊上有區域性變數0,1,2,3
![5.java記憶體模型詳細解析](https://i.iter01.com/images/a30382aad959990f1b33eb5176b3e95d4c3b593a3e2900fab05211164cbc9170.png)
0表示的是this, 1表示將變數放入區域性變數的第二個位置, 2表示放入第三個位置.
對應到compute()方法,0表示的是this, 1表示的區域性變數a, 2表示區域性變數b,3表示區域性變數c
1: istore_1 將int型別值存入區域性變數1-->意思是將int a=1; 中的a放入區域性變數表的第二個位置, 然後讓運算元棧中的1出棧, 賦值給a
![5.java記憶體模型詳細解析](https://i.iter01.com/images/bffd59004f96e18b44fb5b1a4c1328ac53f3c1f66eb9e925b0b9eac7110714fe.png)
![5.java記憶體模型詳細解析](https://i.iter01.com/images/3acf9dfac9cb2d23d3a2a88fa4042a7a462e2ef69c7fa7c7b0000784aed911b6.png)
2: iconst_2 將int型別常量2壓入棧-->意思是將int b=2;中的常量2 壓入運算元棧
![5.java記憶體模型詳細解析](https://i.iter01.com/images/6911dffccff1fd3f698821b2a867244677f94496cd423a03ab6c515f9c93c42c.png)
3: istore_2 將int型別值存入區域性變數2 -->意思是將int b=2;中的變數b存入區域性變數表中第三個位置, 然後讓運算元棧中的數字2出棧, 給區域性變數表中的b賦值為2
![5.java記憶體模型詳細解析](https://i.iter01.com/images/ab5992915ab3d305d8aae5d17e57fafa1891d2f366a018ac43cc2a8ab91c1e47.png)
4: iload_1 從區域性變數1中裝載int型別值--->這句話的意思是, 將運算元1從運算元棧取出, 轉入區域性變數表中的a, 現在區域性變數表中a=1
要想更好的理解iload_1,我們要先來研究程式計數器。
程式計數器
在JVM虛擬機器中,程式計數器是其中的一個組成部分。
![5.java記憶體模型詳細解析](https://i.iter01.com/images/4375b65c4610fca7e8afa53f62a56b7f06d2b860fdd7a8fd4a7f44da8ddef88b.png)
程式計數器是每一個執行緒獨有的, 他用來存放馬上要執行的那行程式碼的記憶體位置, 也可以叫行號. 我們看到jvm反編譯程式碼裡,都會有0 1 2 3這樣的位置(如下圖), 我們可以將其認為是一個標識.而程式計數器可以簡單理解為是記錄這些數字的. 而實際上這些數字對應的是記憶體裡的地址
![5.java記憶體模型詳細解析](https://i.iter01.com/images/36de3c3ce999b0628f76cb4d332e2f6493bfcb260582c483657a946290cab0bf.png)
當位元組碼執行引擎執行到第4行的時候,將執行到4: iload_1, 我們可以簡單理解為程式計數器記錄的程式碼位置是4. 我們的方法Math.class是放在方法區的, 由位元組碼執行引擎執行, 每次執行完一行程式碼, 位元組碼執行引擎都會修改程式計數器的位置, 讓其向下移動一位
![5.java記憶體模型詳細解析](https://i.iter01.com/images/6e83a539021a4460bcdfd76c89116851021f40b7701df31d359bf5a271699aef.png)
java虛擬機器為什麼要設計程式計數器呢?
因為多執行緒。當一個執行緒正在執行, 被另一個執行緒搶佔了cpu, 這時之前的執行緒就要掛起, 當執行緒2執行完以後, 再執行執行緒1. 那麼執行緒1之前執行到哪裡了呢? 程式計數器幫我們記錄了.
下面執行這句話
4: iload_1 從區域性變數1中裝載int型別值--> 意思是從區域性變數表的第二個位置取出int型別的變數值, 將其放入到運算元棧中.此時程式計數器指向的是4
![5.java記憶體模型詳細解析](https://i.iter01.com/images/7e724be6c343e680f829f4fe4deb91a6c55147d8dac52b3bc3a8ecbac5bb1529.png)
5: iload_2 從區域性變數2中裝載int型別值-->意思是將區域性變數中的第三個int型別的元素b的值取出來, 放到運算元棧, 此時程式計數器指向的是5
![5.java記憶體模型詳細解析](https://i.iter01.com/images/3390b5b6fa501433352f05fd30820a51765f3daf86531b59e4ad2ea787cd95b4.png)
6: iadd 執行int型別的加法 ---> 將兩個區域性變數表中的數取出, 進行加法操作, 此操作是在cpu中完成的, 將執行後的結果3在放入到運算元棧 ,此時程式計數器指向的是6
![5.java記憶體模型詳細解析](https://i.iter01.com/images/9a0d8f22bcb78fa114ccb5efc65e4b9ac7ef20f6e7f345aabbcbb2d02a36a929.png)
![5.java記憶體模型詳細解析](https://i.iter01.com/images/54df078a187cb84ed0c9c06c55ec393970c07245ddbdb68ba0b839c785dad520.png)
7: bipush 10 :將一個8位帶符號整數壓入棧 --> 這句話的意思是將10壓入運算元棧
![5.java記憶體模型詳細解析](https://i.iter01.com/images/0294a6aaa7007e3881790fe724a5a59a0c7a7ae65da632d89a4bdc27eeb7da6d.png)
我們發現這裡的位置是7, 但是下一個就變成了9, 那8哪裡去了呢? 其實這裡的0 1 2 3 ...都是對應的記憶體地址, 我們的乘數10也會佔用記憶體空間, 所以, 8的位置存的是乘數10
9: imul 執行int型別的乘法 --> 這個和iadd加法一樣, 首先將運算元棧中的3和10取出來, 在cpu裡面進行計算, 將計算的結果30在放回運算元棧
乘法操作是在cpu的暫存器中進行計算的. 我們這裡說的都是儲存在記憶體中.
![5.java記憶體模型詳細解析](https://i.iter01.com/images/9f081cd8aa28d6507605aa96e628914340e43bae58654b4bd8e5b5f3ef02913c.png)
10: istore_3 將int型別值存入區域性變數表中 ---> 意思是是將c這個變數放入區域性變數表, 然後讓運算元棧中的30出棧, 賦值給變數c
![5.java記憶體模型詳細解析](https://i.iter01.com/images/741df87a4d73e7366309cfda0d5175b92708474dfc16bc0da4f6b825e55ecd19.png)
11: iload_3 從區域性變數3中裝載int型別值 --> 將區域性變數表中取出第4個位置的值30, 裝進區域性變數表
![5.java記憶體模型詳細解析](https://i.iter01.com/images/0fe80cfd86e81e3d3ab89ad52548d6950a89600b6f4d91dba47b9f58b4ec2a0a.png)
12: ireturn 從方法中返回int型別的資料 --> 最後將得到的結果c返回.
這個方法中的變數是如何在運算元棧和區域性變數表中轉換的, 我們就知道了. 現在應該可以理解運算元棧和區域性變數表了吧~~~
總結:什麼是運算元棧?**
在運算的過程中, 常數1, 2, 10, 也需要有記憶體空間存放, 那麼它存在哪裡呢? 就儲存在運算元棧裡面
運算元棧就是在執行的過程中, 一塊臨時的記憶體中轉空間
4.3.3 動態連結
在之前說過什麼是動態連結: 參考文章: https://www.cnblogs.com/ITPower/p/13197220.html 搜尋:動態連結
靜態連結是在程式載入的時候一同被載入進來的. 通常用靜態常量, 靜態方法等, 因為他們在記憶體地址中只有一份, 所以, 為了效能, 就直接被載入進來了
而動態連結, 是使用的時候才會被載入進來的連結, 比如compute方法. 只要在執行到math.compute()方法的時候才會真的進行載入.
4.3.4 方法出口
當我們執行完compute()方法以後, 還要返回到main方法的math.comput()方法的位置, 那麼他怎麼返回回來呢?返回回來以後該執行哪一句程式碼了呢?在進入compute()方法之前,就在方法出口裡記錄好了, 我應該如何返回,返回到哪裡. 方法出口就是記錄一些方法的資訊的.
五. 堆和棧的關係
上面研究了compute()方法的棧幀空間,再來看一下main方法的棧幀空間。整體來說,都是一樣的,但有一塊需要說明一下,那就是區域性變數表。來看看下面的程式碼
public static void main(String[] args) {
Math math = new Math();
math.compute();
}
main方法的區域性變數和compute()有什麼區別呢? main方法中的math是一個物件. 我們知道通常物件是被建立在堆裡面的. 而math是在區域性變數表中, 記錄的是堆中new Math物件的地址。
說的明白一些,math裡存放的不是具體的內容,而是例項物件的地址。
![5.java記憶體模型詳細解析](https://i.iter01.com/images/a9f220b5044a9ff77ae6e688e2f469ca1702f8526407d051ec083d17b1983a7a.png)
那麼棧和堆的關係就出來了, 如果棧中有很多new物件, 這些物件是建立在堆裡面的. 棧裡面存的是這些堆中建立的物件的記憶體地址。
六. 方法區
我們可以通過javap -v Math.class > Math.txt命令, 列印更詳細的jvm反編譯後的程式碼
![5.java記憶體模型詳細解析](https://i.iter01.com/images/a6e3700b3cd034b54a8333d7ce76cf5dd945203da77c3d4c7af78b5c3e860cc7.png)
這次生成的程式碼,和使用javap -c生成的程式碼的區別是多了Constant pool常量池。這些常量池是放在哪裡的呢?放在方法區。這裡看到的常量池叫做執行時常量池。還有很多其他的常量池,比如:八大資料型別的物件常量池,字串常量池等。
這裡主要理解執行時常量池。執行時常量池放在方法區裡。
方法區主要有哪些元素呢?
常量 + 靜態變數 + 類元資訊(就是類的程式碼資訊)
在Math.class類中, 就有常量和靜態常量
public static int initData = 666;
public static User user = new User();
他們就放在方法區裡面. 這裡面 new User()是放在堆裡面的, 在堆中分配了一個記憶體地址,而user物件是放在方法區裡面的. 方法區中user物件指向了在堆中分配的記憶體空間。
堆和方法區的關係是: 方法區中物件引用的是堆中new出來的物件的地址
類元資訊: Math.class整個類中定義的內容就是類元資訊, 也放在方法區。
七. 本地方法棧
本地方法棧是有c++程式碼實現的方法. 方法名帶有native的程式碼.
比如:
new Thread().start();
這裡的start()呼叫的就是本地方法
![5.java記憶體模型詳細解析](https://i.iter01.com/images/b9cb147d605ccda53a81caabdcbc8e2cabda0fc1e01f9f19cd4fbf2db31fce7b.png)
這就是本地方法
本地方法棧: 執行的時候也需要有記憶體空間去儲存, 這些記憶體空間就是本地方法棧提供的
![5.java記憶體模型詳細解析](https://i.iter01.com/images/104d3ddc438404d0e1eb21ff1c18732dd4821d441a853069467e2b604df9bd05.png)
每一個執行緒都會分配一個棧空間,本地方法棧和程式計數器。如上圖main執行緒:包含執行緒棧,本地方法棧,程式計數器。