JVM學習(一)

前言

最近想更加系統研究下一些平時用到的東西的底層運做原理。程序員

因此打算從最簡單的Java底層開始,毫無疑問,想學習JVM。算法


簡介

JDK(Java Development Kit) 是 Java 語言的軟件開發工具包(SDK)。在JDK的安裝目錄下有一個jre目錄,裏面有兩個文件夾bin和lib,在這裏能夠認爲bin裏的就是jvm,lib中則是jvm工做所須要的類庫,而jvm和 lib合起來就稱爲jre。數組

而後咱們來看JRE
JRE(Java Runtime Environment,Java運行環境), 包含JVM標準實現及Java核心類庫。JRE是Java運行環境,並非一個開發環境,因此沒有包含任何開發工具(如編譯器和調試器)
最後JVM也一目瞭然了
JVM是Java Virtual Machine(Java 虛擬機)的縮寫,JVM是一種用於計算設備的規範,它是 一個虛構出來的計算機,是經過在實際的計算機上仿真模擬各類計算機功能來實現的。

 


 

基本概念

 

上面是JVM的邏輯內存模型,咱們首先簡單介紹下主要的概念:服務器

一、程序計數器數據結構

程序計數器(Program Counter Register)是一塊較小的內存空間,它的做用能夠看作是當前線程所執行的字節碼的行號指示器。在虛擬機的概念模型裏(僅是概念模型,各類虛擬機可能會經過一些更高效的方式去實現),字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器來完成。因爲Java 虛擬機的多線程是經過線程輪流切換並分配處理器執行時間的方式來實現的,在任何一個肯定的時刻,一個處理器(對於多核處理器來講是一個內核)只會執行一條線程中的指令。所以,爲了線程切換後能恢復到正確的執行位置,每條線程都須要有一個獨立的程序計數器,各條線程之間的計數器互不影響,獨立存儲,咱們稱這類內存區域爲「線程私有」的內存。若是線程正在執行的是一個Java 方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;若是正在執行的是Natvie 方法,這個計數器值則爲空(Undefined)。此內存區域是惟一一個在Java 虛擬機規範中沒有規定任何OutOfMemoryError 狀況的區域。多線程

二、Java 虛擬機棧jvm

與程序計數器同樣,Java 虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命週期與線程相同。虛擬機棧描述的是Java 方法執行的內存模型:每一個方法被執行的時候都會同時建立一個棧幀(Stack Frame ①)用於存儲局部變量表、操做棧、動態連接、方法出口等信息。每個方法被調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。常常有人把Java 內存區分爲堆內存(Heap)和棧內存(Stack),這種分法比較粗糙,Java 內存區域的劃分實際上遠比這複雜。這種劃分方式的流行只能說明大多數程序員最關注的、與對象內存分配關係最密切的內存區域是這兩塊。其中所指的「堆」在後面會專門講述,而所指的「棧」就是如今講的虛擬機棧,或者說是虛擬機棧中的局部變量表部分。局部變量表存放了編譯期可知的各類基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用(reference 類型,它不等同於對象自己,根據不一樣的虛擬機實現,它多是一個指向對象起始地址的引用指針,也可能指向一個表明對象的句柄或者其餘與此對象相關的位置)和returnAddress 類型(指向了一條字節碼指令的地址)。其中64 位長度的long 和double 類型的數據會佔用2 個局部變量空間(Slot),其他的數據類型只佔用1 個。局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時,這個方法須要在幀中分配多大的局部變量空間是徹底肯定的,在方法運行期間不會改變局部變量表的大小。在Java 虛擬機規範中,對這個區域規定了兩種異常情況:若是線程請求的棧深度大於虛擬機所容許的深度,將拋出StackOverflowError 異常;若是虛擬機棧能夠動態擴展(當前大部分的Java 虛擬機均可動態擴展,只不過Java 虛擬機規範中也容許固定長度的虛擬機棧),當擴展時沒法申請到足夠的內存時會拋出OutOfMemoryError 異常。函數

三、本地方法棧工具

本地方法棧(Native Method Stacks)與虛擬機棧所發揮的做用是很是類似的,其區別不過是虛擬機棧爲虛擬機執行Java 方法(也就是字節碼)服務,而本地方法棧則是爲虛擬機使用到的Native 方法服務。虛擬機規範中對本地方法棧中的方法使用的語言、使用方式與數據結構並無強制規定,所以具體的虛擬機能夠自由實現它。甚至有的虛擬機(譬如Sun HotSpot 虛擬機)直接就把本地方法棧和虛擬機棧合二爲一。與虛擬機棧同樣,本地方法棧區域也會拋出StackOverflowError 和OutOfMemoryError異常。性能

四、Java 堆

對於大多數應用來講,Java 堆(Java Heap)是Java 虛擬機所管理的內存中最大的一塊。Java 堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。此內存區域的惟一目的就是存放對象實例,幾乎全部的對象實例都在這裏分配內存。這一點在Java 虛擬機規範中的描述是:全部的對象實例以及數組都要在堆上分配①,可是隨着JIT 編譯器的發展與逃逸分析技術的逐漸成熟,棧上分配、標量替換②優化技術將會致使一些微妙的變化發生,全部的對象都分配在堆上也漸漸變得不是那麼「絕對」了。Java 堆是垃圾收集器管理的主要區域,所以不少時候也被稱作「GC 堆」(GarbageCollected Heap,幸虧國內沒翻譯成「垃圾堆」)。若是從內存回收的角度看,因爲如今收集器基本都是採用的分代收集算法,因此Java 堆中還能夠細分爲:新生代和老年代;再細緻一點的有Eden 空間、From Survivor 空間、To Survivor 空間等。若是從內存分配的角度看,線程共享的Java 堆中可能劃分出多個線程私有的分配緩衝區(Thread LocalAllocation Buffer,TLAB)。不過,不管如何劃分,都與存放內容無關,不管哪一個區域,存儲的都仍然是對象實例,進一步劃分的目的是爲了更好地回收內存,或者更快地分配內存。在本章中,咱們僅僅針對內存區域的做用進行討論,Java 堆中的上述各個區域的分配和回收等細節將會是下一章的主題。根據Java 虛擬機規範的規定,Java 堆能夠處於物理上不連續的內存空間中,只要邏輯上是連續的便可,就像咱們的磁盤空間同樣。在實現時,既能夠實現成固定大小的,也能夠是可擴展的,不過當前主流的虛擬機都是按照可擴展來實現的(經過-Xmx和-Xms 控制)。若是在堆中沒有內存完成實例分配,而且堆也沒法再擴展時,將會拋出OutOfMemoryError 異常。

四、方法區

方法區(Method Area)與Java 堆同樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。雖然Java 虛擬機規範把方法區描述爲堆的一個邏輯部分,可是它卻有一個別名叫作Non-Heap(非堆),目的應該是與Java 堆區分開來。對於習慣在HotSpot 虛擬機上開發和部署程序的開發者來講,不少人願意把方法區稱爲「永久代」(Permanent Generation),本質上二者並不等價,僅僅是由於HotSpot 虛擬機的設計團隊選擇把GC 分代收集擴展至方法區,或者說使用永久代來實現方法區而已。對於其餘虛擬機(如BEA JRockit、IBM J9 等)來講是不存在永久代的概念的。即便是HotSpot 虛擬機自己,根據官方發佈的路線圖信息,如今也有放棄永久代並「搬家」至Native Memory 來實現方法區的規劃了。Java 虛擬機規範對這個區域的限制很是寬鬆,除了和Java 堆同樣不須要連續的內存和能夠選擇固定大小或者可擴展外,還能夠選擇不實現垃圾收集。相對而言,垃圾收集行爲在這個區域是比較少出現的,但並不是數據進入了方法區就如永久代的名字同樣「永久」存在了。這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載,通常來講這個區域的回收「成績」比較難以使人滿意,尤爲是類型的卸載,條件至關苛刻,可是這部分區域的回收確實是有必要的。在Sun 公司的BUG 列表中,曾出現過的若干個嚴重的BUG 就是因爲低版本的HotSpot 虛擬機對此區域未徹底回收而致使內存泄漏。根據Java 虛擬機規範的規定,當方法區沒法知足內存分配需求時,將拋出OutOfMemoryError 異常。

五、運行時常量池

運行時常量池(Runtime Constant Pool)是方法區的一部分。Class 文件中除了有類的版本、字段、方法、接口等描述等信息外,還有一項信息是常量池(Constant PoolTable),用於存放編譯期生成的各類字面量和符號引用,這部份內容將在類加載後存放到方法區的運行時常量池中。Java 虛擬機對Class 文件的每一部分(天然也包括常量池)的格式都有嚴格的規定,每個字節用於存儲哪一種數據都必須符合規範上的要求,這樣纔會被虛擬機承認、裝載和執行。但對於運行時常量池,Java 虛擬機規範沒有作任何細節的要求,不一樣的提供商實現的虛擬機能夠按照本身的須要來實現這個內存區域。不過,通常來講,除了保存Class 文件中描述的符號引用外,還會把翻譯出來的直接引用也存儲在運行時常量池中①。運行時常量池相對於Class 文件常量池的另一個重要特徵是具有動態性,Java 語言並不要求常量必定只能在編譯期產生,也就是並不是預置入Class 文件中常量池的內容才能進入方法區運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用得比較多的即是String 類的intern() 方法。既然運行時常量池是方法區的一部分,天然會受到方法區內存的限制,當常量池沒法再申請到內存時會拋出OutOfMemoryError 異常

六、直接內存

直接內存(Direct Memory)並非虛擬機運行時數據區的一部分,也不是Java虛擬機規範中定義的內存區域,可是這部份內存也被頻繁地使用,並且也可能致使OutOfMemoryError 異常出現,因此咱們放到這裏一塊兒講解。在JDK 1.4 中新加入了NIO(New Input/Output)類,引入了一種基於通道(Channel)與緩衝區(Buffer)的I/O 方式,它可使用Native 函數庫直接分配堆外內存,而後經過一個存儲在Java 堆裏面的DirectByteBuffer 對象做爲這塊內存的引用進行操做。這樣能在一些場景中顯著提升性能,由於避免了在Java 堆和Native 堆中來回複製數據。顯然,本機直接內存的分配不會受到Java 堆大小的限制,可是,既然是內存,則確定仍是會受到本機總內存(包括RAM 及SWAP 區或者分頁文件)的大小及處理器尋址空間的限制。服務器管理員配置虛擬機參數時,通常會根據實際內存設置-Xmx等參數信息,但常常會忽略掉直接內存,使得各個內存區域的總和大於物理內存限制(包括物理上的和操做系統級的限制),從而致使動態擴展時出現OutOfMemoryError異常。

沒完待更新。。

相關文章
相關標籤/搜索