1. 程式人生 > 其它 >阿里出品:《深入理解Java虛擬機器 1

阿里出品:《深入理解Java虛擬機器 1

阿里出品:《深入理解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%固定的,取決於垃圾收集器組合和引數設定等。下面有幾條分配原則可供參考。

  1. 物件優先在Eden分配
  1. 大物件直接進入老年代
  1. 長期存活的物件將進入老年代
  1. 動態物件年齡判定
  1. 空間分配擔保

二、垃圾回收機制


英文名兒是GC(Garbage Collection)。

1、哪些記憶體需要回收?

堆和方法區中的記憶體需要回收,其它的不用回收。

因為只有堆和方法區是執行緒共享的,其餘的是與執行緒“同生共死”的,執行緒結束,記憶體自然就跟著回收了,所以不用管它們。

2、什麼時候回收?

Docker步步實踐

目錄文件:

①Docker簡介

②基本概念

③安裝Docker

④使用映象:

⑤操作容器:

⑥訪問倉庫:

⑦資料管理:

⑧使用網路:

⑨高階網路配置:

⑩安全:

?底層實現:

?其他專案:

有需要完整版原始碼+筆記的朋友點選這裡免費獲取