JVM簡單入門

初識JVM

JVM的位置:jre中包含jvm。java

雙親委派機制

雙親委派機制:是指當一個類加載器收到一個類加載請求時,該類加載器首先會把請求委派給父類加載器。每一個類加載器都是如此,只有在父類加載器在本身的搜索範圍內找不到指定類時,子類加載器纔會嘗試本身去加載。算法

在IDE中編寫的Java源代碼會被編譯器編譯成.class文件,而後再由ClassLoader(類加載器)將這些class文件加載到JVM中執行。編程

JVM中提供了三層的ClassLoader:安全

  • Bootstrap classLoader: 主要負責加載核心的類庫(java.lang.*等),構造ExtClassLoader和APPClassLoader。
  • ExtClassLoader:主要負責加載jre/lib/ext目錄下的一些擴展的jar。
  • AppClassLoader:主要負責加載應用程序的主函數類。

沙箱安全機制

Java安全模型的核心就是Java沙箱(sandbox),什麼是沙箱?沙箱是一個限制程序運行的環境,沙箱機制就是將Java代碼限定在虛擬機(JVM)特定的運行範圍中,而且嚴格限制代碼對本地系統資源的訪問,經過這樣的措施來保證對代碼的有效隔離,防止對本地系統形成破壞,沙箱主要限制系統資源訪問,那系統資源包括什麼?CPU,內存,文件系統,網絡。不一樣級別的沙箱對這些資源的訪問權限也能夠不同。網絡

全部的java程序運行均可以指定沙箱,能夠定製安全策略。數據結構

在Java中將執行程序分爲本地代碼和遠程代碼兩種,本地代碼默認視爲可信任的,能夠訪問一切本地資源;而遠程代碼則被視爲不受信任的,對於未授信的遠程代碼在早期的Java實現中,安全依賴於沙箱機制。jvm

如此嚴格的安全機制給程序的拓展帶來了障礙,當遠程代碼須要訪問本地資源的時候就沒法實現。所以在Java1.1中,針對安全機制作了改進,增長了安全策略,容許用戶指定代碼對本地資源的訪問。在Java1.2版本中,再次改進了安全機制,不論本地代碼或是遠程代碼,都會按照用戶的安全策略設定,由類加載器加載到虛擬機中權限不一樣的運行空間,來實現差別化的代碼執行權限控制。編程語言

當前最新的安全機制實現,則引入了域(Domain)的概念,虛擬機會把全部的代碼加載到不一樣的系統域和應用域,系統域專門負責與關鍵資源進行交互,而各個應用域部分則經過系統域的部分代理來對各類須要的資源進行訪問。虛擬機中不一樣的受保護域,對應不同的權限,存在於不一樣域的中的類文件,就具備了當前域的所有權限。函數

沙箱的基本組件工具

  • 字節碼校驗器(bytecode verifier):確保Java類文件遵循Java語言規範,這樣能夠幫助Java程序實現內存保護,但並非全部的類文件都會通過字節碼校驗,好比核心庫。

  • 類裝載器(ClassLoader):類裝載器在三個方面對Java沙箱起做用。

    • 防止惡意代碼去幹涉善意代碼。
    • 守護被信任的類庫邊界。
    • 將代碼納入保護域,確保代碼能夠進行哪些操做。

    虛擬機爲不一樣的類加載器載入的類提供了不一樣的命名空間,命名空間由一系列惟一的名稱組成,每個被裝載的類將有一個名字,這個命名空間是由Java虛擬機爲每個類裝載器維護,它們互相之間甚至不可見。

    類裝載器採用的機制是雙親委派機制。

    • 由最內層JVM自帶的類加載器開始加載,外層惡意同名類得不到加載從而沒法使用。
    • 因爲嚴格經過包來區分了訪問域,外層惡意的類經過內置代碼也沒法得到權限訪問的內層類,破壞代碼就天然沒法實現。
  • 存取控制器(access controller):存取控制器能夠控制核心API對操做系統的存取權限,而這個控制策略的設定,也能夠由用戶指定。

  • 安全管理器(security manager):是核心API和操做系統之間的主要接口,實現權限控制,比存取控制器優先級高。

  • 安全軟件包(security package):java.cecurity下的類和拓展包下的類,容許用戶爲本身的類增長新的安全特性,包括:

    • 安全提供者
    • 消息摘要
    • 數字簽名
    • 加密
    • 鑑別

Native

native:凡是帶native關鍵字的,說明Java的做用範圍達不到了。會去調用底層c語言的庫,進入本地方法棧,調用本地方法本地接口(JNI)。

JNI做用:拓展Java的使用,融合不一樣的編程語言爲Java所用。

native method stack做用:登記native方法,在(Execution Engine)執行引擎執行的時候加載native libraies(本地庫)。

PC計數器

程序計數器:program counter register。

每一個線程都有一個程序計數器,是線程私有的,就是一個指針,指向方法區中的方法字節碼(用來存儲指向一條指令的地址),在執行引擎讀取下一條指令,是一個很是小的內存空間,幾乎能夠忽略不記。

方法區

img

方法區是被全部的線程共享,全部字段和方法字節碼,以及一些特殊方法,如構造函數,接口代碼也在此定義,就是全部定義的方法的信息都保存在該區域,此區域屬於共享空間

靜態變量,常量,類信息(構造方法,接口),運行時的常量池存在方法區中,可是實例變量存在堆內存中,與方法區無關。

(jdk1.8已經將方法區去掉了,將方法區移動到直接內存)

JDK1.8爲何要移除方法區

1)永久代來存儲類信息、常量、靜態變量等數據不是個好主意, 很容易遇到內存溢出的問題.JDK8的實現中將類的元數據放入 native memory, 將字符串池和類的靜態變量放入java堆中. 可使用MaxMetaspaceSize對元數據區大小進行調整;

2)對永久代進行調優是很困難的,同時將元空間與堆的垃圾回收進行了隔離,避免永久代引起的Full GC和OOM等問題;

棧:數據結構(先進後出),棧內存,主管程序的運行,生命週期和線程同步,線程結束,棧內存釋放。

對於棧來講,不存在垃圾回收問題。

棧:主要存儲八大基本數據類型和對象引用。

棧運行原理:棧幀用於存儲局部變量表,動態連接,方法出口等信息,方法的執行就對應着棧幀在虛擬機棧中入棧和出棧的過程。

堆(Heap):一個JVM只有一個堆內存,堆內存的大小是能夠調節的。

堆:此內存區域惟一的目的就是存放對象實例。全部的對象實例都在這裏分配內存

堆內存中分爲三個區域

  • 新生區:
    • 伊甸園區:Java新對象的出生地(若是新建立的對象佔用內存很大,則直接分配到老年代),當Eden區內存不夠的時候就會觸發MinorGC,對新生代區進行一次垃圾回收。
    • 倖存區0區:保留了一次MinorGC過程當中的倖存者。
    • 倖存區1區:上一次GC的倖存者,做爲這一次GC的被掃描者。
  • 老年區:存放穩定的對象(年齡到達設定的值 ,通常爲15)。
  • 永久區:常駐內存的,存放Java運行時的一些環境或類信息(這個區不存在垃圾回收,關閉JVM就釋放這個區的內存)。
    • Java17以前:永久代,常量池在方法區;
    • jdk1.7:去永久代,常量池在堆中;
    • jdk1.8:無永久代,常量池在元空間(元空間邏輯上存在,物理上不存在);
package com.jvm;

public class Test02 {
    public static void main(String[] args) {
        //返回虛擬機試圖使用的最大內存
        long max = Runtime.getRuntime().maxMemory();//字節
        //jvm的初始化總內存
        long total = Runtime.getRuntime().totalMemory();
        System.out.println("max="+max+"字節\t"+(max/(double)1024/1024)+"M");
        System.out.println("total="+total+"字節\t"+(total/(double)1024/1024+"M"));
    }
}

工具分析OOM

在一個項目中,出現了OOM,使用的內存分析工具(MAT,Jprofile)。

做用:分析Dump內存文件,快速定位內存泄漏;得到堆中的數據,得到大的對象等。

package com.jvm;

import java.util.ArrayList;
//dump文件
public class Test03 {
    byte[] array = new byte[1*1024*1024];

    public static void main(String[] args) {
        ArrayList<Object> list = new ArrayList<>();
        int count = 0;
        try{
            while (true){
                list.add(new Test03());
                count+=1;
            }
        }catch (Exception e){
            System.out.println("count:"+count);
            e.printStackTrace();
        }

    }
}

GC算法

GC回收大部分都是在新生區。

何時觸發GC

​ (1)程序調用System.gc時能夠觸發

​ (2)系統自身來決定GC觸發的時機(根據Eden區和From Space區的內存大小來決定。當內存大小不足時,則會啓動GC線程並中止應用線程)

GC又分爲 minor GC 和 Full GC (也稱爲 Major GC )

Minor GC觸發條件:當Eden區滿時,觸發Minor GC。

Full GC觸發條件:

a.調用System.gc時,系統建議執行Full GC,可是沒必要然執行

b.老年代空間不足

c.方法區空間不足

d.經過Minor GC後進入老年代的平均大小大於老年代的可用內存

e.由Eden區、From Space區向To Space區複製時,對象大小大於To Space可用內存,則把該對象轉存到老年代,且老年代的可用內存小於該對象大小

GC經常使用的算法:引用計數法,標記清除算法,標記壓縮算法,複製算法分代收集算法

目前主流的JVM(HotSpot)採用的是分代收集算法。

引用計數法:當一個對象的引用爲0時會清除該對象(使用較少)。

複製算法

複製算法:該算法將新生區內存平均分紅兩部分,每次只使用其中的一部分,當這部份內存快滿的時候,將其中的倖存者複製到另外一個內存上,將以前的內存清空。

  • 優勢:不存在內存碎片,只需移動棧頂指針,按順序分配內存便可。
  • 缺點:每次只使用一半的內存。
  • 每交換一次年齡加1,到達默認年齡15後,倖存者進入老年區(-XX: -XX:MaxTenuringThreshold=30設置進入老年代的年齡)。

標記清除算法

標記清除法:用在老年代中,爲對象存儲一個標記位,標記存活的對象,對死亡的對象執行清除操做。

  • 優勢:不須要額外的空間,不須要移動對象。
  • 缺點:兩次掃描效率比較低(全棧遍歷),沒有移動對象會產生內存碎片。

標記壓縮算法

標記壓縮算法是標記清除算法的一個改進版,再次掃描,將存活的對象向一端移動,

  • 優勢:沒有內存碎片。
  • 缺點:存活的對象較多時,會進行屢次的移動操做,效率低。

分代收集算法

分代收集算法是目前大部分JVM的垃圾收集器採用的算法。它的核心思想是根據對象存活的生命週期將內存劃分爲若干個不一樣的區域。通常狀況下將堆區劃分爲老年代(Tenured Generation)和新生代(Young Generation),在堆區以外還有一個代就是永久代(Permanet Generation)。老年代的特色是每次垃圾收集時只有少許對象須要被回收,而新生代的特色是每次垃圾回收時都有大量的對象須要被回收,那麼就能夠根據不一樣代的特色採起最適合的收集算法。

GC算法總結

內存效率:複製算法 > 標記清除算法 > 標記壓縮算法(時間複雜度)

內存整齊度:複製算法 = 標記壓縮算法 > 標記清楚算法(內存碎片)

內存利用率:標記壓縮算法 = 標記清除算法 > 複製算法

因此JVM採用分代收集算法!!

JMM

Java memory model:Java內存模型。

相關文章
相關標籤/搜索