阿里出品:《深入理解Java虛擬機器 1
- 棧幀中的區域性變量表
存放的是編譯期可知的各種基本資料型別,物件引用型別。所以其所需要的記憶體空間在編譯期間就能完成分配,在執行期間不會改變其大小。
在分配基本資料型別所佔的空間時,除了64位的long和double型別的資料會佔用2個區域性變數空間,其餘的資料型別只佔用1個。
3、本地方法棧
本地方法棧和虛擬機器棧的作用是相同的,只不過虛擬機器棧執行的是java方法,本地方法棧執行的是Native方法。
java方法就是開發人員寫的java程式碼,Native方法就是一個java呼叫非java程式碼的介面。
4、Java堆
如果說棧解決的是程式執行問題,即程式如何處理資料;則堆解決的是資料儲存問題,即資料怎麼放,放在哪。
此記憶體區域的唯一目的是存放物件例項,Java堆是垃圾收集器管理的主要區域。
特定:堆是虛擬機器記憶體中最大的一塊,大概佔記憶體的三分之二,堆可處於物理上不連續的記憶體空間中,只要邏輯上是連續的即可。
5、方法區
方法區與Java堆一樣,是各個執行緒共享的記憶體區域,它用於儲存已被虛擬機器載入的資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。方法區也可以看作是Java堆的一部分。
這部分割槽域可以不選擇垃圾回收,這區域的記憶體回收主要針對常量池的回收和對型別的解除安裝。
這部分可能會導致未完全回收而導致記憶體洩漏。
二、java8記憶體模型-永久代(PermGen)和元空間(Metaspace)
1、PermGen(永久代)
絕大部分程式設計師都見過"java.lang.OutOfMemoryError:?PermGen?space?"這個異常。這裡“PermGen space”其實指的就是方法區。不過方法區和“PermGen space”又有著本質的區別。前者是JVM規範,而後者是JVM規範的一種實現,並且只有HotSpot才有“PermGen space”,而對於其他型別的虛擬機器,如 JRockit(Oracle)、J9(IBM) 並沒有“PermGen space”。由於方法區主要儲存類的相關資訊,所以對於動態生成類的情況比較容易出現永久代的記憶體溢位。最典型的場景就是,在jsp頁面比較多的情況,容易出現永久代記憶體溢位。我們現在通過動態生成類來模擬“PermGen?space”的記憶體溢位:
package com.paddx.test.memory;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
public class PermGenOomMock{
public static void main(String[] args) {
URL url = null;
List<ClassLoader> classLoaderList = new ArrayList<ClassLoader>();
try {
url = new File("/tmp").toURI().toURL();
URL[] urls = {url};
while (true){
ClassLoader loader = new URLClassLoader(urls);
classLoaderList.add(loader);
loader.loadClass("com.paddx.test.memory.Test");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
執行結果如下:
本例中使用的 JDK 版本是 1.7,指定的 PermGen 區的大小為 8M。通過每次生成不同URLClassLoader物件來載入Test類,從而生成不同的類物件,這樣就能看到我們熟悉的?"java.lang.OutOfMemoryError:?PermGen?space?" 異常了。這裡之所以採用 JDK 1.7,是因為在 JDK 1.8 中, HotSpot 已經沒有 “PermGen space”這個區間了,取而代之是一個叫做 Metaspace(元空間) 的東西。下面我們就來看看 Metaspace 與 PermGen space 的區別。
2、Metaspace(元空間)
其實,移除永久代的工作從JDK1.7就開始了。JDK1.7中,儲存在永久代的部分資料就已經轉移到了Java Heap或者是 Native Heap。但永久代仍存在於JDK1.7中,並沒完全移除,譬如符號引用(Symbols)轉移到了native heap;字面量(interned strings)轉移到了java heap;類的靜態變數(class statics)轉移到了java heap。我們可以通過一段程式來比較 JDK 1.6 與 JDK 1.7及 JDK 1.8 的區別,以字串常量為例:
package com.paddx.test.memory;
import java.util.ArrayList;
import java.util.List;
public class StringOomMock {
static String base = "string";
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i=0;i< Integer.MAX_VALUE;i++){
String str = base + base;
base = str;
list.add(str.intern());
}
}
}
這段程式以2的指數級不斷的生成新的字串,這樣可以比較快速的消耗記憶體。我們通過 JDK 1.6、JDK 1.7 和 JDK 1.8 分別執行:
JDK 1.6 的執行結果:
JDK 1.7的執行結果:
JDK 1.8的執行結果:
從上述結果可以看出,JDK 1.6下,會出現“PermGen Space”的記憶體溢位,而在 JDK 1.7和 JDK 1.8 中,會出現堆記憶體溢位,並且 JDK 1.8中 PermSize 和 MaxPermGen 已經無效。因此,可以大致驗證 JDK 1.7 和 1.8 將字串常量由永久代轉移到堆中,並且 JDK 1.8 中已經不存在永久代的結論。現在我們看看元空間到底是一個什麼東西?
元空間的本質和永久代類似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元空間並不在虛擬機器中,而是使用本地記憶體。因此,預設情況下,元空間的大小僅受本地記憶體限制,但可以通過以下引數來指定元空間的大小:
-XX:MetaspaceSize,初始空間大小,達到該值就會觸發垃圾收集進行型別解除安裝,同時GC會對該值進行調整:如果釋放了大量的空間,就適當降低該值;如果釋放了很少的空間,那麼在不超過MaxMetaspaceSize時,適當提高該值。
-XX:MaxMetaspaceSize,最大空間,預設是沒有限制的。
除了上面兩個指定大小的選項以外,還有兩個與 GC 相關的屬性:
-XX:MinMetaspaceFreeRatio,在GC之後,最小的Metaspace剩餘空間容量的百分比,減少為分配空間所導致的垃圾收集
-XX:MaxMetaspaceFreeRatio,在GC之後,最大的Metaspace剩餘空間容量的百分比,減少為釋放空間所導致的垃圾收集
現在我們在 JDK 8下重新執行一下程式碼段 4,不過這次不再指定 PermSize 和 MaxPermSize。而是指定 MetaSpaceSize 和 MaxMetaSpaceSize的大小。輸出結果如下:
從輸出結果,我們可以看出,這次不再出現永久代溢位,而是出現了元空間的溢位。
第三章 垃圾收集器與記憶體分配策略
====================
一、記憶體分配
這部分我們說一下物件在java堆中是如何分配、佈局、訪問以及記憶體分配的原則。
1、物件的建立
我們用new來建立物件,來看看系統執行到new時,虛擬機器在幹什麼。此時的類就像一塊肉,他要經過層層安檢,才能到達人類的飯桌。
(1)檢視在常量池中是否有對應的符號引用。【在方法區中進行】
(2)檢視此類是否被載入、解析和初始化過。【在方法區中進行】
(3)領取新生物件的記憶體。有兩種方式:指標碰撞和空閒列表。【在堆中進行】
(4)將分配到的記憶體空間初始化為零。
(5)對物件進行必要的設定,比如其實哪個類的例項,物件的雜湊碼之類的。這些資訊存放在物件的物件頭中。
(6)如果java程式碼對物件進行了賦值,則會走到第六步,執行
2、物件的記憶體佈局
物件在記憶體中的儲存佈局分為三個部分:物件頭+例項資料+對其補充
- 物件頭
物件頭裡面有兩部分資訊:
(1)執行時資料,包括雜湊碼、GC分代年齡、鎖狀態標誌燈。
(2)型別指標,虛擬機器通過這個指標來確定這個物件是哪個類的例項。
- 例項資料
例項資料中存放的是程式碼中定義的各種型別的欄位內容。
- 對其填充
對齊填充起的是佔位符的作用,不是必然存在的,其只要保證物件的大小是8位元組的整數倍即可。
3、物件的訪問定位
建立完物件後,我們就可以使用物件了。通過控制代碼和直接指標兩種方式。
- 控制代碼
控制代碼訪問就是在java堆中劃分出一塊記憶體區域作為控制代碼池,控制代碼中包含了例項資料和型別資料各自具體的地址資訊。
- 直接指標
直接指標之所以“直接”,是因為它去除了控制代碼這個中介。所以在速度上比控制代碼快。
在HotSpot虛擬機器中,使用的是這種方式。
說完了物件在java堆中是如何分配,佈局和訪問的,接下來我們說說記憶體分配的原則。
4、記憶體分配的原則
堆大致分為新生代,老年代,永久代。物件的記憶體分配主要分配在新生代的Eden區,少數情況下會直接分配到老年代中。分配的規則不是100%固定的,取決於垃圾收集器組合和引數設定等。下面有幾條分配原則可供參考。
- 物件優先在Eden分配
- 大物件直接進入老年代
- 長期存活的物件將進入老年代
- 動態物件年齡判定
- 空間分配擔保
二、垃圾回收機制
英文名兒是GC(Garbage Collection)。
1、哪些記憶體需要回收?
堆和方法區中的記憶體需要回收,其它的不用回收。
因為只有堆和方法區是執行緒共享的,其餘的是與執行緒“同生共死”的,執行緒結束,記憶體自然就跟著回收了,所以不用管它們。
2、什麼時候回收?
Docker步步實踐
目錄文件:
①Docker簡介
②基本概念
③安裝Docker
④使用映象:
⑤操作容器:
⑥訪問倉庫:
⑦資料管理:
⑧使用網路:
⑨高階網路配置:
⑩安全:
?底層實現:
?其他專案:
有需要完整版原始碼+筆記的朋友點選這裡免費獲取