走進JVM,淺水也能捉魚
這不是一篇描述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檔案格式;
The ClassFile Structure ClassFile{ u4 magic; //魔數 u2 minor_version; //class 次版本號 u2 major_version; //class 主版本號 u2 constant_pool_count; //常量池計數 cp_info constant_pool[constant_pool_count-1]; //常量池 u2 access_flags; //修飾符 u2 this_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[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開始)都是使用此演算法的。
相關文章
- GravityFlow2自動換水魚缸:養魚不用換水
- 資料也能進超市
- 帶你走進Choerodon豬齒魚的知識管理
- 淺談走進企業的“法”與“術”薦
- 太空上也能進行網路連線?
- 這款智慧魚缸養魚都不用換水!懶人必備
- Jvm 淺析JVM
- 讓微信小程式開發如魚得水微信小程式
- 走進JavaJava
- JVM調優淺談JVM
- 淺談JVM垃圾回收JVM
- 走進Oracle世界Oracle
- 【RabbitMQ】走進RabbitMQMQ
- JavaScript也能寫WebAssemblyJavaScriptWeb
- 開源,還能走多遠?
- 沒有學位,通過這四步也能進GoogleGo
- 資料洪流時代:劍魚和它引領的搏水者
- 如何進行 Python效能分析,你才能如魚得水?Python
- 帶你走進 koa2 的世界(koa2 原始碼淺談)原始碼
- 淺談JVM與垃圾回收JVM
- IT一路走來我還能走多遠–出路薦
- 筆記本進水了怎麼辦?筆記本進水應急策略筆記
- 走進 JDK 之 IntegerJDK
- 走進 JDK 之 BooleanJDKBoolean
- 走進 JDK 之 FloatJDK
- 走進 JDK 之 LongJDK
- 走進 JDK 之 EnumJDK
- 走進springbootSpring Boot
- 帶你走進 RedisRedis
- 走進mysql基礎MySql
- 程式也能是雞湯
- 也曾妄想仗劍走天涯(年中總結)
- 深入淺出之JVM GC篇JVMGC
- 編碼也快樂:兩隻水壺F#程式
- 編碼也快樂:兩隻水壺Scheme程式Scheme
- 編碼也快樂:兩隻水壺C#程式C#
- 走進WebApiClientCore的設計WebAPIclient
- 走進 JDK 之 StringJDK