JVM原理與深度調優(一)

什麼是jvm

jvm是java虛擬機 運行在用戶態、經過應用程序實現java代碼跨平臺、與平臺無關、其實是"一次編譯,處處執行"java

1.從微觀來講編譯出來的是字節碼!去到哪一個平臺都能用,只要有那個平臺的JDK就能夠運行!字碼比如是一我的,平臺比如爲國家,JDK比如這個國家的語言!只要這我的(字節碼)有了這個國家的語言(JDK)就能夠在這個國家(平臺)生活下去。
2.JDK 是整個Java的核心,包括了Java運行環境(Java Runtime Envirnment),一堆Java工具和Java基礎的類庫(rt.jar)。
3.Java虛擬機(JVM)一種用於計算機設備的規範,可用不一樣的方式(軟件或硬件)加以實現。編譯虛擬機的指令集與編譯微處理器的指令集很是相似。Java虛擬機包括一套字節碼指令集、一組寄存器、一個棧、一個垃圾回收堆和一個存儲方法域。
4.java編譯出來的是一種「java字節碼」,由虛擬機去解釋執行。 而c和c++則編譯成了二進制,直接交由操做系統執行。
5.所謂的一次編譯、處處執行,即只需在一個地方編譯,在其餘各個平臺下均可以執行。
6.與平臺無關指的是JAVA只運行在本身的JVM上,不須要依賴任何其餘的底層類,因此和操做系統沒有任何聯繫,平臺是說運行的系統linux

內存結構圖

 

 class文件 

class文件徑打破了C或者C++等語言所遵循的傳統,使用這些傳統語言寫的程序一般首先被編譯,而後被鏈接成單獨的、專門支持特定硬件平臺和操做系統的二進制文件。一般狀況下,一個平臺上的二進制可執行文件不能在其餘平臺上工做。而Java class文件是能夠運行在任何支持Java虛擬機的硬件平臺和操做系統上的二進制文件。c++

 

執行過程

 執行過程簡介

當編譯和鏈接一個C++程序時,所得到的可執行 二進制文件只能在指定的硬件平臺和操做系統上運行,由於這個二進制文件包含了對目標處理器的 機器語言。而Java 編譯器把Java源文件的指令翻譯成 字節碼,這種字節碼就是Java 虛擬機的「機器語言」。
與普通程序不一樣的是,Java程序(class文件)並非本地的可執行程序。當運行Java程序時,首先運行 JVM(Java虛擬機),而後再把Java class加載到JVM裏頭運行,負責加載Java class的這部分就叫作Class Loader。

JVM中的ClassLoader

JVM自己包含了一個ClassLoader稱爲Bootstrap ClassLoader,和JVM同樣,BootstrapClassLoader是用 本地代碼實現的,它負責加載核心JavaClass(即全部java.*開頭的類)。
另外JVM還會提供兩個ClassLoader,它們都是用 Java語言編寫的,由BootstrapClassLoader加載;其中Extension ClassLoader負責加載擴展的Javaclass(例如全部javax.*開頭的類和存放在JRE的ext目錄下的類)ApplicationClassLoader負責加載應用程序自身的類。
當運行一個程序的時候,JVM啓動,運行bootstrapclassloader,該ClassLoader加載java核心API(ExtClassLoader和AppClassLoader也在此時被加載),而後調用ExtClassLoader加載擴展API,最後AppClassLoader加載CLASSPATH目錄下定義的Class,這就是一個程序最基本的加載流程。
 

第一個Class文件、經過javac編譯成字節碼、字節碼以後有個ClassLoader叫類加載器,由於java.class文件到JVM內部運行起來須要有個裝載過程、從物理的文件到內存的結構、好比加載、鏈接、初始化。程序員

linux應用程序有個進程地址空間,對進程地址空間的解釋:bootstrap

linux採用虛擬內存管理技術,每個進程都有一個3G大小的獨立的進程地址空間,這個地址空間就是用戶空間。每一個進程的用戶空間都是徹底獨立、互不相干的。進程訪問內核空間的方式:系統調用和中斷。
    建立進程等進程相關操做都須要分配內存給進程。這時進程申請和得到的不是物理地址,僅僅是虛擬地址。 
實際的物理內存只有當進程真的去訪問新獲取的虛擬地址時,纔會由「請頁機制」產生「缺頁」異常,從而進入分配實際頁框的程序。該異常是虛擬內存機制賴以存在的基本保證,它會告訴內核去爲進程分配物理頁,並創建對應的頁表,這以後虛擬地址才實實在在的映射到了物理地址上。安全

Linux操做系統採用虛擬內存技術,全部進程之間以虛擬方式共享內存。進程地址空間由每一個進程中的線性地址區組成,並且更爲重要的特色是內核容許進程使用該空間中的地址。一般狀況況下,每一個進程都有惟一的地址空間,並且進程地址空間之間彼此互不相干。可是進程之間也能夠選擇共享地址空間,這樣的進程就叫作線程。網絡

基本上全部linux應用程序都會遵循這個規泛、有棧、有堆、對於JVM來講、也是遵循這個規則、只不過在這個規則上作了一些改進併發

經過類加載器把Class文件裝載進內存空間、裝進來之後只是你的字節碼,而後你須要去運行、怎麼去運行呢 ?圖中類加載器子系統下面都是運行區
內存空間裏有:
1.方法區:被裝載的class的信息存儲在Methodarea的內存中。當虛擬機裝載某個類型時,它使用類裝載器定位相應的class文件,而後讀入這個class文件內容並把它傳輸到虛擬機中。
2.Heap(堆):一個Java虛擬實例中只存在一個堆空間。
3.JavaStack(java的棧):虛擬機只會直接對棧執行兩種操做:以幀爲單位的壓棧或出棧,java棧有個核心的數據、先進後出
4.Nativemethodstack(本地方法棧):經過字面意思、基本是調用系統本地的一些方法、通常在底層封裝好了、直接調用
5.地址、在這裏邊是一個指針的概念、好比從變量到對象怎麼作引用、就是地址
6.計數器:主要作字節碼解析的時候要記住它的位置、能夠理解爲一個標記
7.執行引擎:數據、字節碼作一些業務處理、最終達到想要的結果
8.本地方法接口:基本是底層系統、好比IO網絡、調用操做系統自己
9.本地方法庫:爲了兼容、實現跨平臺有不一樣的庫 、兼容平臺性
額外數據信息指的是本地方法接口和本地方法庫jvm

 

JMM

java的內存模型函數


你們可能聽過一個詞、叫線程安全、在寫高併發的時候就會有線程安全問題、java裏邊爲何會出現線程安全問題呢、由於有JMM的存在、它會把內存分爲兩個區域(一個主內存、一個是工做內存)工做內存是每一個java棧所私有的
由於要運行速度快、須要把主內存的數據放到本地內存中、而後進行計算、計算完之後再把數據回顯回去

 

JMM有兩個區域、主內存和棧內存、
java線程可能不止一個、可能有多個棧、如今須要三個線程同時作個運算、主內存初始值x=0 須要把x=0都要裝載在本身的內存裏邊去、至關於有一個
副本、如今初始值和三個棧都是x=0
如今須要作運算
x=x+1
x=x-1
x=0
咱們的指望值是x=0,若是是單個線程跑沒問題 、取回x=0、運算x=+一、回顯進來主內存就是1 、棧1是1,運算x=-一、回顯進來主內存就是0、棧1是0

若是多個線程同時執行、結果是不可預期的、正由於有這種結構的存在、當執行x=+一、棧1是x=1  、棧2來不及執行、棧1就已經把x=1寫到主內存了 、棧2跟棧3拿過去以後初始值就不是0、可能就是1了 、這樣程序就寫亂了 

因此在java中就出現了不少鎖、來確保線程安全 

 

 運行時數據區

PC寄存器----線程私有

PC寄存器也叫程序計數器(Program Counter Register)是一塊較小的內存空間,它的做用能夠看作是當前線程所執行的字節碼的信號指示器。
每一條JVM線程都有本身的PC寄存器
在任意時刻,一條JVM線程只會執行一個方法的代碼。該方法稱爲該線程的當前方法(Current Method)
若是該方法是java方法,那PC寄存器保存JVM正在執行的字節碼指令的地址
若是該方法是native,那PC寄存器的值是undefined。
此內存區域是惟一一個在Java虛擬機規範中沒有規定任何OutOfMemoryError狀況的區域。

Java虛擬機棧 ----線程私有

與PC寄存器同樣,java虛擬機棧(Java Virtual Machine Stack)也是線程私有的。每個JVM線程都有本身的java虛擬機棧,這個棧與線程同時建立,它的生命週期與線程相同。
虛擬機棧描述的是Java方法執行的內存模型:每一個方法被執行的時候都會同時建立一個棧幀(Stack Frame)用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。每個方法被調用直至執行完成的過程就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。
JVM stack 能夠被實現成固定大小,也能夠根據計算動態擴展。
若是採用固定大小的JVM stack設計,那麼每一條線程的JVM Stack容量應該在線程建立時獨立地選定。JVM實現應該提供調節JVM Stack初始容量的手段。
若是採用動態擴展和收縮的JVM Stack方式,應該提供調節最大、最小容量的手段。
JVM Stack 異常狀況:
StackOverflowError:當線程請求分配的棧容量超過JVM容許的最大容量時拋出
OutOfMemoryError:若是JVM Stack能夠動態擴展,可是在嘗試擴展時沒法申請到足夠的內存去完成擴展,或者在創建新的線程時沒有足夠的內存去建立對應的虛擬機棧時拋出。

 本地方法棧----線程私有

Java虛擬機可能會使用到傳統的棧來支持native方法(使用Java語言之外的其它語言編寫的方法)的執行,這個棧就是本地方法棧(Native Method Stack)

若是JVM不支持native方法,也不依賴與傳統方法棧的話,能夠無需支持本地方法棧。

若是支持本地方法棧,則這個棧通常會在線程建立的時候按線程分配。

異常狀況:

StackOverflowError:若是線程請求分配的棧容量超過本地方法棧容許的最大容量時拋出

OutOfMemoryError:若是本地方法棧能夠動態擴展,而且擴展的動做已經嘗試過,可是目前沒法申請到足夠的內存去完成擴展,或者在創建新的線程時沒有足夠的內存去建立對應的本地方法棧,那Java虛擬機將會拋出一個OutOfMemoryError異常。

Jave堆----線程公用

平時所說的java調優就是它
在JVM中,堆(heap)是可供各條線程共享的運行時內存區域,也是供全部類實例和數據對象分配內存的區域。
Java堆載虛擬機啓動的時候就被建立,堆中儲存了各類對象,這些對象被自動管理內存系統(Automatic Storage Management System,也便是常說的「Garbage Collector(垃圾回收器)」)所管理。這些對象無需、也沒法顯示地被銷燬。
Java堆的容量能夠是固定大小,也能夠隨着需求動態擴展,並在不須要過多空間時自動收縮。
Java堆所使用的內存不須要保證是物理連續的,只要邏輯上是連續的便可。
JVM實現應當提供給程序員調節Java 堆初始容量的手段,對於可動態擴展和收縮的堆來講,則應當提供調節其最大和最小容量的手段。
Java 堆異常:
OutOfMemoryError:若是實際所需的堆超過了自動內存管理系統能提供的最大容量時拋出。

方法區----線程公用

方法區是可供各條線程共享的運行時內存區域。存儲了每個類的結構信息,例如運行時常量池(Runtime Constant Pool)、字段和方法數據、構造函數和普通方法的字節碼內容、還包括一些在類、實例、接口初始化時用到的特殊方法

方法區在虛擬機啓動的時候建立。

方法區的容量能夠是固定大小的,也能夠隨着程序執行的需求動態擴展,並在不須要過多空間時自動收縮。

方法區在實際內存空間中能夠是不連續的。

Java虛擬機實現應當提供給程序員或者最終用戶調節方法區初始容量的手段,對於能夠動態擴展和收縮方法區來講,則應當提供調節其最大、最小容量的手段。

Java 方法區異常:

OutOfMemoryError: 若是方法區的內存空間不能知足內存分配請求,那Java虛擬機將拋出一個OutOfMemoryError異常。

相關文章
相關標籤/搜索