JVM是可運行 Java代碼的假想計算機,包括一套字節碼指令集、一組寄存器、一個棧、一個垃圾回收堆和一個存儲方法域。JVM運行在操做系統之上,他與硬件沒有直接交互。java
Java源文件經過編譯器,可以產生相應的.Class文件,也就是字節碼文件,而字節碼文件又經過Java虛擬機中的解釋器,編譯成特定機器上的機器碼。 過程以下:算法
Java源文件->編譯器->字節碼文件->JVM->機器碼
複製代碼
每一種平臺的解釋器是不一樣的,可是實現的虛擬機是相同的,這也就是Java可以跨平臺的緣由了,當一個程序從開始運行,這時候虛擬機就開始實例化了,多個程序啓動就會存在多個虛擬機實例。程序退出或者 關閉,則虛擬機實例消亡,多個虛擬機實例之間數據不能共享。數組
JVM容許一個應用併發執行多個線程。HotspotJVM中的Java線程與原生操做系統線程有直接的映射關係。當線程本地存儲、緩 衝區分配、同步對象、棧、程序計數器等準備好之後,就會建立一個操做系統原生線程。 Java 線程結束,原生線程隨之被回收。操做系統負責調度全部線程,並把它們分配到任何可 用的 CPU 上。當原生線程初始化完畢,就會調用Java線程的run()方法。當線程結束時,會釋放原生線程和 Java 線程的全部資源。數據結構
一塊較小的內存空間,是當前線程所執行的字節碼的行號執行器,每條線程都要有個一獨立的程序計數器,這類內存也稱爲「線程私有」的內存。多線程
正在執行Java方法的話,計數器記錄的是虛擬機字節碼指令的地址(當前指令的地址)。若是仍是Native方法,則爲空。併發
這個內存區域是惟一一個在虛擬機中沒有規定任何OutOfMemoryError狀況的區域。jvm
是描述Java方法執行的內存模型,每一個方法在執行的同時都會建立一個棧幀,用於存儲局部變量表、操做數棧、動態連接、方法出口等信息。每個方法從調用直至完成的過程,就對應着一個棧幀在虛擬機棧中入棧到出棧的過程。spa
棧幀是用來存儲數據和部分結果的數據結構,同時也被用來處理動態連接、方法返回值和異常分派。棧幀隨着方法的調用而建立,隨着方法的結束而銷燬-不管方法是正常完成仍是異常完成(拋出了方法內未捕獲的異常)都算做方法結束。操作系統
本地方法區和java stack做用相似,區別是虛擬機棧爲執行Java方法服務,而本地方法棧則爲native方法服務,若是一個 VM 實現使用 C-linkage 模型來支持 Native 調用, 那麼該棧將會是一個 C 棧,但 HotSpot VM 直接就把本地方法棧和虛擬機棧合二爲一。線程
建立的對象和數組都保存在Java堆內存中,也是垃圾收集器進行垃圾收集的最重要的區域。 因爲如今jvm採用分代收集算法,所以Java堆從GC的角度還能夠細分爲:新生代(Eden區、From Survivor區和To Survivor區)和老年代
永久代用來存儲被JVM加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據, HotSpot VM 把 GC 分代收集擴展至方法區,即便用Java堆的永久代來實現方法區,這樣HotSpot的垃圾收集器就能夠像管理 Java 堆同樣管理這部份內存,而沒必要爲方法區開發專門的內存管理器(永久帶的內存回收的主要目標是針對常量池的回收和類型 的卸載, 所以收益通常很小)。 運行時常量池是方法區的一部分,Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有常量池。用於存放編譯器生成的各類字面量和符號引用,這部份內容將在類加載後存放到方法區的運行時常量池中。Java虛擬機對Class文件對每一部分的格式都有嚴格的規定,每個字節用於存儲哪一種數據結構都必須符合規範上的要求,這樣纔會被虛擬機承認、裝載和執行。
Java堆從GC的角度還能夠細分爲:新生代(Eden區、From survivor區和To survivor區)和老年代
用來存放新生的對象。通常佔據1/3的空間。因爲頻繁建立對象,因此新生代會頻繁觸發MinorGC進行垃圾回收。新生代又分爲 Eden 區、ServivorFrom、ServivorTo 三個區。
Java新對象的出生地(若是新建立的對象佔用內存很大,則直接分配到老 年代)。當 Eden 區內存不夠的時候就會觸發 MinorGC,對新生代區進行 一次垃圾回收。
上一次GC的倖存者,做爲這一次的GC的被掃描者
保留了一次MinorGC過程當中的倖存者
MinorGC採用複製算法 一、Eden、From Survivor複製到 To Survivor,年齡+1 首先把Eden和From Survivor區域中存活的對象複製到To Survivor區域若是有對象的年 齡以及達到了老年的標準,則賦值到老年代區),同時把這些對象的年齡+1,(若是 ServicorTo 不 夠位置了就放到老年區); 二、清空Eden、From Survivor 而後,清空Eden、From Survivor中的對象 三、From Survivor和To Survivor互換 From Survivor和To Survivor互換,原To Survivor成爲下一次GC時的From Survivor區。
主要存放應用程序中生命週期長的內存對象 老年代的對象比較穩定,因此MinorGC不會頻繁執行。在進行MajorGC前通常都先執行了一次MinorGC,使得有新生代的對象晉升到老年代,致使空間不夠用時才觸發。當沒法找到足 夠大的連續空間分配給新建立的較大對象時也會提早觸發一次 MajorGC 進行垃圾回收騰出空間。
MajorGC採用標記清除算法,首先掃描一次全部老年代,標記處存活的對象,而後回收沒有標記的對象。MajorGC會產生內存碎片,爲了減小內存損耗,咱們通常須要進行合併或者標記出來方便下次直接分配。當老年代也滿了裝不下的 時候,就會拋出 OOM(Out of Memory)異常。
指內存的永久保存區域,主要存放 Class 和 Meta(元數據)的信息,Class 在被加載的時候被 放入永久區域,它和和存放實例的區域不一樣,GC 不會在主程序運行期對永久區域進行清理。因此這 也致使了永久代的區域會隨着加載的 Class 的增多而脹滿,最終拋出 OOM 異常。
在 Java8 中,永久代已經被移除,被一個稱爲「元數據區」(元空間)的區域所取代。元空間的本質和永久代相似,元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用 本地內存。所以,默認狀況下,元空間的大小僅受本地內存限制。類的元數據放入native memory, 字符串池和類的靜態變量放入java 堆中,這樣能夠加載多少類的元數據就再也不由 MaxPermSize控制, 而由系統的實際可用空間來控制。
在 Java 中,引用和對象是有關聯的。若是要操做對象則必須用引用進行。所以,很顯然一個簡單 的辦法是經過引用計數來判斷一個對象是否能夠回收。簡單說,即一個對象若是沒有任何與之關 聯的引用,即他們的引用計數都不爲 0,則說明對象不太可能再被用到,那麼這個對象就是可回收 對象。
爲了解決引用計數法的循環引用問題,Java 使用了可達性分析的方法。經過一系列的「GC roots」 對象做爲起點搜索。若是在「GC roots」和一個對象之間沒有可達路徑,則稱該對象是不可達的。 要注意的是,不可達對象不等價於可回收對象,不可達對象變爲可回收對象至少要通過兩次標記 過程。兩次標記後仍然是可回收對象,則將面臨回收。
最基礎的垃圾回收算法,分爲兩階段,標註和清除。標記階段標記出全部須要回收的對象,清除階段回收被標記的對象所佔用的空間。如圖
從圖中咱們能夠發現,該算法最大的問題是內存碎片化嚴重,後續可能發生大對象找不到可利用的空間的問題。
爲了解決 Mark-Sweep 算法內存碎片化的缺陷而被提出的算法。按內存容量將內存劃分爲等大小 的兩塊。每次只使用其中一塊,當這一塊內存滿後將尚存活的對象複製到另外一塊上去,把已使用 的內存清掉,如圖:
標記階段和 Mark-Sweep 算法相同,標記後不是清理對象,而是將存活對象移向內存的一端,而後清除端邊界外的對象,如圖:
分代收集法是目前大部分 JVM 所採用的方法,其核心思想是根據對象存活的不一樣生命週期將內存 劃分爲不一樣的域,通常狀況下將 GC 堆劃分爲老生代(Tenured/Old Generation)和新生代(Young Generation)。老生代的特色是每次垃圾回收時只有少許對象須要被回收,新生代的特色是每次垃 圾回收時都有大量垃圾須要被回收,所以能夠根據不一樣區域選擇不一樣的算法。
目前大部分 JVM 的 GC 對於新生代都採起 Copying 算法,由於新生代中每次垃圾回收都要 回收大部分對象,即要複製的操做比較少,但一般並非按照 1:1 來劃分新生代。通常將新生代 劃分爲一塊較大的 Eden 空間和兩個較小的 Survivor 空間(From Space, To Space),每次使用 Eden 空間和其中的一塊 Survivor 空間,當進行回收時,將該兩塊空間中還存活的對象複製到另 一塊 Survivor 空間中。
老年代由於每次回收少許對象,於是採用標記整理算法
一、Java虛擬機提到過的方法區的永生代,他用來存儲class類,常量,方法描述等。對永久代的回收主要包括廢棄常量和無用的類。
二、對象的內存分配主要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor 目 前存放對象的那一塊),少數狀況會直接分配到老生代。
三、當新生代的 Eden Space 和 From Space 空間不足時就會發生一次 GC,進行 GC 後,Eden Space 和 From Space 區的存活對象會被挪到 To Space,而後將 Eden Space 和 From Space 進行清理。
四、若是 To Space 沒法足夠存儲某個對象,則將這個對象存儲到老生代。
五、在進行 GC 後,使用的即是 Eden Space 和 To Space 了,如此反覆循環。
六、當對象的Survivor區躲過一次GC後,其年齡就會+1。默認狀況下年齡到達15的對象會被移到老生代中。
在 Java 中最多見的就是強引用,把一個對象賦給一個引用變量,這個引用變量就是一個強引 用。當一個對象被強引用變量引用時,它處於可達狀態,它是不可能被垃圾回收機制回收的,即 使該對象之後永遠都不會被用到 JVM 也不會回收。所以強引用是形成 Java 內存泄漏的主要緣由之 一。
軟引用須要用 SoftReference 類來實現,對於只有軟引用的對象來講,當系統內存足夠時它 不會被回收,當系統內存空間不足時它會被回收。軟引用一般用在對內存敏感的程序中。
弱引用須要用 WeakReference 類來實現,它比軟引用的生存期更短,對於只有弱引用的對象 來講,只要垃圾回收機制一運行,無論 JVM 的內存空間是否足夠,總會回收該對象佔用的內存。
虛引用須要 PhantomReference 類來實現,它不能單獨使用,必須和引用隊列聯合使用。虛 引用的主要做用是跟蹤對象被垃圾回收的狀態。
當前主流 VM 垃圾收集都採用」分代收集」(Generational Collection)算法, 這種算法會根據對象存活週期的不一樣將內存劃分爲幾塊, 如JVM 中的 新生代、老年代、永久代,這樣就能夠根據 各年代特色分別採用最適當的 GC 算法
每次垃圾收集都能發現大批對象已死, 只有少許存活. 所以選用複製算法, 只須要付出少許 存活對象的複製成本就能夠完成收集.
由於對象存活率高、沒有額外空間對它進行分配擔保, 就必須採用「標記—清理」或「標 記—整理」算法來進行回收, 沒必要進行內存複製, 且直接騰出空閒內存.
分區算法則將整個堆空間劃分爲連續的不一樣小區間,每一個小區間獨立使用,獨立回收.這樣作的好處是可控制一次回收多少個小區間, 根據目標停頓時間,每次合理地回收若干個小區間(而不是整個堆),從而減小一次 GC 所產生的停頓。
Java堆內存被劃分爲新生代和老年代兩部分,新生代主要使用複製和標記-清除垃圾回收算法;老年代主要使用標記-整理垃圾回收算法,jdk1.6中虛擬機的垃圾收集器以下:
Serial是最基本的垃圾收集器,使用複製算法.serial是一個單線程的收集器,只會使用一個CPU或一條線程去完成垃圾收集工做,而且在進行垃圾收集的同時,必須暫停其餘全部的工做線程,知道垃圾收集結束. serial垃圾收集器雖然在收集垃圾過程當中須要暫停其餘的工做線程,可是它簡單高效,對於限定單個cpu環境來講,沒有線程交互的開銷,能夠得到最高的單線程垃圾收集效率,所以serial垃圾收集器依然是Java虛擬機運行在 Client 模式下默認的新生代垃圾收集器
ParNew垃圾收集器實際上是Serial收集器的多線程版,也是使用複製算法除了使用多線程進行垃 圾收集以外,其他的行爲和 Serial 收集器徹底同樣,ParNew 垃圾收集器在垃圾收集過程當中一樣也 要暫停全部其餘的工做線程。ParNew收集器默認開啓和CPU數目相同的線程數,能夠經過-XX:ParallelGCThreads參數來限制垃圾收集器的線程數.ParNew 雖然是除了多線程外和 Serial 收集器幾乎徹底同樣,可是 ParNew 垃圾收集器是不少 java 虛擬機運行在 Server 模式下新生代的默認垃圾收集器。
Parallel Scavenge收集器是一個新生代垃圾收集器,一樣使用複製算法,也是一個多線程的垃圾收集器,他重點關注的是程序達到一個可控制的吞吐量(吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間), 高吞吐量能夠最高效率地利用 CPU 時間,儘快地完成程序的運算任務,主要適用於在後臺運算而 不須要太多交互的任務。自適應調節策略也是 ParallelScavenge 收集器與 ParNew 收集器的一個 重要區別。
Serial Old 是 Serial 垃圾收集器年老代版本,它一樣是個單線程的收集器,使用標記-整理算法, 這個收集器也主要是運行在 Client 默認的 java 虛擬機默認的年老代垃圾收集器。 在 Server 模式下,主要有兩個用途:
Parallel Old 收集器是 Parallel Scavenge 的年老代版本,使用多線程的標記-整理算法,在 JDK1.6 纔開始提供。 在 JDK1.6 以前,新生代使用 ParallelScavenge 收集器只能搭配年老代的 Serial Old 收集器,只 能保證新生代的吞吐量優先,沒法保證總體的吞吐量,Parallel Old 正是爲了在年老代一樣提供吞 吐量優先的垃圾收集器,若是系統對吞吐量要求比較高,能夠優先考慮新生代 Parallel Scavenge 和年老代 Parallel Old 收集器的搭配策略。 新生代 Parallel Scavenge 和年老代 Parallel Old 收集器搭配運行過程圖:
只是標記一下 GC Roots 能直接關聯的對象,速度很快,仍然須要暫停全部的工做線程。 Concurrent mark sweep(CMS)收集器是一種年老代垃圾收集器,其最主要目標是獲取最短垃圾 回收停頓時間,和其餘年老代使用標記-整理算法不一樣,它使用多線程的標記-清除算法。 最短的垃圾收集停頓時間能夠爲交互比較高的程序提升用戶體驗。 CMS 工做機制相比其餘的垃圾收集器來講更復雜,整個過程分爲如下 4 個階段: 13/04/2018 Page 33 of 283
進行 GC Roots 跟蹤的過程,和用戶線程一塊兒工做,不須要暫停工做線程。
爲了修正在併發標記期間,因用戶程序繼續運行而致使標記產生變更的那一部分對象的標記 記錄,仍然須要暫停全部的工做線程。 清除 GC Roots 不可達對象,和用戶線程一塊兒工做,不須要暫停工做線程。因爲耗時最長的並 發標記和併發清除過程當中,垃圾收集線程能夠和用戶如今一塊兒併發工做,因此整體上來看 CMS 收集器的內存回收和用戶線程是一塊兒併發地執行。 CMS 收集器工做過程:
Garbage first 垃圾收集器是目前垃圾收集器理論發展的最前沿成果,相比與 CMS 收集器,G1 收 集器兩個最突出的改進是: