# 第一節、Java運行時數據區
**注意!!!**
JVM運行時的數據區域和JVM的內存模型不一樣,這兩個概念別搞混了!!!
JVM運行時的數據區域主要包括:程序計數器、虛擬機棧、堆、方法區、直接內存、本地方法棧。
## 一、程序計數器
:-: 
JVM為每個線程維護了一個獨立的程序計數器,因此程序計數器屬于線程私有的,在線程之間不共享。
程序計數器有如下兩個特性:
1. 在該區域中不會出現內存溢出錯誤。
2. 線程私有。
程序計數器舉例:
對java的Class文件使用反匯編命令
~~~
?javap -v xxx.class
~~~
可以得到類似下面圖片的內容,程序計數器可以簡單看成指向下一條要執行指令的地址編號,與操作系統的程序計數器寄存器(PC)的作用一樣,都是保存下一條運行指令的地址。
:-: 
## 二、虛擬機棧
虛擬機棧,簡稱棧,是JVM運行程序的關鍵內存模型。與虛擬機棧相關的兩個概念為:
* 棧:線程運行時所需的內存空間,每個線程運行時都有一個屬于自己的虛擬機棧,一個線程默認分配的空間為1M。
* 棧幀:棧中的存儲單元,運行一個方法所需要的內存空間,棧幀里面包含`方法參數、局部變量、返回值、返回地址等。`每一個方法的被調用到執行完畢對應著一個棧幀入棧到出棧的過程。

虛擬機棧特點:
* 每個棧(Stack)由棧幀(Stack Frame)組成,每個棧幀對應著線程中每次方法調用時所需的內存。
* 每個線程同一時刻只有一個活動的棧幀,即棧頂元素,對應正在執行代碼的方法。
* 棧是線程私有的。
* 有可能會導致棧溢出錯誤:StackOverFlowError。
:-: 
??可以使用IDEA的Debug模式查看棧和棧幀的內容:

在虛擬機棧中存在個**局部變量表**的結構,該結構存放了編譯期可知的各種基本參數(boolean、type、char、int、float、long、double)、對象引用和returnAddress類型。同時這些類型在局部變量表中存儲空間以槽(Slot)來表示,局部變量表所需要的內存空間在編譯期的時候就完全確定了,因此每個方法在運行時能給棧幀分配的局部變量表的槽位的數量也是完全確定的,不可以再改變。
**與虛擬機棧的相關問題??**
1. 垃圾回收是否會涉及虛擬機棧的區域?
答:
不會涉及!如果簡單的對JVM運行時數據區域進行劃分的話可以劃分為堆和棧結構(這也是傳統的C/C++的劃分方式),而垃圾回收器作用的位置主要位于堆區域,對棧結構沒有影響。
另外一方面,虛擬機棧對應的一個線程運行所需要的臨時空間,其棧元素棧幀(Stack Frame)的入棧和出棧對應著一次方法的調用和結束,因此會自動釋放所占用的空間,而不用垃圾回收器進行回收。
2. 棧內存分配越大越好嗎?
答:
需要看實際情況進行調優分配,例如在多線程的場景下應該盡可能的減少每個虛擬機棧的大小,但是同時也要確保其不會出現“StackOverFlowError”的錯誤。可以使用虛擬機參數`-Xss=size`進行修改。
補充:可能會出現“StackOverFlowError”錯誤的場景!
`對象轉化為Json格式數據的時候出現遞歸轉化!`
~~~
?@Data
?class Emp{
? ? ?private String name;
? ? ?@JsonIgnore // 加上這個注解,不然會遞歸轉化
? ? ?private Dept dept;
? ?
?}
??
?@Data
?class dept {
? ? ?private String name;
? ? ?private List<Emp> emps;
?}
~~~
3. 方法內的局部變量是否是線程安全的?
答:如果局部變量逃離了方法的作用范圍,則需要考慮線程安全的問題,否則不需要考慮。

**線程診斷??**
案例1:CPU被占用過多!
1. 先定位
* 使用top命令定位哪個進程對cpu占用的資源比較高。
~~~
?top
~~~

* 使用PS命令進一步定位該進程中哪個線程對cpu占用過高。
~~~
?ps -H -eo pid,tid,%cpu | grep pid
~~~
2. 使用JDK自帶工具jstack分析詳細的線程信息
~~~
?jstack pid
~~~
案例2:線程發生死鎖
仍然使用jstack命令,如果發生死鎖的話會有死鎖的提示。
### 2.1 運行時棧幀
參考:《深入理解Java虛擬機》第八章。
Java虛擬機把方法作為最基本的執行單元,“棧幀”就是支持JVM進行方法調用的數據結構,主要包括如下幾部分的內容:`局部變量表`、`操作數棧`、`動態連接`、`方法返回值地址`等。
#### 2.1.1 局部變量表
局部變量表是方法內一組變量值的存儲空間(可以看成一個數組),用變量槽來表示變量的存儲單位(可能是32位也可能是64位),局部變量表在編譯階段就可以確定出來了,該值會被記錄到Code屬性的max\_locals項中。
局部變量表中的會保存方法參數、對象的this引用和方法內部的局部變量。局部變量表的第0號索引就表示調用該方法對象的“this”引用。局部變量表也是作為GC Roots的一部分,如果在局部變量表的槽位中仍然有對某一個變量的引用,則垃圾回收作用不到,經常的做法是會將該變量手動的設置為null,這樣局部變量表就沒有對于該變量的引用了,垃圾回收器也就可以起作用。
> 備注:周志明老師并不推薦這種做法,具體可以查看《深入理解Java虛擬機》第298頁的內容。
#### 2.1.2 操作數棧
操作數棧與局部變量表一樣,其最大深度在編譯階段即可確定出來,每一個元素是32位字節。操作數棧與精簡指令集中的棧起到的作用類似,用于代碼的執行過程中變量的存取。
## 三、堆
Java堆是虛擬機管理的最大的一塊內存空間。《Java虛擬機規范》對堆的描述是:“所有的對象實例以及數組都應當在堆上分配。”但是現在隨著逃逸分析、棧上分配、標量替換等優化手段的發展,對象實例都分配在堆上開始變得不那么絕對了。同時值得一提的是在有些描述中說:“堆可以劃分為新生代、老年代、永久代...”等等這些描述是和特定的垃圾回收器相關的概念,對堆進行細分只是為了更好的進行垃圾回收;但是有些垃圾回收器這可能就沒有這些概念,《Java虛擬機規范》并沒有對堆進行進一步的劃分。
堆內存有如下兩個特點:
1. 堆內存對于所有線程都共享,因此存放在堆中的對象實例需要考慮線程安全及線程可見性問題。
2. 堆內存是垃圾回收器主要作用的地方。
與堆大小分配相關的兩個虛擬機參數為:
~~~
?-Xmx:堆的最大大小
?-Xms:初始化堆的大小
~~~
堆內存溢出:
當源源不斷的有對象產生,并且產生的對象還在使用(垃圾回收器無法作用),或者一次分配的對象過大導致堆內存放不下的時候可能會出現堆內存溢出:`OutOfMemoryError`
### 對象在堆中的創建過程
:-: 
**對象的內存布局**
* 對象頭
* 實例數據:填充順序為double/long、int、short/char、bytes/boolean、ordinary object pointers;
* 對齊填充:對象大小為8字節的整數倍。
**對象的訪問方式**
* 句柄方式

* 直接訪問方式:HotSpot用這種方式更多!

## 四、方法區
方法區與Java堆一樣是一塊線程共享的區域,在《Java虛擬機規范》中把方法區描述成堆的一個邏輯部分,但是還是得將方法區和堆分開來。方法區在虛擬機啟動的時候被創建!
方法區中保存的數據有:`被虛擬機加載的類型信息`、`常量`、`靜態常量`、`JIT編譯后的代碼緩存`。
HotSpot虛擬機在JDK1.8之前將方法區稱之為“永久代”,這個名稱與堆中的“新生代”,“老年代”的稱呼一樣,只是HotSpot的垃圾回收器的分代設計的沿用,在其他虛擬機實現中并沒有這個稱呼。而在JDK1.8及其之后的版本,棄用了“永久代”這個概念,將方法區稱為“元空間(Meta-space)”,同時元空間使用的是本地內存。

方法區特點:
1. 線程共享。
2. 使用本地內存(HotSpot在1.8版本之后)。
3. 也可能出現內存溢出錯誤。
**方法區內存溢出舉例 -> 1.6版本**
~~~
?public class Demo3 extends ClassLoader{
? ? ?public static void main(String[] args) {
? ? ? ? ?int i = 0;
? ? ? ? ?try {
? ? ? ? ? ? ?Demo3 test = new Demo3();
? ? ? ? ? ? ?for (int j = 0; j < 10000; j++, i++) {
? ? ? ? ? ? ? ? ?// 生成類的二進制字節碼
? ? ? ? ? ? ? ? ?ClassWriter classWriter = new ClassWriter(0);
? ? ? ? ? ? ? ? ?// 參數:版本號,作用域,類名,包名,父類,接口
? ? ? ? ? ? ? ? ?classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + j, null, "java/lang/Object", null);
? ? ? ? ? ? ? ? ?byte[] code = classWriter.toByteArray();
? ? ? ? ? ? ? ? ?// 只執行了類的加載
? ? ? ? ? ? ? ? ?test.defineClass("Class" + j, code, 0, code.length);
? ? ? ? ? ? }
? ? ? ? } finally {
? ? ? ? ? ? ?System.out.println(i);
? ? ? ? }
? ? }
?}
~~~
由于1.8版本是使用本地內存空間,和本機的虛存管理有關,一般不會出現方法區的內存溢出,測試時可以配置大小。
~~~
?方法區大小配置參數:
?`-XX:MaxMetaspaceSize=10m`
??
?1.8以前的配置參數為:
?`-XX:MaxPermSize=10m`
~~~
**方法區可能出現的問題**
Spring和Mybatis等框架底層使用了cglib這種基于字節碼的技術進行類的動態代理生成,可能就會導致方法區的OutOfMemoryError。
## 五、直接內存
直接內存(Direct Memory)是操作系統管理的內存,實際上不屬于運行時的數據區域。在JDK1.4版本之后引入了NIO類,這是一種基于通道與緩沖區的IO方式,NIO類可以使用Native庫函數直接分配堆外內存,然后通過存儲在Java堆里面的DirectByteBuffer對象作為這塊內存的引用。這樣可以提高一些性能,避免在Java堆和Native堆之間進行數據的復制。
特點:
* 不被JVM規范所限制,由操作系統管理。
* 分配回收成本高,但是讀寫性能高,采用零拷貝的方式減少數據的復制。
* 會出現`OutOfMemoryError`錯誤。
實現原理:
1. Java底層的Unsafe類用來分配和釋放直接內存,NIO中的ByteBuffer對象會和Unsafe對象相關聯。
2. 虛擬機中有個Cleaner類,表示虛引用對象,當關聯的對象被回收時,調用Unsafe類中的方法清空直接內存分配的區域。
:-: 
Linux每個進程都有代碼段、數據區、堆、棧等布局,受內存管理單元統一管理。JVM中的堆即在進程中的堆(linux heap)分配一部分空間,由JVM再進一步管理,而這里的直接內存指的是在當前進程中堆(linux heap)的一部分。
## 六、本地方法棧
本地方法棧是給本地方法接口提供運行時所需要的空間,本地方法接口一般都會使用native關鍵字修飾,具體的源碼得去查看openJDK。本地方法一般都是由C/C++編寫。
- 第一章 Java基礎
- ThreadLocal
- Java異常體系
- Java集合框架
- List接口及其實現類
- Queue接口及其實現類
- Set接口及其實現類
- Map接口及其實現類
- JDK1.8新特性
- Lambda表達式
- 常用函數式接口
- stream流
- 面試
- 第二章 Java虛擬機
- 第一節、運行時數據區
- 第二節、垃圾回收
- 第三節、類加載機制
- 第四節、類文件與字節碼指令
- 第五節、語法糖
- 第六節、運行期優化
- 面試常見問題
- 第三章 并發編程
- 第一節、Java中的線程
- 第二節、Java中的鎖
- 第三節、線程池
- 第四節、并發工具類
- AQS
- 第四章 網絡編程
- WebSocket協議
- Netty
- Netty入門
- Netty-自定義協議
- 面試題
- IO
- 網絡IO模型
- 第五章 操作系統
- IO
- 文件系統的相關概念
- Java幾種文件讀寫方式性能對比
- Socket
- 內存管理
- 進程、線程、協程
- IO模型的演化過程
- 第六章 計算機網絡
- 第七章 消息隊列
- RabbitMQ
- 第八章 開發框架
- Spring
- Spring事務
- Spring MVC
- Spring Boot
- Mybatis
- Mybatis-Plus
- Shiro
- 第九章 數據庫
- Mysql
- Mysql中的索引
- Mysql中的鎖
- 面試常見問題
- Mysql中的日志
- InnoDB存儲引擎
- 事務
- Redis
- redis的數據類型
- redis數據結構
- Redis主從復制
- 哨兵模式
- 面試題
- Spring Boot整合Lettuce+Redisson實現布隆過濾器
- 集群
- Redis網絡IO模型
- 第十章 設計模式
- 設計模式-七大原則
- 設計模式-單例模式
- 設計模式-備忘錄模式
- 設計模式-原型模式
- 設計模式-責任鏈模式
- 設計模式-過濾模式
- 設計模式-觀察者模式
- 設計模式-工廠方法模式
- 設計模式-抽象工廠模式
- 設計模式-代理模式
- 第十一章 后端開發常用工具、庫
- Docker
- Docker安裝Mysql
- 第十二章 中間件
- ZooKeeper