jvm(2)-java内存区域
生活随笔
收集整理的這篇文章主要介紹了
jvm(2)-java内存区域
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
【0】README
0.1)本文轉自 深入理解jvm, 旨在學習 java內存區域 的基礎知識;
【1】運行時數據區域
1)jvm 所管理的內存將會包括以下幾個運行時數據區域
1.1)方法區;(線程共享)
1.2)虛擬機棧;(線程私有)
1.3)本地方法棧;(線程私有)
1.4)java 堆;(線程共享)
1.5)程序計數器;(線程私有)
Attention) 除了程序計數器外,虛擬機內存的其他幾個運行時區域都有發生 OutOfMemoryError(內存溢出) 異常的可能;
【1.1】程序計數器(線程私有)
1)程序計數器: 是一塊較小的內存空間,可以看做是當前線程所執行的字節碼的行號指示器;(干貨——程序計數器==行號指示器)
2)其作用: 為了線程切換后能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間計數器互不影響,獨立存儲,我們稱這類內存區域為“線程私有”的內存;
3)此內存區域:是唯一一個在 jvm 規范中 沒有規定任何 OutOfMemoryError 情況的區域;
【1.2】java 虛擬機棧(線程私有,用于存儲局部變量表,操作數棧,動態鏈接,方法出口等信息)
1)虛擬機棧的作用: 描述的是 java 方法執行的內存模型,每個方法在執行的時候都會創建一個棧幀,用于存儲局部變量表,操作數棧,動態鏈接,方法出口等信息;
2)局部變量表: 存放了編譯器可知的基本數據類型,對象引用 和 return address 類型(指向了一跳字節碼指令的地址);
3)局部變量空間(Slot): 其中64 位長度的long 和 double 類型數據都會占用兩個局部變量空間(slot),其余的數據類型占用1個;(干貨——引入slot==局部變量空間)
4)jvm 在該區域規定了兩種異常:?
Exception1)如果線程請求的棧深度大于jvm 所允許的深度,將拋出 StackOverFlowError 異常;
Exception2)如果虛擬機棧可以動態擴展,如果擴展時無法申請到足夠的內存,就會拋出 OutOfMemoryError 異常;
【1.3】本地方法棧(線程私有)
1)本地方法棧的作用于 虛擬機棧的作用相似;
2)他們作用的區別在于: 虛擬機棧為虛擬機執行 java(字節碼)服務,而本地方法棧為 虛擬機使用到的 Native 方法(本地方法)服務;
【1.4】java 堆(存放對象實例,線程共有)
1)java 堆的唯一作用:存放對象實例,幾乎所有的對象實例都在這里分配內存;
2)GC堆(垃圾收集堆): java 堆是垃圾收集器管理的主要區域,因此很多時候也被稱為 GC堆(Garbage Collected Heap);
3)從內存回收的角度看:由于現在收集器基本都采用分代收集算法,所以 java 堆還可以分為: 新生代 和 老年代;(干貨——在java堆中引入新生代和老年代)
4)從內存分配的角度來看:線程共享的 java 堆可能劃分出多個線程私有的分配緩沖區(Thread Local Allocation Buffer, TLAB);
5)java堆 可以處理物理上不連續的內存空間中, 只要邏輯上是連續的即可;
6) java 堆是可擴展的,不過當前主流的jvm 都是按照可擴展來實現的(通過 Xmx 和 -Xms 控制), 如果在堆中沒有內存完成實例分配,并且堆也無法再擴展,將會拋出 OutOfMemoryError 異常;
【1.5】方法區(用于存放 class 的相關信息,線程共有)
1)方法區的作用:用于存儲被虛擬機加載的類信息,常量,靜態變量,即時編譯器(JIT)編譯后的代碼等數據;
2)方法區除了和 java 堆一樣不需要連續的內存和可以選擇固定大小或者可擴展外,還可以選擇不實現垃圾收集;
3) 方法區的內存回收目標: 主要是針對常量池的回收和對類型的卸載;
【1.6】運行時常量池
1)運行時常量池是方法區的一部分;
2)Class 文件中除了有類的版本,字段,方法,接口等描述信息外,還有一個信息——常量池;(干貨——引入常量池)
3)常量池的作用:用于存放編譯器生成的各種字面量和符號引用,這部分內容將在類加載后進入方法區的運行時常量池中存放;
4)運行時常量池的一個重要特征:具備動態性;(干貨——運行時常量池的具備動態性)
看個荔枝): 并非預置入 Class 文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中, 這種特性被開發人員利用的多的便是 String類的 intern 方法;
【1.7】直接內存
0)直接內存: 分配在本機直接內存上,不是 jvm 運行時數據區的一部分,但被頻繁地使用;
1)jdk1.4 中新加入了NIO(new input/output)類: 引入了一種基于通道與緩沖區(buffer)的IO方式,他可以使用 Native 函數庫直接分配堆外內存,然后通過一個存儲在java 堆中的DirectByteBuffer 對象作為這塊內存的引用進行操作,這樣能在一些場景中提高性能,因為避免了在 java 堆 和 Native 堆中來回復制數據;
2)顯然,本機直接內存的分配不會受到 java 堆大小的限制;
【2】HotSpot 虛擬機對象探秘
1)對象的創建steps:
step1)是否執行類加載過程:虛擬機遇到一條new指令,首先去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否已被加載,解析和初始化過。如果沒有,那必須執行相應的類加載過程;
step2)分配內存: 接下來 虛擬機將為新生對象分配內存。(內存分配方式有: 指針碰撞 和 空閑列表)
problem):對象創建在虛擬機中是非常頻繁的行為,即使是僅僅修改一個指針指向的位置, 在并發情況下也并不是線程安全的。如,可能出現正在給對象A分配內存,指針還未來得及修改,對象B 又同時使用了原來指針來分配內存的情況;
solution1):對分配內存空間的動作進行同步處理——實際上采用 CAS 配上失敗重試的方式保證更新操作的原子性;
solution2):吧內存分配的動作按照線程劃分在不同的空間中進行,即每個線程在 java堆中預先分配一小塊內存,稱為本地線程分配緩沖(Thread Local Allocation Buffer,TLAB),只有TLAB用完并分配新的TLAB時,才需要同步鎖定;(干貨——每個線程在 java堆中預先分配一小塊內存,稱為本地線程分配緩沖)
step3)初始化為零: 內存分配完成后,虛擬機需要將分配到 的內存空間都初始化為零值;
step4)虛擬機對對象進行必要的設置: 如這個對象的類是什么,如果才能找到類的元數據信息,對象的哈希碼,對象的GC 分代年齡等信息。這些信息存放在對象的對象頭中;(干貨——引入了對象頭)
step5)執行 <init>方法: 執行完new 執行后,會執行 init 方法,把對象按照程序員的意愿進行初始化;
【2.1】對象的內存布局
1)對象的內存布局分為3個區域: 對象頭, 實例數據 和 對齊填充;
2)HotSpot 虛擬機的對象頭包括兩部分數據:
2.1)對象頭第一部分(Mark Word):用于存儲對象自身的運行時數據,如哈希碼,GC分代年齡,鎖狀態標志,線程持有的鎖,偏向線程ID,偏向時間戳,這部分數據的長度在 32位 和 64 位的虛擬機(未開啟壓縮指針)中分別為 32bit 和 64 bit, 官方稱為 “Mark Word”;(干貨——引入對象頭的Mark Word);對象頭信息是與對象自身定義 的數據無關的額外存儲成本,考慮到虛擬機的空間效率,Mark Word被設計成 非固定的數據結構以便在 極小的空間內存儲盡量多的信息;(干貨——對象頭信息是與對象自身定義 的數據無關的額外存儲成本)
Complementary)HotSpot 虛擬機對象頭 Mark Word: 2.2)對象頭第二部分(類型指針):類型指針, 即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例;
3)對象的實例數據區域:是對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型的字段內容;
4)對象的對齊填充區域: 沒有什么特別的含義,它僅僅起著占位符的作用, 換句話說,就是對象的大小必須是 8字節 的整數倍;
【2.3】對象的訪問定位
1)java 程序需要通過 棧上的 引用數據來操作堆上的具體對象。
2)引用應該通過何種方式去定位,訪問堆中的對象的具體位置,目前主流的訪問方式有 使用句柄 和 直接指針兩種;(干貨——棧上引用訪問堆上對象的主流訪問方式)
2.1)使用句柄訪問方式: 那么java 堆中將會劃分出一塊內存來作為句柄池,引用數據中存儲的就是對象的句柄地址,而句柄地址包含了對象實例數據和類型數據各自的具體地址;
2.2)使用直接指針訪問(Sun HotSpot 使用這種方式訪問)
0.1)本文轉自 深入理解jvm, 旨在學習 java內存區域 的基礎知識;
【1】運行時數據區域
1)jvm 所管理的內存將會包括以下幾個運行時數據區域
1.1)方法區;(線程共享)
1.2)虛擬機棧;(線程私有)
1.3)本地方法棧;(線程私有)
1.4)java 堆;(線程共享)
1.5)程序計數器;(線程私有)
Attention) 除了程序計數器外,虛擬機內存的其他幾個運行時區域都有發生 OutOfMemoryError(內存溢出) 異常的可能;
【1.1】程序計數器(線程私有)
1)程序計數器: 是一塊較小的內存空間,可以看做是當前線程所執行的字節碼的行號指示器;(干貨——程序計數器==行號指示器)
2)其作用: 為了線程切換后能恢復到正確的執行位置,每條線程都需要有一個獨立的程序計數器,各條線程之間計數器互不影響,獨立存儲,我們稱這類內存區域為“線程私有”的內存;
3)此內存區域:是唯一一個在 jvm 規范中 沒有規定任何 OutOfMemoryError 情況的區域;
【1.2】java 虛擬機棧(線程私有,用于存儲局部變量表,操作數棧,動態鏈接,方法出口等信息)
1)虛擬機棧的作用: 描述的是 java 方法執行的內存模型,每個方法在執行的時候都會創建一個棧幀,用于存儲局部變量表,操作數棧,動態鏈接,方法出口等信息;
2)局部變量表: 存放了編譯器可知的基本數據類型,對象引用 和 return address 類型(指向了一跳字節碼指令的地址);
3)局部變量空間(Slot): 其中64 位長度的long 和 double 類型數據都會占用兩個局部變量空間(slot),其余的數據類型占用1個;(干貨——引入slot==局部變量空間)
4)jvm 在該區域規定了兩種異常:?
Exception1)如果線程請求的棧深度大于jvm 所允許的深度,將拋出 StackOverFlowError 異常;
Exception2)如果虛擬機棧可以動態擴展,如果擴展時無法申請到足夠的內存,就會拋出 OutOfMemoryError 異常;
【1.3】本地方法棧(線程私有)
1)本地方法棧的作用于 虛擬機棧的作用相似;
2)他們作用的區別在于: 虛擬機棧為虛擬機執行 java(字節碼)服務,而本地方法棧為 虛擬機使用到的 Native 方法(本地方法)服務;
【1.4】java 堆(存放對象實例,線程共有)
1)java 堆的唯一作用:存放對象實例,幾乎所有的對象實例都在這里分配內存;
2)GC堆(垃圾收集堆): java 堆是垃圾收集器管理的主要區域,因此很多時候也被稱為 GC堆(Garbage Collected Heap);
3)從內存回收的角度看:由于現在收集器基本都采用分代收集算法,所以 java 堆還可以分為: 新生代 和 老年代;(干貨——在java堆中引入新生代和老年代)
4)從內存分配的角度來看:線程共享的 java 堆可能劃分出多個線程私有的分配緩沖區(Thread Local Allocation Buffer, TLAB);
5)java堆 可以處理物理上不連續的內存空間中, 只要邏輯上是連續的即可;
6) java 堆是可擴展的,不過當前主流的jvm 都是按照可擴展來實現的(通過 Xmx 和 -Xms 控制), 如果在堆中沒有內存完成實例分配,并且堆也無法再擴展,將會拋出 OutOfMemoryError 異常;
【1.5】方法區(用于存放 class 的相關信息,線程共有)
1)方法區的作用:用于存儲被虛擬機加載的類信息,常量,靜態變量,即時編譯器(JIT)編譯后的代碼等數據;
2)方法區除了和 java 堆一樣不需要連續的內存和可以選擇固定大小或者可擴展外,還可以選擇不實現垃圾收集;
3) 方法區的內存回收目標: 主要是針對常量池的回收和對類型的卸載;
【1.6】運行時常量池
1)運行時常量池是方法區的一部分;
2)Class 文件中除了有類的版本,字段,方法,接口等描述信息外,還有一個信息——常量池;(干貨——引入常量池)
3)常量池的作用:用于存放編譯器生成的各種字面量和符號引用,這部分內容將在類加載后進入方法區的運行時常量池中存放;
4)運行時常量池的一個重要特征:具備動態性;(干貨——運行時常量池的具備動態性)
看個荔枝): 并非預置入 Class 文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中, 這種特性被開發人員利用的多的便是 String類的 intern 方法;
【1.7】直接內存
0)直接內存: 分配在本機直接內存上,不是 jvm 運行時數據區的一部分,但被頻繁地使用;
1)jdk1.4 中新加入了NIO(new input/output)類: 引入了一種基于通道與緩沖區(buffer)的IO方式,他可以使用 Native 函數庫直接分配堆外內存,然后通過一個存儲在java 堆中的DirectByteBuffer 對象作為這塊內存的引用進行操作,這樣能在一些場景中提高性能,因為避免了在 java 堆 和 Native 堆中來回復制數據;
2)顯然,本機直接內存的分配不會受到 java 堆大小的限制;
【2】HotSpot 虛擬機對象探秘
1)對象的創建steps:
step1)是否執行類加載過程:虛擬機遇到一條new指令,首先去檢查這個指令的參數是否能在常量池中定位到一個類的符號引用,并且檢查這個符號引用代表的類是否已被加載,解析和初始化過。如果沒有,那必須執行相應的類加載過程;
step2)分配內存: 接下來 虛擬機將為新生對象分配內存。(內存分配方式有: 指針碰撞 和 空閑列表)
problem):對象創建在虛擬機中是非常頻繁的行為,即使是僅僅修改一個指針指向的位置, 在并發情況下也并不是線程安全的。如,可能出現正在給對象A分配內存,指針還未來得及修改,對象B 又同時使用了原來指針來分配內存的情況;
solution1):對分配內存空間的動作進行同步處理——實際上采用 CAS 配上失敗重試的方式保證更新操作的原子性;
solution2):吧內存分配的動作按照線程劃分在不同的空間中進行,即每個線程在 java堆中預先分配一小塊內存,稱為本地線程分配緩沖(Thread Local Allocation Buffer,TLAB),只有TLAB用完并分配新的TLAB時,才需要同步鎖定;(干貨——每個線程在 java堆中預先分配一小塊內存,稱為本地線程分配緩沖)
step3)初始化為零: 內存分配完成后,虛擬機需要將分配到 的內存空間都初始化為零值;
step4)虛擬機對對象進行必要的設置: 如這個對象的類是什么,如果才能找到類的元數據信息,對象的哈希碼,對象的GC 分代年齡等信息。這些信息存放在對象的對象頭中;(干貨——引入了對象頭)
step5)執行 <init>方法: 執行完new 執行后,會執行 init 方法,把對象按照程序員的意愿進行初始化;
【2.1】對象的內存布局
1)對象的內存布局分為3個區域: 對象頭, 實例數據 和 對齊填充;
2)HotSpot 虛擬機的對象頭包括兩部分數據:
2.1)對象頭第一部分(Mark Word):用于存儲對象自身的運行時數據,如哈希碼,GC分代年齡,鎖狀態標志,線程持有的鎖,偏向線程ID,偏向時間戳,這部分數據的長度在 32位 和 64 位的虛擬機(未開啟壓縮指針)中分別為 32bit 和 64 bit, 官方稱為 “Mark Word”;(干貨——引入對象頭的Mark Word);對象頭信息是與對象自身定義 的數據無關的額外存儲成本,考慮到虛擬機的空間效率,Mark Word被設計成 非固定的數據結構以便在 極小的空間內存儲盡量多的信息;(干貨——對象頭信息是與對象自身定義 的數據無關的額外存儲成本)
Complementary)HotSpot 虛擬機對象頭 Mark Word: 2.2)對象頭第二部分(類型指針):類型指針, 即對象指向它的類元數據的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例;
3)對象的實例數據區域:是對象真正存儲的有效信息,也是在程序代碼中所定義的各種類型的字段內容;
4)對象的對齊填充區域: 沒有什么特別的含義,它僅僅起著占位符的作用, 換句話說,就是對象的大小必須是 8字節 的整數倍;
【2.3】對象的訪問定位
1)java 程序需要通過 棧上的 引用數據來操作堆上的具體對象。
2)引用應該通過何種方式去定位,訪問堆中的對象的具體位置,目前主流的訪問方式有 使用句柄 和 直接指針兩種;(干貨——棧上引用訪問堆上對象的主流訪問方式)
2.1)使用句柄訪問方式: 那么java 堆中將會劃分出一塊內存來作為句柄池,引用數據中存儲的就是對象的句柄地址,而句柄地址包含了對象實例數據和類型數據各自的具體地址;
2.2)使用直接指針訪問(Sun HotSpot 使用這種方式訪問)
總結
以上是生活随笔為你收集整理的jvm(2)-java内存区域的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 成本猛降 40%!国产 Model Y
- 下一篇: SEO关键词优化怎么收费(seo关键词优