慢慢琢磨JVM

1 JVM簡介

JVM是一個Javaer的最基本功底了,剛開始學Java的時候,通常都是從「Hello World」開始的,而後會寫個複雜點class,而後再找一些開源框架,好比SpringHibernate等等,再而後就開發企業級的應用,好比網站、企業內部應用、實時交易系統等等,直到某一天忽然發現作的系統咋就這麼慢呢,並且時不時還來個內存溢出什麼的,今天是交易系統報了StackOverflowError,明天是網站系統報了個OutOfMemoryError,這種錯誤又很難重現,只有分析Javacoredump文件,運氣好點還能分析出個結果,運行遭的點,就直接去廟裏燒香吧!天天接客戶的電話都是戰戰兢兢的,生怕再出什麼幺蛾子了。我想Java作的久一點的都有這樣的經歷,那這些問題的最終根結是在哪呢?—— JVMjava

JVM全稱是Java Virtual MachineJava虛擬機,也就是在計算機上再虛擬一個計算機,這和咱們使用 VMWare不同,那個虛擬的東西你是能夠看到的,這個JVM你是看不到的,它存在內存中。咱們知道計算機的基本構成是:運算器、控制器、存儲器、輸入和輸出設備,那這個JVM也是有這成套的元素,運算器是固然是交給硬件CPU還處理了,只是爲了適應一次編譯,隨處運行的狀況,須要作一個翻譯動做,因而就用了JVM本身的命令集,這與彙編的命令集有點相似,每一種彙編命令集針對一個系列的CPU,好比8086系列的彙編也是能夠用在8088上的,可是就不能跑在8051上,而JVM的命令集則是能夠處處運行的,由於JVM作了翻譯,根據不一樣的CPU,翻譯成不一樣的機器語言。程序員

JVM中咱們最須要深刻理解的就是它的存儲部分,存儲?硬盤?NONO JVM是一個內存中的虛擬機,那它的存儲就是內存了,咱們寫的全部類、常量、變量、方法都在內存中,這決定着咱們程序運行的是否健壯、是否高效,接下來的部分就是重點介紹之。算法

 

2 JVM的組成部分

咱們先把JVM這個虛擬機畫出來,以下圖所示:編程

 

 

 

從這個圖中能夠看到,JVM是運行在操做系統之上的,它與硬件沒有直接的交互。咱們再來看下JVM有哪些組成部分,以下圖所示:安全



 該圖參考了網上廣爲流傳的JVM構成圖,你們看這個圖,整個JVM分爲四部分:網絡

 

q  Class Loader 類加載器框架

類加載器的做用是加載類文件到內存,好比編寫一個HelloWord.java程序,而後經過javac編譯成class文件,那怎麼才能加載到內存中被執行呢?Class Loader承擔的就是這個責任,那不可能隨便創建一個.class文件就能被加載的,Class Loader加載的class文件是有格式要求,在《JVM Specification》中式這樣定義Class文件的結構:編程語言

    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];

    }

須要詳細瞭解的話,能夠仔細閱讀《JVM Specification》的第四章「The class File Format」,這裏再也不詳細說明。

友情提示:Class Loader只管加載,只要符合文件結構就加載,至於說能不能運行,則不是它負責的,那是由Execution Engine負責的。

q  Execution Engine 執行引擎

執行引擎也叫作解釋器(Interpreter),負責解釋命令,提交操做系統執行。

q  Native Interface本地接口

本地接口的做用是融合不一樣的編程語言爲Java所用,它的初衷是融合C/C++程序,Java誕生的時候是C/C++橫行的時候,要想立足,必須有一個聰明的、睿智的調用C/C++程序,因而就在內存中專門開闢了一塊區域處理標記爲native的代碼,它的具體作法是Native Method Stack中登記native方法,在Execution Engine執行時加載native libraies。目前該方法使用的是愈來愈少了,除非是與硬件有關的應用,好比經過Java程序驅動打印機,或者Java系統管理生產設備,在企業級應用中已經比較少見,由於如今的異構領域間的通訊很發達,好比可使用Socket通訊,也可使用Web Service等等,很少作介紹。

q  Runtime data area運行數據區

運行數據區是整個JVM的重點。咱們全部寫的程序都被加載到這裏,以後纔開始運行,Java生態系統如此的繁榮,得益於該區域的優良自治,下一章節詳細介紹之。

 

整個JVM框架由加載器加載文件,而後執行器在內存中處理數據,須要與異構系統交互是能夠經過本地接口進行,瞧,一個完整的系統誕生了!

2 JVM的內存管理

全部的數據和程序都是在運行數據區存放,它包括如下幾部分:

q  Stack 

棧也叫棧內存,是Java程序的運行區,是在線程建立時建立,它的生命期是跟隨線程的生命期,線程結束棧內存也就釋放,對於棧來講不存在垃圾回收問題,只要線程一結束,該棧就Over。問題出來了:棧中存的是那些數據呢?又什麼是格式呢?

棧中的數據都是以棧幀(Stack Frame)的格式存在,棧幀是一個內存區塊,是一個數據集,是一個有關方法(Method)和運行期數據的數據集,當一個方法A被調用時就產生了一個棧幀F1,並被壓入到棧中,A方法又調用了B方法,因而產生棧幀F2也被壓入棧,執行完畢後,先彈出F2棧幀,再彈出F1棧幀,遵循先進後出原則。

那棧幀中到底存在着什麼數據呢?棧幀中主要保存3類數據:本地變量(Local Variables),包括輸入參數和輸出參數以及方法內的變量;棧操做(Operand Stack),記錄出棧、入棧的操做;棧幀數據(Frame Data),包括類文件、方法等等。光說比較枯燥,咱們畫個圖來理解一下Java棧,以下圖所示:



 圖示在一個棧中有兩個棧幀,棧幀2是最早被調用的方法,先入棧,而後方法2又調用了方法1,棧幀1處於棧頂的位置,棧幀2處於棧底,執行完畢後,依次彈出棧幀1和棧幀2,線程結束,棧釋放。

q  Heap 堆內存

一個JVM實例只存在一個堆類存,堆內存的大小是能夠調節的。類加載器讀取了類文件後,須要把類、方法、常變量放到堆內存中,以方便執行器執行,堆內存分爲三部分:

Permanent Space 永久存儲區

永久存儲區是一個常駐內存區域,用於存放JDK自身所攜帶的Class,Interface的元數據,也就是說它存儲的是運行環境必須的類信息,被裝載進此區域的數據是不會被垃圾回收器回收掉的,關閉JVM纔會釋放此區域所佔用的內存。

Young Generation Space 新生區

新生區是類的誕生、成長、消亡的區域,一個類在這裏產生,應用,最後被垃圾回收器收集,結束生命。新生區又分爲兩部分:伊甸區(Eden space)和倖存者區(Survivor pace),全部的類都是在伊甸區被new出來的。倖存區有兩個: 0區(Survivor 0 space)和1區(Survivor 1 space)。當伊甸園的空間用完時,程序又須要建立對象,JVM的垃圾回收器將對伊甸園區進行垃圾回收,將伊甸園區中的再也不被其餘對象所引用的對象進行銷燬。而後將伊甸園中的剩餘對象移動到倖存0區。若倖存0區也滿了,再對該區進行垃圾回收,而後移動到1區。那若是1區也滿了呢?再移動到養老區。

Tenure generation space養老區

養老區用於保存重新生區篩選出來的JAVA對象,通常池對象都在這個區域活躍。   三個區的示意圖以下:



 q  Method Area 方法區

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

q  PC Register 程序計數器

每一個線程都有一個程序計數器,就是一個指針,指向方法區中的方法字節碼,由執行引擎讀取下一條指令。

q  Native Method Stack 本地方法棧

 

 

 

3 JVM相關問題

問:堆和棧有什麼區別

答:堆是存放對象的,可是對象內的臨時變量是存在棧內存中,如例子中的methodVar是在運行期存放到棧中的。

棧是跟隨線程的,有線程就有棧,堆是跟隨JVM的,有JVM就有堆內存。

 

問:堆內存中到底存在着什麼東西?

答:對象,包括對象變量以及對象方法。

 

問:類變量和實例變量有什麼區別?

答:靜態變量是類變量,非靜態變量是實例變量,直白的說,有static修飾的變量是靜態變量,沒有static修飾的變量是實例變量。靜態變量存在方法區中,實例變量存在堆內存中。

 

問:我據說類變量是在JVM啓動時就初始化好的,和你這說的不一樣呀!

答:那你是道聽途說,信個人,沒錯。

 

問:Java的方法(函數)究竟是傳值仍是傳址?

答:都不是,是以傳值的方式傳遞地址,具體的說原生數據類型傳遞的值,引用類型傳遞的地址。對於原始數據類型,JVM的處理方法是從Method AreaHeap中拷貝到Stack,而後運行frame中的方法,運行完畢後再把變量指拷貝回去。

 

問:爲何會產生OutOfMemory產生?

答:一句話:Heap內存中沒有足夠的可用內存了。這句話要好好理解,不是說Heap沒有內存了,是說新申請內存的對象大於Heap空閒內存,好比如今Heap還空閒1M,可是新申請的內存須要1.1M,因而就會報OutOfMemory了,可能之後的對象申請的內存都只要0.9M,因而就只出現一次OutOfMemoryGC也正常了,看起來像偶發事件,就是這麼回事。       但若是此時GC沒有回收就會產生掛起狀況,系統不響應了。

 

問:我產生的對象很少呀,爲何還會產生OutOfMemory

答:你繼承層次忒多了,Heap 產生的對象是先產生 父類,而後才產生子類,明白不?

 

問:OutOfMemory錯誤分幾種?

答:分兩種,分別是「OutOfMemoryError:java heap size」」OutOfMemoryError: PermGen space」,兩種都是內存溢出,heap size是說申請不到新的內存了,這個很常見,檢查應用或調整堆內存大小。

「PermGen space」是由於永久存儲區滿了,這個也很常見,通常在熱發佈的環境中出現,是由於每次發佈應用系統都不重啓,長此以往永久存儲區中的死對象太多致使新對象沒法申請內存,通常從新啓動一下便可。

 

問:爲何會產生StackOverflowError

答:由於一個線程把Stack內存所有耗盡了,通常是遞歸函數形成的。

 

問:一個機器上能夠看多個JVM嗎?JVM之間能夠互訪嗎?

答:能夠多個JVM,只要機器承受得了。JVM之間是不能夠互訪,你不能在A-JVM中訪問B-JVMHeap內存,這是不可能的。在之前老版本的JVM中,會出現A-JVM Crack後影響到B-JVM,如今版本很是少見。

 

問:爲何Java要採用垃圾回收機制,而不採用C/C++的顯式內存管理?

答:爲了簡單,內存管理不是每一個程序員都能折騰好的。

 

問:爲何你沒有詳細介紹垃圾回收機制?

答:垃圾回收機制每一個JVM都不一樣,JVM Specification只是定義了要自動釋放內存,也就是說它只定義了垃圾回收的抽象方法,具體怎麼實現各個廠商都不一樣,算法各異,這東西實在不必深刻。

 

問:JVM中到底哪些區域是共享的?哪些是私有的?

答:HeapMethod Area是共享的,其餘都是私有的,

 

問:什麼是JIT,你怎麼沒說?

答:JIT是指Just In Time,有的文檔把JIT做爲JVM的一個部件來介紹,有的是做爲執行引擎的一部分來介紹,這都能理解。Java剛誕生的時候是一個解釋性語言,別噓,即便編譯成了字節碼(byte code)也是針對JVM的,它須要再次翻譯成原生代碼(native code)才能被機器執行,因而效率的擔心就提出來了。Sun爲了解決該問題提出了一套新的機制,好,你想編譯成原生代碼,沒問題,我在JVM上提供一個工具,把字節碼編譯成原生碼,下次你來訪問的時候直接訪問原生碼就成了,因而JIT就誕生了,就這麼回事。

 

問:JVM還有哪些部分是你沒有提到的?

答:JVM是一個異常複雜的東西,寫一本磚頭書都不爲過,還有幾個要說明的:

常量池(constant pool):按照順序存放程序中的常量,而且進行索引編號的區域。好比int i =100,這個100就放在常量池中。

安全管理器(Security Manager):提供Java運行期的安全控制,防止惡意攻擊,好比指定讀取文件,寫入文件權限,網絡訪問,建立進程等等,Class LoaderSecurity Manager認證經過後才能加載class文件的。

方法索引表(Methods table),記錄的是每一個method的地址信息,StackHeap中的地址指針實際上是指向Methods table地址。

      

問:爲何不建議在程序中顯式的生命System.gc()

答:由於顯式聲明是作堆內存全掃描,也就是Full GC,是須要中止全部的活動的(Stop  The World Collection),你的應用能承受這個嗎?

 

問:JVM有哪些調整參數?

答:很是多,本身去找,堆內存、棧內存的大小均可以定義,甚至是堆內存的三個部分、新生代的各個比例都能調整。

相關文章
相關標籤/搜索