天天看點

java 虛拟機記憶體類_java 虛拟機類加載 及記憶體結構

在面試java工程師的時候,這道題經常被問到,故需特别注意。

1、JVM 簡介

JVM 是我們Javaer 的最基本功底了,剛開始學Java 的時候,一般都是從“Hello World ”開始的,然後會寫個複雜點class ,然後再找一些開源架構,比如Spring ,Hibernate 等等,再然後就開發企業級的應用,比如網站、企業内部應用、實時交易系統等等,直到某一天突然發現做的系統咋就這麼慢呢,而且時不時還來個記憶體溢出什麼的,今天是交易系統報了StackOverflowError ,明天是網站系統報了個OutOfMemoryError ,這種錯誤又很難重制,隻有分析Javacore 和dump 檔案,運氣好點還能分析出個結果,運作遭的點,就直接去廟裡燒香吧!每天接客戶的電話都是戰戰兢兢的,生怕再出什麼幺蛾子了。我想Java 做的久一點的都有這樣的經曆,那這些問題的最終根結是在哪呢?—— JVM 。

JVM 全稱是Java Virtual Machine ,Java 虛拟機,也就是在計算機上再虛拟一個計算機,這和我們使用 VMWare不一樣,那個虛拟的東西你是可以看到的,這個JVM 你是看不到的,它存在記憶體中。我們知道計算機的基本構成是:運算器、控制器、存儲器、輸入和輸出裝置,那這個JVM 也是有這成套的元素,運算器是當然是交給硬體CPU 還處理了,隻是為了适應“一次編譯,随處運作”的情況,需要做一個翻譯動作,于是就用了JVM 自己的指令集,這與彙編的指令集有點類似,每一種彙編指令集針對一個系列的CPU ,比如8086 系列的彙編也是可以用在8088 上的,但是就不能跑在8051 上,而JVM 的指令集則是可以到處運作的,因為JVM 做了翻譯,根據不同的CPU ,翻譯成不同的機器語言。

JVM 中我們最需要深入了解的就是它的存儲部分,存儲?硬碟?NO ,NO , JVM 是一個記憶體中的虛拟機,那它的存儲就是記憶體了,我們寫的所有類、常量、變量、方法都在記憶體中,這決定着我們程式運作的是否健壯、是否高效,接下來的部分就是重點介紹之。

2、JVM 的組成部分

我們先把JVM 這個虛拟機畫出來,如下圖所示:

java 虛拟機記憶體類_java 虛拟機類加載 及記憶體結構

從這個圖中可以看到,JVM 是運作在作業系統之上的,它與硬體沒有直接的互動。我們再來看下JVM 有哪些組成部分,如下圖所示:

java 虛拟機記憶體類_java 虛拟機類加載 及記憶體結構

該圖參考了網上廣為流傳的JVM 構成圖,大家看這個圖,整個JVM 分為四部分:

## Class Loader 類加載器

類加載器的作用是加載類檔案到記憶體,比如編寫一個HelloWord.java 程式,然後通過javac 編譯成class 檔案,那怎麼才能加載到記憶體中被執行呢?Class Loader 承擔的就是這個責任,那不可能随便建立一個.class 檔案就能被加載的,Class Loader 加載的class 檔案是有格式要求,在《JVM Specification 》中式這樣定義Class 檔案的結構:

java 虛拟機記憶體類_java 虛拟機類加載 及記憶體結構

ClassFile {

u4 magic;

u2 minor_version;

u2 major_version;

u2 constant_pool_count;

cp_info constant_pool[constant_pool_count-1];

u2 access_flags;

u2 this_class;

u2 super_class;

u2 interfaces_count;

u2 interfaces[interfaces_count];

u2 fields_count;

field_info fields[fields_count];

u2 methods_count;

method_info methods[methods_count];

u2 attributes_count;

attribute_info attributes[attributes_count];

}

java 虛拟機記憶體類_java 虛拟機類加載 及記憶體結構

需要詳細了解的話,可以仔細閱讀《JVM Specification 》的第四章“The class File Format ”,這裡不再詳細說明。

友情提示:Class Loader 隻管加載,隻要符合檔案結構就加載,至于說能不能運作,則不是它負責的,那是由Execution Engine 負責的。

## Execution Engine 執行引擎

執行引擎也叫做解釋器(Interpreter) ,負責解釋指令,送出作業系統執行。

## Native Interface 本地接口

本地接口的作用是融合不同的程式設計語言為Java 所用,它的初衷是融合C/C++ 程式,Java 誕生的時候是C/C++ 橫行的時候,要想立足,必須有一個聰明的、睿智的調用C/C++ 程式,于是就在記憶體中專門開辟了一塊區域處理标記為native 的代碼,它的具體做法是Native Method Stack 中登記native 方法,在Execution Engine 執行時加載native libraies 。目前該方法使用的是越來越少了,除非是與硬體有關的應用,比如通過Java 程式驅動列印機,或者Java 系統管理生産裝置,在企業級應用中已經比較少見,因為現在的異構領域間的通信很發達,比如可以使用Socket 通信,也可以使用Web Service 等等,不多做介紹。

## Runtime data area 運作資料區

運作資料區是整個JVM 的重點。我們所有寫的程式都被加載到這裡,之後才開始運作,Java 生态系統如此的繁榮,得益于該區域的優良自治。

整個JVM 架構由加載器加載檔案,然後執行器在記憶體中處理資料,需要與異構系統互動是可以通過本地接口進行,瞧,一個完整的系統誕生了!

3、JVM加載class檔案的原理機制

Java中的所有類,都需要由類加載器裝載到JVM中才能運作。類加載器本身也是一個類,而它的工作就是把class檔案從硬碟讀取到記憶體中。在寫程式的時候,我們幾乎不需要關心類的加載,因為這些都是隐式裝載的,除非我們有特殊的用法,像是反射,就需要顯式的加載所需要的類。

類裝載方式,有兩種

1.隐式裝載, 程式在運作過程中當碰到通過new 等方式生成對象時,隐式調用類裝載器加載對應的類到jvm中,

2.顯式裝載, 通過class.forname()等方法,顯式加載需要的類

隐式加載與顯式加載的差別:兩者本質是一樣?

Java類的加載是動态的,它并不會一次性将所有類全部加載後再運作,而是保證程式運作的基礎類(像是基類)完全加載到jvm中,至于其他類,則在需要的時候才加載。這當然就是為了節省記憶體開銷。

Java的類加載器有三個,對應Java的三種類:(java中的類大緻分為三種:   1.系統類   2.擴充類 3.由程式員自定義的類 )

Bootstrap Loader  // 負責加載系統類 (指的是内置類,像是String,對應于C#中的System類和C/C++标準庫中的類)

|

- - ExtClassLoader   // 負責加載擴充類(就是繼承類和實作類)

|

- - AppClassLoader   // 負責加載應用類(程式員自定義的類)

三個加載器各自完成自己的工作,但它們是如何協調工作呢?哪一個類該由哪個類加載器完成呢?為了解決這個問題,Java采用了委托模型機制。

委托模型機制的工作原理很簡單:當類加載器需要加載類的時候,先請示其Parent(即上一層加載器)在其搜尋路徑載入,如果找不到,才在自己的搜尋路徑搜尋該類。這樣的順序其實就是加載器層次上自頂而下的搜尋,因為加載器必須保證基礎類的加載。之是以是這種機制,還有一個安全上的考慮:如果某人将一個惡意的基礎類加載到jvm,委托模型機制會搜尋其父類加載器,顯然是不可能找到的,自然就不會将該類加載進來。

我們可以通過這樣的代碼來擷取類加載器:

ClassLoader loader = ClassName.class.getClassLoader();

ClassLoader ParentLoader = loader.getParent();

注意一個很重要的問題,就是Java在邏輯上并不存在BootstrapKLoader的實體!因為它是用C++編寫的,是以列印其内容将會得到null。

前面是對類加載器的簡單介紹,它的原理機制非常簡單,就是下面幾個步驟:

1.裝載:查找和導入class檔案;

2.連接配接:

(1)檢查:檢查載入的class檔案資料的正确性;

(2)準備:為類的靜态變量配置設定存儲空間;

(3)解析:将符号引用轉換成直接引用(這一步是可選的)

3.初始化:初始化靜态變量,靜态代碼塊。

這樣的過程在程式調用類的靜态成員的時候開始執行,是以靜态方法main()才會成為一般程式的入口方法。類的構造器也會引發該動作。