【總結】jvm

一.jvm體系結構

1.jvm總體結構

clipboard1.png
jvm整體上是由類裝載子系統(ClassLoader)、運行時數據區、執行引擎三個部分組成。
(jvm本質上就是一個java進程)java

2.jvm生命週期

(1)jvm啓動:經過一個引導類加載器建立一個初始類來完成,這個類由虛擬機具體實現指定
(2)jvm運行:執行java程序
(3)jvm退出:①程序執行結束或遇到異常②某線程調用System或Runtime類的exit方法或Runtime的halt方法c++

3.jvm發展歷程

(1)sun classic VM:世界上第一款商用虛擬機(jdk1.4時被徹底淘汰)只有解釋器,hotspot內置此虛擬機
(2)exact VM:JDK1.2引入,能夠知道內存中某個位置的數據具體是什麼類型(eg:不能判斷某個數據是數值仍是地址,要經過handler句柄去找,很麻煩)
(3)hotspot VM:jdk1.3稱爲默認虛擬機。具有熱點代碼探測技術(引入方法區的概念)
(4)Jrockit:專一於服務器端應用。不關注啓動速度,所以不包含解析器實現,全部代碼都靠即時編譯器編譯後執行
(5)J9:ibm公司,普遍應用與ibm的各類java產品算法

二.類加載子系統

clipboard2.png

1.做用

加載class文件,加載的類信息存放於方法區(除類信息外方法區還有運行時常量池,字符串字面量和數字常量)api

2.加載過程

1.加載:經過類的全限定名獲取類的二進制字節流(從磁盤或網路加載類到內存),在方法區生成一個表明這個類的class對象
2.連接:
(1)驗證:確保class文件包含的信息符合當前虛擬機的需求
(2)準備:爲類變量分配內存(方法區),並設置默認初始值,即0(注意:不包含final修飾的static,由於在編譯階段就會初始化)
(3)解析:將常量池內符號引用轉化爲直接引用(加載類中用到的其它類如System)
3.初始化
靜態初始化——父類初始化——子類初始化
父類靜態成員和static塊-子類靜態成員和static塊
父類普通成員和非static塊-父類構造函數
子類普通成員和非static塊-子類構造函數數組

3.類加載器分類

(1)啓動類加載器:使用c/c++實現,加載java的核心庫(jre的lib下rt.jat)
(2)擴展類加載器:java編寫,父加載器爲啓動類加載器(從jre/lib/ext下加載類庫)
(3)應用類加載器:程序中默認的類加載器(加載classpath下的類庫)安全

4.雙親委派機制

1.機制
(1)一個類加載器收到類加載請求,不會本身加載,而是交給父類的加載器去加載
(2)父類加載器還存在父加載器,進一步向上委託,直到達到頂層的啓動類加載器
(3)父類加載器能夠完成類加載,就成功返回。若是不能子類加載器才嘗試加載
2.好處
(1)避免類重複加載,父classloader加載後不必子classloader再加載
(2)安全保證,java核心api定義的類型不會被隨意替換服務器

三.運行時數據區

1.程序計數器

1.程序計數器做用:指向下一條指令的地址 線程私有(惟一一個再jvm中沒有內存溢出的區域)
2.爲何須要?:cpu須要不停的切換各個線程,切換回來後須要知道從哪開始繼續執行多線程

2.虛擬機棧

1.概述
每一個線程建立時都會建立一個虛擬機棧,內部保存着一個個棧幀,對應着方法的執行
2.設置棧內存大小
-Xss:設置線程最大的棧空間
3.棧幀
(1)局部變量表:主要用於存儲方法參數和方法體內的局部變量(基本單位是slot(變量槽)32位之內佔一個槽,64位佔兩個槽)
變量的分類:併發

成員變量:又分爲類變量和實例變量(根據是否static修飾)  
局部變量:方法內的變量

(2)操做數棧:在方法執行過程當中,根據字節碼指令往棧中寫入或提取數據。即入棧/出棧
(3)動態連接:將符號引用轉化爲直接引用
每一個棧幀內部包含一個指向運行時常量池中該棧幀所屬方法的引用(能夠理解爲棧中保存的方法的地址,真正的方法結構是在方法區的運行時常量池中)
(4)方法返回地址:調用程序計數器的值做爲返回地址。即調用嚇一條指令的地址app

3.本地方法棧

做用:用於管理本地方法(native)的調用

4.堆

1.概述

全部的對象和數組都應當在運行時分配在堆上 。

2.堆的內存細分

java8前,新生區(Eden和Survivor)+老年區+永久區
java8後,新生區(Eden和Survivor)+老年區+元空間

3.堆空間大小設置

(1)
-Xms:表示堆區的起始內存
-Xmx:表示堆區的最大內存(超出OOM)
(一般兩個參數值相等,避免了gc後頻繁的調整堆內存大小)

(2)默認初始大小:電腦內存/64
,最大內存大小 電腦內存/4

(3)查看設置的方式
方式一:
jps (查看java進程)
jstat -gc 進程id (顯示gc相關的堆信息)
方式二:
-XX:+PrintGCDetails

4.年輕代與老年代

(1)參數配置
①默認-XX:NewRatio 新生代佔1,老年代佔2
②默認-XX:SurvivorRatio=8 Eden和兩個survivor-1:8:8

5. 對象分配通常過程

1.new的對象先放到Eden,此區有大小限制
2.當Eden滿時,會進行young gc(stop the world),將Eden死亡的對象銷燬,加載新的對象進eden
3.將Eden剩餘的對象放到survivor0
4.再次gc時,會將eden和survivor0存活的對象放到survivor1
5.當對象的年齡超過必定次數(默認15)進入老年區
(-XX:MaxTenuringThreshold=15)

6.對象分配的特殊狀況

1.對象過大,Eden區放不下,直接放到老年代 (老年代也放不下進行old gc)
2.從eden放survivor的對象放不下,直接放老年代

7.yong gc ,old gc, full gc?

1.yong gc:只是新生代的垃圾收集(eden,s0,s1)(stw)
2.old gc:針對老年代的收集(stw)
(stw緣由:避免垃圾回收的時候用戶線程再產生垃圾)
3.full gc:針對整個堆和方法區的垃圾收集
full gc出發機制:①調用System.gc() ②老年代空間不足 ③方法區空間不足

5.方法區

1.概述

和堆同樣,線程共享。存儲 類信息,常量,靜態變量,編譯後的代碼。實現方式是永久代(1.8以前)和元空間(1.8以後)(非堆)

2.永久代和元空間的區別?

1.永久代使用jvm內存,元空間使用本地內存
2.運行時常量池從永久代放入堆中

3.參數設置

1.8以前:
-XX:permsize 設置永久代初始空間
-xx:maxpermsize 永久代最大空間
1.8以後:
-xx:metaspacesize 默認元空間大小
-xx:maxmetaspacesize 最大元空間大小

4.常量池和運行時常量池

字節碼文件,內部包含了常量池
方法區,內部包含了運行時常量池

1.常量池
一個java源文件中的類,接口編譯產生字節碼文件。代碼中可能用到system,print等結構。若是把他們全部的信息都存在這個class文件,那文件就會很是大。因此把他們標識存到常量池。因此它能夠看做一個表,虛擬機指令根據這張表找到曜執行的類,方法等。

2.運行時常量池
類加載後會將常量池中的內容加載到方法區的運行時常量池中

5.jdk6 7 8 方法區的演進?

jdk1.6:有永久代,靜態變量存放在永久代
jdk1.7:有永久代,字符串常量池,靜態變量保存在堆
jdk1.8:永久代變元空間。類信息,常量保存在元空間。字符串常量池,靜態變量仍在堆

6.jdk1.7後字符串常量池爲何放到堆中?

永久代回收效率很低,在full gc的時候纔會觸發。而full gc是老年代空間不足,永久代不足時纔會觸發。這就致使字符串常量池回收效率不高。開發過程咱們會建立大量的字符串。因此放到堆裏能及時回收

6.對象的實例化

1.對象建立的方式

1.new
2.Class的newInstance()
3.Constructor的newInstance方法
4.clone() 不調用任何構造器,當前類實現cloneable接口,實現clone方法
5.使用反序列化

2.建立對象的步驟

1.判斷對象對應的類是否加載,鏈接,初始化
2.爲對象分配內存
(1)若是內存規整,虛擬機採用指針碰撞來分配內存。全部用過的內存放在一邊,空閒的內存放在另外一邊。中間放着一個指針做爲分界點指示器,分配內存就僅僅把指針空閒那一邊挪動一段與對象大小相等的距離
(2)若是內存不規整,虛擬機採用空閒列表法來分配內存。列表上記錄哪些塊是可用的,在分配的時候從列表找到一塊足夠大的空間分配給列表實例
3.處理併發安全問題。採用cas失敗重試,區域加鎖保證更新的原子性
4.初始化屬性值
5.設置對象的對象頭
6.執行init進行初始化(顯示初始化)

7.string

1.string的不可變性

字符串建立後就不可改變,即便對它進行拼接等操做也是在新的字符串的基礎上作的

2.string建立的位置

經過字面量給字符串賦值,此時字符串值聲明在字符串常量池。(字符串常量池不會存儲相同的字符串——底層使用map)
(1)String str1= 「abc」; 在編譯期,JVM會去常量池來查找是否存在「abc」,若是不存在,就在常量池中開闢一個空間來存儲「abc」;若是存在,就不用新開闢空間。而後在棧內存中開闢一個名字爲str1的空間,來存儲「abc」在常量池中的地址值。

(2)String str2 = new String("abc") ;在編譯階段JVM先去常量池中查找是否存在「abc」,若是過不存在,則在常量池中開闢一個空間存儲「abc」。在運行時期,經過String類的構造器在堆內存中new了一個空間,而後將String池中的「abc」複製一份存放到該堆空間中,在棧中開闢名字爲str2的空間,存放堆中new出來的這個String對象的地址值。

3.string拼接操做

1.常量與常量的拼接結果在常量池,原理是編譯期優化

String s1 = "a" + "b" + "c";
String s2 = "abc"
s1 == s2 //true

2.常量池中不會存在相同內容的常量

3.只要有一個是變量,結果就在堆中(至關於new String)。變量拼接的原理是stringBuilder

String s1 = "a";
String s2 ="b";

String s3 = "a" + s2;
String s4 = s1 + s2;
String s5 = "a" + "b"

s3 == s4 //false
s4 == s5 //false

String s6 = s3.intern()
s6 == s5  //true

4.拼接結果調用intern()方法,則主動將常量池中尚未的對象放入池中。並返回此對象地址(常量池中的地址)

4.拼接操做與append效率對比?

(1)
String s = "";
Random r = new Random();
for (int i = 0;i < 10;i++) {
      s = s + r.nextInt(1000) + " ";          
}
(2) 
String s = "";
StringBuilder sb = new StringBuilder();
for(int i = 0; i < 10; i++){
     sb.append(" ");
}

變量的字符串拼接,底層調用的是stringBuilder的append方法。在for循環中這樣拼接的話每次都建立一個stringBuilder對象

5.new string建立對象的個數?

1.new string("ab");會建立幾個對象?
兩個,堆空間+字符串常量池

2.new String("a") + new String("b")建立幾個對象?

(1)StringBuilder
(2)new string("a")
(3)常量池中的"a"
(4)new string("b")
(5)常量池中的"b"

四.執行引擎

1.執行引擎概述

將字節碼指令解釋/編譯(注意區分和前面java的編譯)爲對應平臺上的本地機器指令

2.解釋器和編譯器

1.解釋器:對字節碼採用逐行解釋的方式執行,將字節碼指令解釋爲本地本地機器指令
2.編譯器:將Java編譯爲class代碼

五.對象存活判斷(標記階段)

1.引用計數算法

1.概述

每一個對象關聯一個引用計數器屬性,任何一個對象引用了A,引用計數器的值加1.當引用失效時,引用計數器就減1.當引用計數器的值爲0時,表示對象再也不被使用,可進行回收

2.缺點

1.須要單獨的字段存儲計數器,這樣增長了存儲空間的開銷
2.每次賦值都要更新計數器值,增長了時間開銷
3.存在循環引用的問題(因此jvm不用):好比一個引用p指向a,a依賴於b,b依賴於c,c依賴於a.當p置空時。a,b,c引用計數器的值仍爲1.不會被回收

2.可達性分析法

1.概念

設立若干根對象,當任何一個根對象到某一個對象均不可達時,認爲這個對象能夠被回收

2.哪些能夠做爲gc roots?

1.虛擬機棧(棧幀中的本地變量表)中引用的對象;
2.本地方法棧中JNI(即通常說的Native方法)中引用的對象
3.方法區中的類靜態屬性引用的對象;
4.方法區中常量引用的對象;
(GC Root Object能夠從Java堆的外部訪問,也就是不受GC的自動回收管制。能夠理解爲有免死金牌的Java對象)

六.垃圾清除算法(清除階段)

1.標記-清除算法

1.概述

標記-清除算法:經過根節點,標記全部根節點開始的可達對象,清除未被標記對象(會產生內存碎片)

2.缺點

1.stop the world,gc的時候中止整個應用程序
2.產生碎片,須要維護一個空閒列表(記錄垃圾所在的位置,新對象到時能夠直接覆蓋該區域)
3.效率不高(進行了兩次O(N)的遍歷)

2.複製算法(新生代默認)

1.概述

將內存分爲一塊較大的Eden和兩塊較小的survivor,每次使用Eden和其中一塊survivor。Gc時將標記的對象複製到另外一塊survivor

1.優勢

1.沒有標記和清除的過程,運行高效
2.複製過去之後保證空間的連續性,不會有碎片產生

2.缺點

浪費內存空間,始終要有一個空閒的survivor

3.標記-整理算法

1.概述

將標記的對象移動到內存的一端,清除邊界外的全部空間(解決了碎片問題)

4.三種算法的對比

速度:複製>標記清除>標記整理

5.分代收集算法

目前幾乎全部的gc採用分代收集算法進行垃圾回收
年輕代:複製算法
老年代:標記清除與標記整理混合

七.垃圾收集器

新生代收集器:Serial、ParNew、Parallel;

老年代收集器:Serial Old、Parallel Old、CMS;

整堆收集器:G1;

1.serial

採用一條收集線程完成收集工做。stop the world(新生代複製 老年代標記整理)
-xx:+UseSerialGC

2.parnew

serial收集器的多線程版本
-xx:+UseparnewGC

3.parallel(高吞吐)

相似parnew。也採用複製算法。不過他的目的是高吞吐量(吞吐量 = 用戶代碼運行時間/(垃圾收集時間+用戶代碼運行時間))。經過-XX:MaxGCPauseMillis設置gc最大暫停時間提升吞吐量(但可能致使垃圾收集發生的頻繁一些)
對於老年代,提供了parallel old收集器。目的也是高吞吐量

4.cms(低延遲)

1.概述
他關注的是儘量縮短stw的時間(採用標記清除算法)
能夠實現用戶線程與垃圾收集線程同時執行(併發標記和清除階段)
-xx:+UseConcMarkSweepGc
2.cms工做原理
(1)初始標記:標出gc root能直接關聯到的對象(stw,但很是短)
(2)併發標記:從gc-root的直接關聯對象遍歷整個對象圖的過程(耗時長但不stw)
(3)從新標記:標記併發標記期間用戶線程產生的新的而未被標記的對象(stw)
(4)併發清除:清除未被標記的對象

3.cms爲何不使用標記-整理算法?
由於採用併發,併發清除時垃圾收集線程和用戶線程都在執行,使用標記整理會改變對象的內存地址,用戶線程沒法正常使用對象

4.G1

garbage first。對垃圾進行優先級劃分。 它把堆內存劃分爲不一樣的region。來代標eden,s0,s1,老年代等。跟蹤各個region的垃圾價值(垃圾的數量和垃圾收集的時間)。維護一個優先級列表,優先收集價值最大的region。避免了堆中全區域的垃圾收集

相關文章
相關標籤/搜索