虛擬機將所管理的內存分爲如下幾個部分:html
在Java8中,永久代已經被移除,被一個稱爲「元數據區」(Metaspace)的區域所取代。元空間的本質和永久代相似,元空間與永久代之間最大的區別在於:元空間並不在虛擬機中,而是使用本地內存。java
在 Java 中,堆被劃分紅兩個不一樣的區域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被劃分爲三個區域:Eden、From Survivor、To Survivor。 程序員
(1)大多狀況下,對象在新生代Eden區中分配,當Eden區沒有足夠空間進行分配時,虛擬機將發起一次Minor GC;(2)大對象(須要大量連續內存空間的對象)直接進入老年代;(3)長期存活對象將進入老年代。算法
Minor GC(新生代GC):在新生代的垃圾收集動做,比較頻繁,回收速度較快,採用複製算法。數組
Full GC(老年代GC,Major GC):發生在老年代的GC,速度較慢,採用標記-清除算法等。緩存
回收過程:多線程
當對象在 Eden ( 包括一個 Survivor 區域,這裏假設是 from 區域 ) 出生後,在通過一次 Minor GC 後,若是對象還存活,而且可以被另一塊 Survivor 區域所容納(上面已經假設爲 from 區域,這裏應爲 to 區域,即 to 區域有足夠的內存空間來存儲 Eden 和 from 區域中存活的對象 ),則使用複製算法將這些仍然還存活的對象複製到另一塊 Survivor 區域 ( 即 to 區域 ) 中,而後清理所使用過的 Eden 以及 Survivor 區域 ( 即 from 區域 ),而且將這些對象的年齡設置爲1,之後對象在 Survivor 區每熬過一次 Minor GC,就將對象的年齡 + 1,當對象的年齡達到某個值時 ( 默認是 15 歲,能夠經過參數 -XX:MaxTenuringThreshold 來設定 ),這些對象就會成爲老年代。併發
注:有的資料還有持久代的概念,存儲類信息、常量、靜態變量、類方法。app
自動內存管理解決的主要問題:給對象分配內存以及回收分配給對象的內存。內存分配規則並不是徹底固定,取決於當前使用的垃圾回收器組合還有虛擬機內存參數設定。如下是廣泛的內存分配規則:分佈式
對象的建立
(1)檢查常量池是否能夠定位到一個類的符號引用,檢查是否被加載、解析和初始化過
(2)加載檢查經過後,爲新生對象分配內存(內存分配方式:指針碰撞、空閒列表)
(3)內存分配完成後,初始化零值
(4)對對象進行必要的設置,如對象是哪一個類的實例、元數據信息、對象的哈希碼、
GC分代信息,這些信息存放在對象頭中。
(5)執行<init>方法,把對象按照程序員的意願進行初始化。
對象的內存佈局
三塊區域:對象頭(對象自身運行時數據;類型指針->對象指向它的類元數據的指針,用於肯定該對象屬於哪一個類的實例)、實例數據(對象真正存儲的有效信息)、對齊填充(起到佔位符的做用)。
對象的訪問定位
經過棧上的reference數據來操做堆上的具體對象。主流的訪問方式有:使用句柄、直接指針。
(1)使用句柄:劃份內存做爲句柄池,reference中存儲對象的句柄地址,最大的好處是存儲的句柄地址是穩定的,在對象移動時reference自己不須要修改。
(2)使用直接指針:reference中存儲的直接就是對象對象地址,速度更快,節省了一次指針定位的時間開銷。HotSpot採用。
引用計數法:經過引用計數器實現,任什麼時候刻計數器爲0的對象就是不可能再被使用的。實現簡單,斷定效率很高,但很難解決對象之間的互相循環引用問題。
可達性分析算法:經過一系列的稱爲「GC ROOTS」的對象做爲起始點,從這些節點開始向下搜索,搜索所走過的路徑稱爲引用鏈,當一個對象到GC ROOTS沒有任何引用鏈相連時(圖論的觀點,就是從GC ROOTS到這個對象不可達),則證實對象是不可用的。
標記-清除算法:分爲兩個階段,首先標記出全部須要回收的對象,在標記完成後統一回收全部被標記的對象。存在的兩個問題:效率問題;產生大量不連續的內存碎片。
複製算法:每次只使用其中一塊,當一塊內存用完了,就將還存活的對象複製到另外一塊上面,而後再把已使用過的內存空間一次清理掉。在存活對象很少的狀況下,性能高,能解決內存碎片問題。經常使用於回收新生代。萬一存活對象數量比較多,那麼To域的內存可能不夠存放,這個時候會藉助老年代的空間,而老年代因爲沒有其額外內存空間進行分配擔保,通常不採用複製算法。
標記-整理算法:標記過程與標記-清除算法同樣,但後續步驟不是直接對可回收對象進行整清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。
分代收集算法:根據各個年代的特色採用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少許存活,那就選用複製算法,只須要付出少許存活對象的複製成本就能夠完成收集。而老年代中由於對象存活率較高、沒有額外空間對它進行分配擔保,就必須使用後面兩種算法來進行回收。
Serial垃圾收集器(單線程、複製算法):Serial 是一個單線程的收集器,它不但只會使用一個CPU或一條線程去完成垃圾收集工 做,而且在進行垃圾收集的同時,必須暫停其餘全部的工做線程,直到垃圾收集結束。是java 虛擬機運行在Client模式下默認的新生代垃圾收集器。
ParNew垃圾收集器(Serial+多線程),Serial收集器的多線程版本,使用複製算法,是不少java虛擬機運行在Server模式下新生代的默認垃圾收集器。
Parallel Scavenge 收集器(多線程複製算法、高效)
SerialOld收集器(單線程標記整理算法)
ParallelOld收集器(多線程標記整理算法)
CMS收集器(多線程標記清除算法):一種以獲取最短回收停頓時間爲目標的收集器,在重視服務的響應速度、注重用戶體驗的場景下很是適用。基於「標記-清除」算法,特色是:併發收集、低停頓。具體分爲四個階段:初始標記->併發標記->從新標記->併發清除。
初始標記和從新標記兩個步驟須要STW,sun官方文檔稱做併發低停頓收集器,主要有如下侷限性:(1)對CPU資源很是敏感,在併發階段,雖然不會致使用戶線程停頓,可是會佔用一部分線程或者說CPU資源致使應用程序變慢,總吞吐量下降,默認啓動的回收線程數是(CPU數量+3)/4。(2)沒法處理浮動垃圾,運行期間預留的內存沒法知足程序須要,出現Concurrent Mode Fail, 可能致使另外一次Full GC的發發生。(3)內存碎片,能夠犧牲停頓時間開啓內存碎片的合併整理過程。
G1收集器:面向服務端應用的垃圾收集器,可能代替CMS收集器,特色:並行與併發,分代收集(能夠不須要其餘收集器配合就能獨立管理整個堆,採用不一樣的方式分代收集),空間整合(總體上是「標記-整理」算法實現,局部上是「複製」算法實現,不會產生內存空間碎片),能夠預測的停頓時間(創建可預測的停頓時間模型)。具體能夠分爲四個階段:初始標記->併發標記->最終標記->篩選回收。
引入分區的思路,弱化了分代的概念,使用G1垃圾收集器時,Java堆的內存佈局與其餘收集器有很大的差異,它將整個堆劃分對多個大小相等的獨立區域(region),新生代、老年代的概念依然保留,再也不是物理隔離而是一部分region(不須要連續)的集合,回收時則以分區爲單位進行回收。
G1的設計原則是"首先收集儘量多的垃圾"。所以,G1並不會等內存耗盡(串行、並行)或者快耗盡(CMS)的時候開始垃圾收集,而是使用啓發式算法,跟蹤各個region裏面的垃圾堆積的價值大小(回收所得到的空間大小以及回收所需時間的經驗值),在後臺維護一個優先列表,優先回收價值最大的region(這是garbage first名字的由來)。同時G1能夠根據用戶設置的暫停時間目標自動調全年輕代和總堆大小。
G1的收集都是STW的,但年輕代和老年代的收集界限比較模糊,採用了混合(mixed)收集的方式。即每次收集既可能只收集年輕代分區(年輕代收集),也可能在收集年輕代的同時,包含部分老年代分區(混合收集),這樣即便堆內存很大時,也能夠限制收集範圍,從而下降停頓。
Minor GC(新生代GC):在新生代的垃圾收集動做,比較頻繁,回收速度較快,採用複製算法。
Full GC(老年代GC,Major GC):發生在老年代的GC,速度較慢,採用標記-清除算法等。
jmap:用於生成堆轉儲快照(heap dump或dump文件),還能夠查詢finalize隊列,java堆和永久代的詳細信息,如空間使用率、當前使用的收集器等。
jstack:java堆棧跟蹤工具,生成虛擬機當前時刻的線程快照,主要目的在於定位線程長時間停頓的緣由,如出現死鎖、死循環、請求外部資源致使的長時間等待。線程出現停頓的時候經過jstack查看各個線程的調用堆棧,就能夠知道沒有響應的線程到底在後臺作什麼或者等待什麼資源。
jconsole:可視化監視、管理工具,內存監控(至關於可視化的jstat命令,用於監視受收集器管理的虛擬機內存的變化趨勢),線程監控(至關於可視化得jstack命令,遇到線程停頓時能夠進行監控分析)
類加載或者初始化的三個步驟:加載、鏈接、初始化
(1)加載:將類的class文件讀入內存,併爲之建立一個java.lang.Class對象,類的加載由類加載器完成。
(2)鏈接:鏈接階段負責把類的二進制數據合併到JRE中,具體又分爲以下三個階段:驗證,驗證階段用於檢驗被加載的類是否有正確的內部結構,並和其餘類協調一致;
準備,類準備階段負責爲類的類變量分配內存,並設置默認初始值。
解析,將類的二進制數據中的符號引用替換爲直接引用
(3)初始化:虛擬機負責對類初始化,主要就是對類變量進行初始化。假設這個類尚未被加載和鏈接,則程序先加載並鏈接這個類,假設該類的直接父類尚未被初始化,則先初始化其直接父類,假設類中有初始化語句,則系統依次執行這些初始化語句。
BootstrapClassLoader(根類加載器)、ExtensionClassLoader(擴展類加載器)、ApplicationClassLoader(應用程序類加載器或者叫系統類加載器)
(1)根類加載器:也被稱爲引導(原始)類加載器,負責加載Java的核心類,並非java.lang.ClassLoader的子類,而是由JVM自身實現的。
(2)擴展類加載器,負責加載JRE的擴展目錄中JAR包的類,經過這種方式,就能夠爲java擴展核心類之外的新功能,只要把本身開發的類打包成JAR文件,而後放入JAVA_HOME/jre/lib/ext路徑便可。
(3)應用程序類加載器,負責在JVM啓動時加載來自java命令的-classpath選項、java.class.path系統屬性,或CLASSPATH環境變量所指定的JAR包和類路徑。若是沒有特別指定,用戶自定義的類加載器都以系統類加載器做爲父加載器。
如何建立並使用自定義的類加載器?
繼承ClassLoader並重寫其findClass()方法。
URLClassLoader---系統類加載器和擴展類加載器的父類(繼承關係),既能夠從本地文件系統獲取二進制文件來加載類,也能夠從遠程主機獲取二進制文件來加載類。
雙親委派模型
若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每個層次的父加載器都是如此,所以全部的請求最終都應該傳送到頂層的啓動類加載器中,只有當父類加載器反饋本身沒法完成這個加載請求時,子加載器纔會嘗試本身去加載。雙親委派模型對於保證JAVA程序的穩定運做很重要。
(暫時跳過)
定義了一些規則,保證多個線程間能夠高效地、正確地協同工做,關鍵的工做圍繞多線程的原子性、可見性和有序性展開。
(1)原子性指一個操做不可被中斷
(2)可見性是指一個線程修改了共享變量的值,其餘線程是否能知道這個修改
(3)有序性是指程序在執行的過程當中會發生指令重排,指令重排能夠保證串行語義的一致性,但不能保證多線程下的語義也一致。
*爲什麼進行指令重排?
儘可能少的中斷流水線,提升CPU性能。
哪些指令不能進行重排?(Happen-before規則)
(1)程序順序原則:一個線程內保證語義的串行性
(2)volatile規則:volatile變量的寫先於讀,保證了volatile變量的可見性
(3)鎖規則:解鎖(unlock)先於加鎖(lock)
......(詳見:《實戰Java高併發程序設計》)
以上原則保證了指令重排不會破壞原有的語義結構。
強引用
在 Java 中最多見的就是強引用,把一個對象賦給一個引用變量,這個引用變量就是一個強引 用。當一個對象被強引用變量引用時,它處於可達狀態,它是不可能被垃圾回收機制回收的,即便該對象之後永遠都不會被用到 JVM 也不會回收。所以強引用是形成 Java 內存泄漏的主要緣由之 一。
軟引用
軟引用須要用 SoftReference 類來實現,對於只有軟引用的對象來講,當系統內存足夠時它不會被回收,當系統內存空間不足時它會被回收。軟引用一般用在對內存敏感的程序中。
弱引用
弱引用須要用 WeakReference 類來實現,它比軟引用的生存期更短,對於只有弱引用的對象來講,只要垃圾回收機制一運行,無論JVM 的內存空間是否足夠,總會回收該對象佔用的內存。
虛引用
虛引用須要 PhantomReference 類來實現,它不能單獨使用,必須和引用隊列聯合使用。虛引用的主要做用是對象在被收集器收集時收到一個系統通知。