類加載器是將編譯好的class文件加載到內存中,並進行驗證、初始化等步驟,造成能被jvm直接使用的類型。
加載過程
可分解爲5個步驟:加載–>鏈接–>初始化–>使用–>卸載。
① 驗證:驗證文件格式、元數據、字節碼是否符合規範。
② 準備:爲成員變量分配內存並初始化值。
③ 解析:解析是虛擬機將常量池的符號引用替換爲直接引用的過程。
執行引擎
其做用是將class字節碼轉變成機器能識別的碼,而後在jvm中建立方法棧去執行方法。
運行時數據區
運行時數據區包含方法區、堆、虛擬機棧、本地方法棧、程序計數器。以下圖:java
方法區
方法區同堆同樣,是全部線程共享的內存區域,爲了區分堆,又被稱爲非堆。用於存儲已被虛擬機加載的類信息、常量、靜態變量,如static修飾的變量加載類的時候就被加載到方法區中。
堆
簡單的說就是對象的存儲區,它是被全部線程共享的一塊區域堆是java虛擬機管理內存最大的一塊內存區域,由於堆存放的對象是線程共享的,因此多線程的時候也須要同步機制。堆回收算法使用的複製算法效率高沒有碎片利用率低分爲三個區 eden、S0、 S1 按照8:1:1的默認值。
程序計數器
咱們知道對於一個處理器,在一個肯定的時刻都會執行一條線程中的指令,一條線程中有多個指令,爲了線程切換能夠恢復到正確執行位置,每一個線程都需有獨立的一個程序計數器,不一樣線程之間的程序計數器互不影響,獨立存儲。
虛擬機棧
虛擬機棧指的是java方法執行的內存概念模型,每一個方法執行時都會在棧內存裏面建立一個棧幀,棧幀用來存儲局部變量表、操做數棧、動態連接、方法出口等信息。每一個方法從調用到執行完成,都會對應一個棧幀在虛擬機中入棧到出棧的過程。
本地方法棧
本地方法棧與虛擬機棧功能類似,是爲虛擬機使用的Native方法服務。有的虛擬機可能會把這兩個棧合二爲一。
程序計數器
咱們知道對於一個處理器,在一個肯定的時刻都會執行一條線程中的指令,一條線程中有多個指令,爲了線程切換能夠恢復到正確執行位置,每一個線程都需有獨立的一個程序計數器,不一樣線程之間的程序計數器互不影響,獨立存儲。
目前比較經常使用的gc回收是年代回收法,JVM將堆分紅了二個大區新生代、老年代和持久代。新生代和老年代的內存區域是在堆上也是gc回收的主要區域,默認狀況新生代與老年代比例爲1:2,該值能夠經過參數–XX:NewRatio 設定。持久代是在方法區,持久代存放一些通常不須要被回收的對象,持久代通常狀況不會觸發GC。
新生代
新生代又分爲Eden和Survivor區,而Survivor由S0和S1組成。,新生代默認分配是eden:S0:S1爲8:1:1,該值能夠經過參數–XX:SurvivorRatio 來設定。新生代採用的是複製回收算法,當第一次產生對象是在eden區分配空間,當eden區空間滿了時候,會在S0區域分配空間,當S0空間滿了時候會觸發Minor GC,這時候會把eden區和S0區存活對象複製出來放在S1區,而後直接清空eden區和S0區,新生代就是這麼反覆的進行垃圾回收。
老年代
老年代用於存放通過屢次Minor GC以後依然存活的對象,在年代回收法對象有個年齡的概念,在新生代每進行一次Minor GC仍然存活的對象年齡都會加1,當對象年齡達到必定的值就會進入老年代區域, 默認的值是15 ,能夠經過參數-XX:MaxTenuringThreshold 來設定。還有一種狀況是當對象特別大時候不須要達到設定值會直接進入老年代。老年代因爲對象比較穩定因此老年代採用標記整理算法進行Full GC,此算法會減小內存碎片帶來的效率損耗,下面會重點介紹一下本算法。
垃圾收集算法
①.(標記-清除)算法
這是最基礎的算法,標記-清除算法分爲「標記」和「清除」兩個階段:首先標記出全部須要回收的對象,標記完成後統一回收全部被標記的對象。這種算法的缺點是會產生內存碎片並且效率也不高。下圖是此算法的執行過程。
②.(複製)算法
爲了解決(標記-清除)算法的缺陷,複製算法就被提了出來。它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面,這種算法雖然實現簡單,運行高效且不容易產生內存碎片,可是卻對內存空間的使用作出了高昂的代價,由於可以使用的內存縮減到原來的一半。 很顯然,複製算法的效率跟存活對象的數目多少有很大的關係,若是存活對象不少,那麼複製算法的效率將會大大下降。咱們的新生代GC算法採用的是這種算法。複製算法執行過程以下圖:
③.(標記-整理)算法
由於複製算法效率低,清除算法會產生內存碎片,因此又產生了了(標記-整理)算法。該算法標記階段和(標記-清除)同樣,可是在完成標記以後,它不是直接清理可回收對象,而是將存活對象都向一端移動,而後回收被標記的對象,此算法的好處是效率高,同時不會產生內存碎片。標記-整理算法執行過程以下圖:
內存泄漏
在咱們平時寫代碼時候很容易發生gc沒有及時回收的狀況,這時就會發生內存泄漏狀況,下面介紹一個內存泄漏的例子。
Public class test{
Public static Map<int, Object> map = new HashMap<int, Object>();
Public void insert(){
for (int i=1;i<100;
i++){ Object o=new Object()
map.put(i,o);
}
}
}複製代碼
在這個例子中,因爲map是靜態的,因此gc不會回收,當執行insert方法時候,進入for循環,聲明o對象,而後放進map裏面,執行完此方法o對象本來能夠被gc回收,可是因爲map是靜態的因此不會被回收,這樣就會致使內存泄漏,因此咱們在寫代碼時候必定要謹慎使用常量和靜態變量這類型的變量,可能在不經意間形成內存泄漏狀況。
jvm以及gc都是咱們寫代碼和設計程序時常常能涉及到的知識,深刻的學習一下jvm和gc能提升對jvm調優的能力,也可讓本身寫出更優雅的代碼。提升本身的java水平。
Thanks!
做者簡介
程東旭,民生科技有限公司,用戶體驗技術部Firefly移動金融開發平臺Java開發工程師。