JVM 主要由 ClassLoader
和 執行引擎
兩子系統組成.html
任何一個Java類的main方法運行都會建立一個JVM實例, 當main函數結束時, JVM實例也就結束了. JVM實例啓動時默認啓動幾個守護線程, 好比: 垃圾回收的線程, 而 main 方法的執行是在一個單獨的非守護線程中執行的.只要母線程結束, 子線程就自動銷燬, 只要非守護main 線程結束JVM實例就銷燬了.java
JVM的工做原理以下:git
類生命週期 github
類: 須要由加載它的類加載器和這個類自己共同保證其在JVM中的惟一性算法
加載編程
經過類的全路徑名獲取類的二進制字節流,將類的靜態內容和對象信息加載進方法區,在堆中建立對象,做爲方法區數據的訪問入口.數組
具體是將類的.class
文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,而後在堆區建立一個java.lang.Class
對象,做爲這個類封裝在方法區內的數據結構的入口.安全
類的加載最終是在堆區內的Class對象,Class對象封裝了類在方法區內的數據結構,而且向開發者提供了訪問方法區內的數據結構的接口.數據結構
怎樣加載一個類:多線程
Class.forName()
方法動態加載ClassLoader.loadClass()
方式動態加載,如ClassLoader.getSystemClassLoader().loadClass("org.luvx.User")
一個類被加載,當且僅當其某個靜態成員(靜態方法等、構造器)被調用時發生,加載一個類時,其內部類不會同時被加載。
驗證
檢查Class文件數據的正確性,是否符合當前虛擬機的要求 ,是否會危害JVM的安全等,是類加載過程當中最複雜耗時的過程.
細分爲如下過程:
準備
正式爲類的靜態內容分配內存並設置變量初始值
- 進行內存分配的僅是靜態變量,不包括對象變量,對象變量在對象實例化時隨着對象分配在堆內存中.
- 設置初始值並非是什麼就是什麼,而是設置對應類型的初始值,如定義
static int num = 12
,此時設置爲0,12是在上圖初始化階段設置,但static final
修飾除外,直接就是12
解析
將常量池中的符號引用替換爲直接引用,主要針對類或接口、字段、類方法、接口方法四類符號引用進行
符號引用不必定要已經加載到內存,而直接引用一定存在於存中 關於符號引用, 查下如下代碼的字節碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 符號引用 public class Test { public static void main() { String s="adc"; System.out.println("s=" + s); } } // 直接�引用 public class Test { public static void main() { System.out.println("s=" + "abc"); } } |
初始化
類加載的最後階段,對靜態內容進行初始化操做
Java類初始化順序:
父類靜態變量->父類靜態代碼塊->子類靜態代碼塊->父類非靜態變量->父類非靜態代碼塊->父類構造函數->子類非靜態變量->子類非靜態代碼塊->子類構造函數
不會加載類的情形:
Class.forName()
加載類時,指定參數initialize
爲false
ClassLoader
的loadClass()
方法加載類Java中的類都是在程序運行期間加載的,雖然會下降性能,但這種動態加載機制增長了靈活性,如面向接口編程中,只有運行時才能知道具體的類,能夠自定義類加載器,動態加載指定的二進制數據建立對象.
JVM規範中並無約束類加載時機,但約束了5種狀況需對類進行初始化操做,其以前的操做天然就須要完成.
java.lang.reflect
包的方法對類進行反射調用main()
方法的類java.lang.invoke.MethodHandle
的解析結果對應的類沒有初始化,則須要初始化本身寫的兩個不一樣的類是被同一個類加載器加載的嗎?爲何
類加載器的做用就是從字節碼建立一個類,並負責加載 Java 應用所需的資源.
只有當一個類要使用的時候,類加載器纔會加載這個類並初始化
rt.jar
java.lang.ClassLoader
實現自定義類加載器.User user = new User()
實質就是User user = Class.forName("org.luvx.User", false, this.class.getClassLoader()).newInstance();
雙親委託模型
工做過程爲:一個類收到類加載的請求,首先不會本身去嘗試加載,而是委派爲父加載器去加載,只有當父類反饋沒法加載時,纔會嘗試本身去加載. 若是全部加載器均加載失敗, 則會拋出ClassNotFoundException
異常.
意義: 能夠保證java的一些重要類如Object在各類類加載器加載下都是同一個類,由於最終都是由啓動類加載器加載,保證的類的惟一性.
存在的問題:模型自己決定的,例如基礎類要掉回用戶代碼 怎麼解決了:線程上下文類加載器
開發者能夠繼承java.lang.ClassLoader
並重寫findClass()
方法便可建立自定義類加載器.
一個類的類型是類自己和加載該類的加載器一塊兒肯定的
NoClassDefFoundError
NoSuchMethodError
ClassCastException:同一個類若是被不一樣的加載器加載,那他們就不是同一個類,也沒法將一個類強轉爲另外一個類,會報類轉換異常,這也是ClassLoader隔離
問題.
java 的對象分配策略 在Eden中, 大對象直接進入老年代, 長期存活的對象進入老年代, 動態年齡分配, 空間分配擔保
區域 | 做用 | 共享性 | 存儲內容 |
---|---|---|---|
堆內存 | 存放對象實例,能夠細分爲新生代和老年代 | √ | new出來的對象(屬性,方法的地址(指向方法區)) |
方法區 | 內有運行時常量池,也有人稱之爲永久代 | √ | 常量,static變量,類信息(屬性,方法) |
運行時常量池 | 方法區的一部分 | √ | 運行時常量池(各類字面量和符號引用) |
程序計數器 | 比較小的內存區域,指示當前線程所執行的字節碼的位置 | × | 正在執行的VM字節碼指令地址(Java方法,native方法時爲空) |
VM棧 | 記錄方法調用 | × | 局部變量表,對象的引用指針 |
本地方法棧 | 執行Native方法時使用 | × | - |
一塊較小的內存空間, 屬於線程私有.
字節碼解釋器工做時就是經過改變這個計數器的值來選取下一條須要執行的字節碼指令, 分支、循環、跳轉、異常處理、線程恢復等基礎功能都須要依賴這個計數器來完成. 若是線程正在執行一個java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;
若是是Native方法,則計數器爲空;多線程時, 存在多個程序計數器.
此內存區域是惟一一個在Java虛擬機規範中沒有規定任何OutOfMemoryError狀況的區域.
線程私有, 生命週期與線程相同
虛擬機棧描述的是Java方法執行的內存模型:每一個方法被執行的時候都會同時建立一個棧幀(Stack Frame)用於存儲局部變量表、操做棧、動態連接、方法出口等信息.
每個方法被調用直至執行完成的過程, 就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程.
局部變量表所需的內存空間在編譯期間完成分配,當進入一個方法時該方法對應的須要在棧幀中分配多大的局部變量空間是徹底肯定的.
對這個區域規定了兩種異常情況: 若是線程請求的棧深度大於虛擬機所容許的深度, 將拋出StackOverflowError
異常; 若是虛擬機棧能夠動態擴展(當前大部分的Java虛擬機均可動態擴展, 只不過Java虛擬機規範中也容許固定長度的虛擬機棧), 當擴展時沒法申請到足夠的內存時會拋出OutOfMemoryError
異常.
棧幀: 一個棧幀隨着一個方法的調用開始而建立,這個方法調用完成而銷燬.棧幀內存放着方法中的局部變量,操做數棧等數據
Java堆是被全部線程共享的一塊內存區域, 在虛擬機啓動時建立.此內存區域的惟一目的就是存放對象實例, 幾乎全部的對象實例都在這裏分配內存.
Java堆是垃圾收集器管理的主要區域, 所以不少時候也被稱作"GC堆" 多采用分代收集策略,因此細分爲新生代和老年代,再細分可分爲Eden空間,From Survivor空間,To Survivor空間,在GC的複製算法中起着重要做用,HotSpot VM默認的Eden和Survivor大小比例爲8:1
堆內存能夠是物理上不連續的內存空間, 邏輯上連續便可,
在堆中沒有內存完成實例分配, 而且堆也沒法再擴展時, 將會拋出OutOfMemoryError
異常
與Java堆同樣, 是各個線程共享的內存區域, 它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據,有時也被稱爲永久代(PermGen)
Java虛擬機規範對這個區域的限制很是寬鬆, 除了和Java堆同樣不須要連續的內存和能夠選擇固定大小或者可擴展外, 還能夠選擇不實現垃圾收集
這個區域的內存回收目標主要是針對常量池的回收和對類型的卸載
當方法區沒法知足內存分配需求時, 將拋出OutOfMemoryError
異常
是方法區的一部分,存放編譯期生成的各類字面量和符號引用,當JVM運行的時候會將這些常量池的信息加載進方法區.
當方法區沒法知足內存分配需求時,拋出OutOfMemoryError
虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務, 而本地方法棧則是爲虛擬機使用到的Native方法服務
異常拋出類型和JVM棧相同
不是虛擬機運行時數據區的一部分,也不是Java虛擬機規範中定義的內存區域,可是這部份內存也被頻繁的使用.
也可能致使OutOfMemoryError
異常出現
Exception in thread "main": java.lang.OutOfMemoryError: Java heap space
緣由: 對象不能被分配到堆內存中
Exception in thread "main": java.lang.OutOfMemoryError: PermGen space
緣由: 類或者方法不能被加載到永久代.它可能出如今一個程序加載不少類的時候, 好比引用了不少第三方的庫;
Exception in thread "main": java.lang.OutOfMemoryError: Requested array size exceeds VM limit
緣由: 建立的數組大於堆內存的空間
Exception in thread "main": java.lang.OutOfMemoryError: request <size> bytes for <reason>. Out of swap space?
緣由: 分配本地分配失敗.JNI、本地庫或者Java虛擬機都會從本地堆中分配內存空間.
Exception in thread "main": java.lang.OutOfMemoryError: <reason> <stack trace>(Native method)
緣由: 一樣是本地方法內存分配失敗, 只不過是JNI或者本地方法或者Java虛擬機發現
java.lang.OutOfMemoryError: unable to create new native thread
緣由: 建立了太多的線程,而能建立的線程數是有限制的,致使了異常的發生
工具 | 做用 |
---|---|
jps | 進程狀態工具,查看正在運行的JVM進程 |
jstat | 統計信息監視工具,實時顯示JVM進程中類裝載、內存、垃圾收集、JIT編譯等數據 |
jinfo | 配置信息工具,查詢當前運行着的JVM屬性和參數的值 |
jmap | 內存映射工具,生成VM的內存轉儲快照 |
jhat | 堆轉儲快照分析工具,分析使用jmap生成的dump文件 |
jstack | 堆棧跟蹤工具,生成當前JVM的全部線程快照,線程快照是虛擬機每一條線程正在執行的方法,目的是定位線程出現長時間停頓的緣由. |
jconsole | jvm監視管理控制檯,圖像化顯示堆棧等使用狀況,能夠手動進行GC,很是實用 |
jcmd |
JVM參數:
工具 | 做用 |
---|---|
-Xmx | 最大堆內存 |
-Xms | 最小堆內存, 一般設置成跟最大堆內存同樣,減小GC |
-Xmn | 設置年輕代大小,官方推薦設置爲堆的3/8 |
-Xss | 指定線程的最大棧空間, 此參數決定了java函數調用的深度, 值越大調用深度越深, 若值過小則容易出棧溢出錯誤(StackOverflowError) |
-XX:PermSize | 指定方法區(永久區)的初始值,默認是物理內存的1/64, 在Java8永久區移除, 代之的是元數據區, 由-XX:MetaspaceSize指定 |
-XX:MaxPermSize | 指定方法區的最大值, 默認是物理內存的1/4, 在java8中由-XX:MaxMetaspaceSize指定元數據區的大小 |
-XX:NewRatio=n | 年老代與年輕代的比值,-XX:NewRatio=2, 表示年老代與年輕代的比值爲2:1 |
-XX:SurvivorRatio=n | Eden區與一個Survivor區的大小比值,-XX:SurvivorRatio=8表示Eden區與兩個Survivor區的大小比值是8:1:1,由於Survivor區有兩個(from, to) |
GC的工做內容就是就是回收內存,包括哪些內存須要回收,何時回收,怎麼回收等3件工做.
堆內存也被稱爲GC堆,是由於GC的主要進行場所就是堆內存,方法區是堆內存的一部分,一樣也能夠GC的對象,
但Java虛擬機規範不要求虛擬機在方法區實現GC,並且在方法區進行GC的"性價比"通常都比較低,這和方法區被稱爲永久代有着相同的緣由.
在GC進行前首先要肯定的就是對象是否還活着
(是否還在直接或間接的被使用中)
判斷對象的使用常有如下2種策略:
引用計數算法
存儲對特定對象的全部引用數,也就是說,當應用程序建立引用以及引用超出範圍時,JVM必須適當增減引用數.當某對象的引用數爲0時,即可以進行垃圾回收. 優勢: 實現簡單、效率高 缺點: 很難解決對象之間相互引用問題
可達性分析算法
經過一系列的稱爲"GC Roots"的對象做爲起始點,從這些節點開始向下搜索,搜索所走的路徑稱爲引用鏈,當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的.
可做爲GC Roots的對象
包括:
標記-清除算法
Mark-Sweep,適用於老年代,最基礎的算法,後續的算法都是基於這種思想改進而來,
標記或清除過程的效率都不高,產生大量不連續的內存碎片,在分配大對象時候因沒法找到符合的連續空間而再次進行GC
標記-整理-清除算法
在標記-清除算法的標記的基礎上,將不被回收的對象向同一端移動,而後清理到邊界外的內存,解決了內存碎片
複製算法
爲解決標記清除算法效率和形成的不連續碎片問題而生,適用於對象存活率低的新生代
將內存分爲一塊較大的Eden空間和兩塊較小的Survivor,每次使用Eden和其中一塊Survivor,回收時,將Eden和Survivor中存活的對象一次性地拷貝到另外一塊Survivor空間,最後清理掉Eden和Survivor空間
分代回收策略
不是一種具體的GC算法,是一種不一樣代採起不一樣的回收算法的策略.
新生代的對象生命週期短,只有少許存活,那就選用複製算法,只須要付出少許存活對象的複製成本就能夠完成收集. 而老年代中由於對象存活率高,沒有額外的空間進行分配擔保,就必須使用「標記-清理」或者「標記-整理」算法來進行回收.
回收方法區(永久代)
此區域的回收主要有兩項內容:廢棄常量和無用的類
垃圾回收器一般是做爲一個單獨的低級別的線程運行, 不可預知的狀況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清除和回收,開發者不能實時的調用垃圾回收器對某個對象或全部對象進行垃圾回收. 常見垃圾回收器有:
應用標記-清除算法,具體過程有初始標記-併發標記-從新標記-併發清理-併發重置
併發收集、低停頓 ;但也有對CPU資源很是敏感、沒法處理浮動垃圾,以及算法自己所具備的會產生內存碎片的缺點
Garbage-First(G1,垃圾優先)收集器是服務類型的收集器,目標是多處理器機器、大內存機器.
應用標記-整理算法,相對於CMS能很是精確地控制停頓,高度符合垃圾收集暫停時間的目標,同時實現高吞吐量.能夠實如今基本不犧牲吞吐量的狀況下完成低停頓的回收
內存劃分方式: 它是將堆內存被劃分爲多個大小相等的 heap 區,每一個heap區都是邏輯上連續的一段內存(virtual memory). 並跟蹤這些區域裏面的垃圾堆積程度,在後臺維護一個優先列表,每次根據容許的收集時間, 優先回收垃圾最多的區域(這也是Garbage First名稱的由來). 總而言之,區域劃分和有優先級的區域回收,保證了G1收集器在有限的時間內能夠得到最高的收集效率.
對象主要分配在新生代的Eden區,少數狀況下會直接分配在老年代中.
分配策略:
對象年齡斷定
JVM給每一個對象都定義一個age計數器,若對象在Eden出生並通過第一次Minor GC後仍存在並被Survivor容納,對象age爲1,以後在Survivor中每通過一次Minor GC,age加1.當age達到必定數值(默認15)就會成爲老年代,默認值能夠經過-XX:MaxTenuringThreshold
修改.
實際上,JVM並非總要求對象的年齡必需達到MaxTenuringThreshold
才能晉升老年代,若是在Survivor空間中相同年齡全部對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象就能夠直接進入老年代.
參數 | 說明 |
---|---|
-XX:+UseSerialGC | 在新生代和老年代使用串行收集器 |
-XX:+UseParallelGC | 新生代使用並行回收收集器 |
-XX:+UseParallelOldGC | 老年代使用並行回收收集器 |
-XX:+UseParNewGC | 在新生代使用並行收集器 |
-XX:+UseConcMarkSweepGC | 新生代使用並行收集器,老年代使用CMS+串行收集器 |
-XX:+UseCMSCompactAtFullCollection | 設置CMS收集器在完成垃圾收集後是否要進行一次內存碎片的整理 |
-XX:UseCtpancyOnly | 表示只在到達閥值的時候,才進行CMS回收 |
-XX:SurvivorRatio | 設置eden區大小和survivior區大小的比例 |
-XX:NewRatio | 新生代和老年代的比 |
-XX:ParallelGCThreads | 設置用於垃圾回收的線程數 |
-XX:ParallelCMSThreads | 設定CMS的線程數量 |
-XX:CMSInitiatingOccupancyFraction | 設置CMS收集器在老年代空間被使用多少後觸發 |
-XX:CMSFullGCsBeforeCompaction | 設定進行多少次CMS垃圾回收後,進行一次內存壓縮 |
-XX:+CMSClassUnloadingEnabled | 容許對類元數據進行回收 |
-XX:CMSInitiatingPermOccupancyFraction | 當永久區佔用率達到這一百分比時,啓動CMS回收 |
-XX:+PrintGCDetails | 開啓後,GC時打印內存回收日誌,並在線程退出時輸出內存分配狀況 |
Minor GC與Full GC分別在何時發生?何時觸發Full GC;
類型 | GC對象 | 發生時機 |
---|---|---|
Minor GC | 回收年輕代, 包括Eden 和 Survivor 區域 | 沒法爲一個新的對象分配空間時 |
Major GC | 永久代 | |
Full GC | 整個堆空間 |
GC收集器有哪些?CMS收集器與G1收集器的特色。
Java中的大對象如何進行存儲;
爲何新生代內存須要有兩個Survivor區?
G1停頓嗎,CMS回收步驟,CMS爲何會停頓,停頓時間;
每一個算法的優缺點啊, 怎麼簡單的解決啊
增長堆的大小, 增長後臺線程, 提早開始併發週期等
有沒有了解G1收集器這些, G1的流程, 相比CMS有哪些優點.
Minor GC發生的頻繁的緣由?�GC的時間長的緣由是什麼 對象過小, 對象太大
Full GC次數太多了,如何優化;
文章轉載於:https://www.javazhiyin.com/24771.html