內存結構(2)

前言

JVM老是開始於main()方法,方法必須是公有、靜態、void、參數爲String數組; 被初始化線程執行啓動。html

public static void main(String[] args) {}

線程分爲: 守護線程(daemon) 和 普通線程, main()的初始化線程一定不是守護線程。在setDaemon(true)時若是isAlive,則拋出IllegalThreadStateException。java

形參 & 實參

形式參數指在定義函數名後小括號裏定義的args,用於接受來自調用函數的參數,做用域只在函數內有效;實際參數指在調用時傳遞給函數的真正的參數。形參的本質是一個名字,不佔用內存空間;實參的本質是一個變量,已經佔用內存空間。數組

在函數體內緩存

  1. 對於基本類型,改變不會影響實參;
  2. 對於引用類型,改變會直接影響實參,不管改變的是對象中的基礎類型或引用類型變量,修改的最終是堆內地址實際的對象。
package com.noob.learn.netty;

import lombok.AllArgsConstructor;
import lombok.Data;

public class Test {
    private static void function(String a, int b) {
        a = "100a";
        b = 1000;
    }

    private static void function2(Arg arg) {
        arg.arg1 = "function2";
        arg.arg2 = 100;
    }

    public static void main(String[] args) {
        String arg1 = "arg1";
        int arg2 = 0;
        function(arg1, arg2);
        System.out.println(String.format("arg1=%s, arg2=%s", arg1, arg2));
        Arg arg = new Arg("arg1", 0);
        function2(arg);
        System.out.println(arg.toString());

    }

}

@Data
@AllArgsConstructor
class Arg {
    public String arg1;
    public int    arg2;
}

測試結果:安全

arg1=arg1, arg2=0   // 沒變
Arg(arg1=function2, arg2=100)    // 改變

內存模型JMM(Java Memory Model)

      存在一個主內存(Main Memory),儲存ava中全部實例變量,對於全部線程都是共享的。每一個線程都有一個私有的工做內存(Local Memory), 存儲了該線程以讀/寫共享變量的副本,(本地內存是JMM的一個抽象概念,並不真實存在。它涵蓋了緩存,寫緩衝區,寄存器以及其餘的硬件和編譯器優化)工做內存由緩存 堆棧 <理解爲:棧>兩部分組成:oracle

  • 緩存中保存的是主存中變量的拷貝,緩存並不總能時刻與主存同步,修改變量可能沒有馬上寫入到主存中; (關聯的知識點<待寫>: 內存屏障 & 指令重排 & volatile )
  • 堆棧中保存的是線程的局部變量,線程之間沒法相互直接訪問棧中的變量。JVM對堆棧只進行兩種操做: 以幀爲單位的壓棧和出棧操做。當線程調用方法時,虛擬機將堆棧塊壓入堆棧中,執行結束後,彈出對應塊拋棄。

JMM決定一個線程對共享變量的寫入什麼時候對另外一個線程可見。根據 JVM 規範,JVM 內存共分爲虛擬機棧、堆、方法區、程序計數器、本地方法棧五個部分:app

虛擬機棧

  每一個線程有一個私有的棧,隨着線程的建立而建立,虛擬機棧描述的是Java方法執行的內存模型:每一個方法執行的時候都會建立一個棧幀(大小能夠固定也能夠動態擴展),用於存放局部變量表(基本數據類型和對象引用<句柄>),操做數棧,動態連接,方法出口等信息< 對象自己不存放在棧中,而是存放在堆(new 出來的對象)或者常量池中(字符串常量對象)或駐留於常規RAM(隨機訪問存儲器)區域,可經過 「堆棧指針」  得到。堆棧指針若向下移,會建立新的內存;若向上移,則會釋放那些內存。>less

      每個方法從調用直到執行完成的過程都對應着一個棧幀在虛擬機中的入棧到出棧的過程。把內存分爲堆內存和棧內存,其中的棧內存就指的是虛擬機棧的局部變量表部分。局部變量表存放了編譯期能夠知道的基本數據類型(boolean、byte、char、short、int、float、long、double),對象引用(多是一個指向對象起始地址的引用指針,也可能指向一個表明對象的句柄或者其餘與此對象相關的位置),和返回後所指向的字節碼的地址。其中64 位長度的long 和double 類型的數據會佔用2個局部變量空間(Slot),其他的數據類型只佔用1個。局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法須要在幀中分配多大的局部變量空間是徹底肯定的,在方法運行期間不會改變局部變量表的大小。當遞歸層次太深時,會引起java.lang.StackOverflowError,這是虛擬機棧拋出的異常函數

還有另外一種錯誤,那就是當申請不到空間時,會拋出 OutOfMemoryError。這裏有一個小細節須要注意,catch 捕獲的是 Throwable,而不是 Exception。由於 StackOverflowError 和 OutOfMemoryError 都不屬於 Exception 的子類測試

優勢:存取速度比堆要快,僅次於寄存器。
缺點:存在棧中的數據大小與生存期必須是肯定的,缺少靈活性。

本地方法棧

保存native方法進入區域的地址。

寄存器

指令寄存器、地址寄存器、程序計數器。位於處理器內部,處理速度最快。寄存器是根據須要由編譯器分配。

程序計數器

  在CPU的寄存器中有一個PC寄存器,存放下一條指令地址,這裏,虛擬機不使用CPU的程序計數器,本身在內存中設立一片區域來模擬CPU的程序計數器。只有一個程序計數器是不夠的,當多個線程切換執行時,那就單個程序計數器就沒辦法了,虛擬機規範中指出,每一條線程都有一個獨立的程序計數器。注意,Java虛擬機中的程序計數器指向正在執行的字節碼地址,而不是下一條。

堆(heap)

堆內存是 JVM 全部線程共享的部分,在虛擬機啓動的時候就已經建立, 存放全部new出來的對象和數組堆中的對象的由垃圾回收器負責回收,當申請不到空間時會拋出 OutOfMemoryError。
優勢:能夠動態地分配內存大小,生存期也沒必要事先告訴編譯器,由於它是在運行時動態分配內存的。
缺點:因爲要在運行時動態分配內存,存取速度較慢。

方法區

保存虛擬機加載並解析類後的信息、常量、靜態變量(JDK7中被移到Java堆),即時編譯期編譯後的代碼(類方法)等數據。全部的線程共享一個方法區,因此訪問方法區信息的方法必須是線程安全的。
雖然Java 虛擬機規範把方法區描述爲堆的一個邏輯部分,可是它卻有一個別名叫作Non-Heap(非堆),目的應該是與Java 堆區分開來。同時,因爲類class是JVM實現的一部分,並非由應用建立的,因此又被認爲是「非堆(non-heap)」內存。

《Java虛擬機規範》只是規定了有方法區這麼個概念和它的做用,並無規定如何去實現它。那麼,在不一樣的 JVM 上方法區的實現確定是不一樣的了。 同時大多數用的JVM都是Sun公司的HotSpot。在HotSpot上把GC分代收集擴展至方法區,或者說使用永久代來實現方法區。所以獲得告終論,永久代是HotSpot的概念,方法區是Java虛擬機規範中的定義,是一種規範標準,而永久代是一種實現,其餘的虛擬機實現並無永久帶這一說法。在1.7以前在(JDK1.2 ~ JDK6)的實現中,HotSpot 使用永久代實現方法區,HotSpot 使用 GC分代來實現方法區內存回收.

常量池

官方文檔說明:https://www.oracle.com/technetwork/java/javase/jdk7-relnotes-418459.html
In JDK 7, interned strings are no longer allocated in the permanent generation of the Java heap, but are instead allocated in the main part of the Java heap (known as the young and old generations), along with the other objects created by the application<字符字面量(字符串常量池)再也不分配在永久代中,代替的是分配來堆內存中的年輕代與年老代中>. This change will result in more data residing in the main Java heap, and less data in the permanent generation, and thus may require heap sizes to be adjusted. Most applications will see only relatively small differences in heap usage due to this change, but larger applications that load many classes or make heavy use of the String.intern() method will see more significant differences.<大部分應用對於堆的變化感知不多,可是有太多classes或須要大量使用動態添加String進常量池的方法的大應用感知強烈>

在JDK1.7中的HotASpot中,已經把本來放在方法區的字符串常量池移出。

  1. 將interned String移到Java堆中
  2. 將符號Symbols移到native memory(不受GC管理的內存)

從JDK7開始永久代的移除工做,貯存在永久代的一部分數據已經轉移到了Java Heap或者是Native Heap,但永久代仍然存在於JDK7,並無徹底的移除:

  1. 符號引用(Symbols)轉移到了native memory。
  2. 字面量(interned strings)轉移到了java heap。
  3. 類的靜態變量(class statics)轉移到了java heap。

隨着JDK8的到來,JVM再也不有PermGen, 但類的元數據信息(metadata)還在,只不過再也不是存儲在堆空間上,而是移動到叫作「Metaspace」的本地內存(Native memory)中

常量池精分

相關文章
相關標籤/搜索