這不是一篇描述jvm是什麼的文章,也不介紹jvm跨平臺的特性,也不是講述jvm安全特性的文章,更不是講解jvm指令操做,數據運算的文章,本文重點講述類型的生命週期。
類型的生命週期涉及到:類的裝載、jvm體系結構、垃圾回收機制。
爲何要講jvm體系結構?由於類的裝載和垃圾回收機制都和jvm體系結構息息相關。
那麼什麼是jvm體系結構呢?
當jvm運行起來的時候,它會向系統申請一片內存區(不一樣的jvm實現可能不一樣,有些可使用虛擬內存),將這塊內存分出一部分存儲許多東西,例如:程序建立的對象,傳遞給方法的參數,返回值,局部變量等等,咱們將這塊內存稱之爲運行時數據區,運行時數據區能夠劃分紅方法區、堆、java棧、pc寄存器、本地方法棧。看到上面這幅圖,和這些解說你可能大概的明白jvm體系是個啥樣子,可是你或許還不瞭解運行時數據區裏面方法區等用來幹嗎的。
- 方法區:當虛擬機裝載一個class文件的時候,它會從這個class文件包含的二進制數據中解析類型信息,而後將這些類型信息放到方法區中。由於方法區是被全部線程共享的,因此必須考慮數據的線程安全。假如兩個線程都在試圖找lava的類,在lava類尚未被加載的狀況下,只應該有一個線程去加載,而另外一個線程等待。
- PC寄存器:每一個新線程產生都將獲得本身的pc寄存器以及一個java棧幀。
- 堆:存放程序運行時產生的全部對象。堆是一個線程共享的內存區,因此咱們寫多線程程序的時候須要考慮併發。
- Java棧:java棧由許多棧幀組成的,如圖,當一個線程調用java方法時,虛擬機壓入一個新的棧幀到java棧中,當方法返回的時候,這個棧幀被從java棧彈出並被拋棄。

那麼如今你應該能夠想象到一些jvm是怎麼工做的了,是否是應該接着講具體工做原理了呢?。可是不急,先了解下類的裝載機制。
瞭解類的裝載機制以前先了解jvm裏面的類裝載器:BootstrapLoader、ExtClassLoader、AppClassLoader;ExtClassLoader(負責裝載jre下面的rt.jar,charsets.jar)和AppClassLoader(負責轉載classpath下面的類包)是ClassLoader(抽象類)的子類;
BootstrapLoader(負責裝載jre核心類庫)是根裝載器,是c/c++寫的,在java裏面看不到它。
這三個類裝載器存在父子關係,根裝載器是ExtClassLoader父裝載器,ExtClassLoader是AppClassLoader父裝載器;
Jvm中類的裝載也是安全機制沙箱模型的第一道門檻。Java裝載類使用雙親委派模式即全盤負責委託機制。好如今讓咱們瞭解裝載大概流程。
當裝載一個類的時候,如果由用戶指定一個類裝載器裝載的話,那麼那個類裝載器會先委派給父類裝載器,一直委派到根裝載器,若是裝載的是一個java.lang.String,因爲它是核心類庫的並且已經被裝載過了,那麼就會直接返回一個class對象,那麼若是是一個根裝載器找不到的類呢?接着就會交給子類(下一級父類)裝載器,若是仍是沒有找到類文件,接着就會由以前用戶指定的那個類裝載器裝載。(這裏沒有說明裝載超類的過程,請勿疏忽)。
若是是有人惡意的寫了一個基礎類java.lang.String,那麼會影響虛擬機嗎?不會由於這個類最終會交由根裝載器裝載,而根裝載器只會去jre核心類庫加載,最終返回的class類型並非用戶寫的String,並且系統自帶的String,也就是說用戶寫String永遠不會被加載。
瞭解了類裝載器是怎麼工做了以後,咱們也須要了解下class文件格式;
TheClassFileStructure
ClassFile{
u4magic;
//
魔數
u2minor_version;
//
class次版本號
u2major_version;
//
class主版本號
u2constant_pool_count;
//
常量池計數
cp_infoconstant_pool[constant_pool_count
-
1
];
//
常量池
u2access_flags;
//
修飾符
u2this_class;
//
常量池索引
u2interfaces_count;
u2interfaces[interfaces_count];
u2fields_count;
field_infofields[fields_count];
u2methods_count;
method_infomethods[methods_count];
u2attributes_count;
attribute_infoattributes[attrributes_count];
}
咱們須要瞭解的有不少,可是咱們難以理解的就是cp_infoconstant_pool常量池。
一個常量池裏面有不少表:
CONSTANT_Utf8 UTF-8編碼的Unicode字符串
CONSTANT_Integer int類型的字面值
CONSTANT_Float float類型的字面值
CONSTANT_Long long類型的字面值
CONSTANT_Double double類型的字面值
CONSTANT_Class 對一個類或接口的符號引用
CONSTANT_String String類型字面值的引用
CONSTANT_Field ref對一個字段的符號引用
CONSTANT_Method ref對一個類中方法的符號引用
CONSTANT_InterfaceMethod ref對一個接口中方法的符號引用
CONSTANT_NameAndType 對一個字段或方法的部分符號引用
這些表結構我也不解釋了,若是對class文件不夠了解也沒什麼關係,知道個大概也行。那麼咱們瞭解了jvm體系,類裝載器工做流程,那麼咱們細看下類裝載器工做中,jvm運行時數據區的變化,方法區裏面的結構等等。
在類裝載的過程當中,每個類裝載器都會在方法區裏面造成一張表,這張表記載着該裝載器和對應的類的權限定名。沒這麼一張表就造成了jvm內部的命名空間。同時在方法區裏面還該類的常量池等信息。
那麼說到這些,其實這個過程仍是很模糊,並且不少知識也落下了,那麼咱們如今看一個詳細一點的裝載過程。
當裝載一個普通的類的時候,即調用類裝載器的loadClass方法,若是但願裝載的類尚未被裝載到命名空間,那麼jvm會傳遞一個該類型的全限定名給類裝載器,也就是常量池CONSTANT_Class_info(該表存儲着父類、類裝載器等信息)入口的裝載器,來試圖裝載被引用的類型,若是發起引用的類型是被jvm裝載器定義的,那麼由jvm類裝載器裝載,不然由用戶自定義裝載器裝載,那麼一旦被引用的類型被裝載了,jvm仔細檢查它的二進制數據,若是類是是一個類,而且不是java.lang.Object。jvm根據數據獲得它的全限定名進行裝載(遞歸的應用了)這個過程還須要遞歸超接口。
裝載差很少講完了,一個完整的過程是:裝載鏈接——初始化。
那麼鏈接和初始化就一帶而過了,重點放在垃圾回收。
鏈接的過程主要是驗證(確認類型符合java語言的語義,而且它不會危及虛擬機的完整性)、準備(java虛擬機爲類變量分配內存,設計默認初始值)、解析(在類型的常量池中尋找類、接口、字段和方法的符合引用,把這些符號引用替換成直接引用的過程)。
初始化的時候,若是類存在直接超類,且超類尚未被初始化,就先初始化直接超類。初始化接口並不須要初始化它的父接口。
補充:
Jvm當運行某個方法的時候,先把這個方法壓入java棧中,裏面包含局部變量等信息,那麼對象放入哪裏呢?壓入棧的是對象的引用,即變量,全部的對象都存儲在堆中。
爲何要把對象放入堆,把變量之類的數據放入棧呢?說白了,對象太大了,存入棧中運算麻煩。(固然標準的回答不是這樣的,我這裏僅僅是說明實質)
瞭解了這麼一個過程以後,咱們必然要了解垃圾回收機制了。
基本回收算法
1. 引用計數:比較古老的回收算法。原理是此對象有一個引用,即增長一個計數,刪除一個引用則減小一個計數。垃圾回收時,只用收集計數爲0的對象。此算法最致命的是沒法處理循環引用的問題。
2. 標記-清除:此算法執行分兩階段。第一階段從引用根節點開始標記全部被引用的對象,第二階段遍歷整個堆,把未標記的對象清除。此算法須要暫停整個應用,同時,會產生內存碎片。
3. 複製:此算法把內存空間劃爲兩個相等的區域,每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象複製到另一個區域中。次算法每次只處理正在使用中的對象,所以複製成本比較小,同時複製過去之後還能進行相應的內存整理,不過出現碎片問題。固然,此算法的缺點也是很明顯的,就是須要兩倍內存空間。
4. 標記-整理:此算法結合了標記-清除和複製兩個算法的優勢。也是分兩階段,第一階段從根節點開始標記全部被引用對象,第二階段遍歷整個堆,把清除未標記對象而且把存活對象壓縮到堆的其中一塊,按順序排放。此算法避免了標記-清除的碎片問題,同時也避免了複製算法的空間問題。
5. 增量收集:實施垃圾回收算法,即:在應用進行的同時進行垃圾回收。
6. 分代:基於對對象生命週期分析後得出的垃圾回收算法。把對象分爲年青代、年老代、持久代,對不一樣生命週期的對象使用不一樣的算法(上述方式中的一個)進行回收。如今的垃圾回收器(從J2SE1.2開始)都是使用此算法的。