一:JVM體系概述前端
1:JVM是運行在操做系統之上的,他與硬件沒有直接的交互。java
二:JVM內存結構算法
Java虛擬機在運行時,會把內存空間分爲若干個區域。Java虛擬機所管理的內存區域分爲以下部分:方法區、堆內存、虛擬機棧、本地方法棧、程序計數器。安全
一、類裝載器ClassLoader前端框架
負責加載class文件,class文件在文件開頭有特定的文件標識,而且ClassLoader只負責class文件的加載,至於它是否能夠運行,則是由執行引擎(Execution Engine)決定。框架
虛擬機自帶的加載器:spa
啓動類加載器(Bootstrap):由C++編寫,不是前端框架Bootstrap。操作系統
擴展類加載器(Extension):由Java語言編寫線程
應用程序類加載器(App):由Java語言編寫,也叫系統類加載器,加載當前應用的classpath的全部類。3d
用戶自定義加載器
Java.lang.ClassLoader的之類,用戶能夠定製的加載方式。
類加載器的雙親委派機制
某個特定的類加載器在加載類的請求時,首先將加載任務委託給父類加載器,依次遞歸,若是父類加載器能夠完成類加載任務,就成功返回;只有父類加載器沒法完成該加載任務時,才本身去加載。
沙箱機制(防止惡意代碼對java自己的破壞)
當用戶命名了和Java同樣的類時,Java會首先加載自帶的類。
二、方法區
方法區是線程共享的,一般用來保存裝載的類的元結構信息。主要用於存儲虛擬機加載的類信息、常量、靜態變量,以及編譯器編譯後的代碼等數據。
在jdk1.7及其以前,方法區是堆的一個「邏輯部分」(一片連續的堆空間)。也有人用「永久代」表示方法區。
在jdk1.8中,方法區已經不存在,原方法區中存儲的類信息、編譯後的代碼數據等已經移動到了元空間(MetaSpace)中,元空間並無處於堆內存上,而是直接佔用的本地內存(NativeMemory)。
3:堆內存
一個JVM實例只存在一個堆內存,堆內存的大小是能夠調節的。類加載器讀取了類文件後,須要把類,方法,穿變量放到堆內存中去,
它是JVM管理的內存中最大的一塊區域,堆內存和方法區都被全部線程共享,在虛擬機啓動時建立。在垃圾收集的層面上來看,因爲如今收集器基本上都採用分代收集算法,所以堆還能夠分爲新生代(YoungGeneration)和老年代(OldGeneration),新生代還能夠分爲Eden、From Survivor、To Survivor。
JAVA1.7以下圖,但在Java1.8中,其餘基本沒變,只是將Perm變成了元空間
4:程序計數器
程序計數器是一塊很是小的內存空間,能夠看作是當前線程執行字節碼的行號指示器,每一個線程都有一個獨立的程序計數器,所以程序計數器是線程私有的一塊空間,此外,程序計數器是Java虛擬機規定的惟一不會發生內存溢出的區域。
5:虛擬機棧
虛擬機棧也是每一個線程私有的一塊內存空間,它描述的是方法的內存模型。
虛擬機會爲每一個線程分配一個虛擬機棧,每一個虛擬機棧中都有若干個棧幀,每一個棧幀中存儲了局部變量表、操做數棧、動態連接、返回地址等。一個棧幀就對應Java代碼中的一個方法,當線程執行到一個方法時,就表明這個方法對應的棧幀已經進入虛擬機棧而且處於棧頂的位置,每個Java方法從被調用到執行結束,就對應了一個棧幀從入棧到出棧的過程。
六、本地方法棧
虛擬機棧執行的是Java方法,本地方法棧執行的是本地方法(Native Method),其餘基本上一致
7:元空間
上面說到,jdk1.8中,已經不存在永久代(方法區),替代它的一塊空間叫作「元空間」,和永久代相似,都是JVM規範對方法區的實現,可是元空間並不在虛擬機中,而是使用本地內存,元空間的大小僅受本地內存限制,但能夠經過-XX:MetaspaceSize和-XX:MaxMetaspaceSize來指定元空間的大小。
三:垃圾回收機制
垃圾回收,就是經過垃圾收集器把內存中沒用的對象清理掉。垃圾回收涉及到的內容有:一、判斷對象是否已死;二、選擇垃圾收集算法;三、選擇垃圾收集的時間;四、選擇適當的垃圾收集器清理垃圾(已死的對象)。
1:判斷對象是否以死
判斷對象是否已死就是找出哪些對象是已經死掉的,之後不會再用到的,就像地上有廢紙、飲料瓶和百元大鈔,掃地前要先判斷出地上廢紙和飲料瓶是垃圾,百元大鈔不是垃圾。判斷對象是否已死有 引用計數算法 和 可達性分析算法。
(1)引用計數算法
給每個對象添加一個引用計數器,每當有一個地方引用它時,計數器值加1;每當有一個地方再也不引用它時,計數器值減1,這樣只要計數器的值不爲0,就說明還有地方引用它,它就不是無用的對象。以下圖,對象2有1個引用,它的引用計數器值爲1,對象1有兩個地方引用,它的引用計數器值爲2 。
這種方法看起來很是簡單,但目前許多主流的虛擬機都沒有選用這種算法來管理內存,緣由就是當某些對象之間互相引用時,沒法判斷出這些對象是否已死,以下圖,對象1和對象2都沒有被堆外的變量引用,而是被對方互相引用,這時他們雖然沒有用處了,可是引用計數器的值仍然是1,沒法判斷他們是死對象,垃圾回收器也就沒法回收。
(2)可達性分析算法
瞭解可達性分析算法以前先了解一個概念——GC Roots,垃圾收集的起點,能夠做爲GC Roots的有虛擬機棧中本地變量表中引用的對象、方法區中靜態屬性引用的對象、方法區中常量引用的對象、本地方法棧中JNI(Native方法)引用的對象。
當一個對象到GC Roots沒有任何引用鏈相連(GC Roots到這個對象不可達)時,就說明此對象是不可用的,是死對象。以下圖:object一、object二、object三、object4和GC Roots之間有可達路徑,這些對象不會被回收,但object五、object六、object7到GC Roots之間沒有可達路徑,這些對象就被判了死刑。
(3)方法區回收
上面說的都是對堆內存中對象的判斷,方法區中主要回收的是廢棄的常量和無用的類。
判斷常量是否廢棄能夠判斷是否有地方引用這個常量,若是沒有引用則爲廢棄的常量。
判斷類是否廢棄須要同時知足以下條件:
二、經常使用垃圾回收算法
(1)標記-清除算法:分爲標記和清除兩個階段,首先標記出全部須要回收的對象,標記完成後統一回收全部被標記的對象,以下圖。
缺點:標記和清除兩個過程效率都不高;標記清除以後會產生大量不連續的內存碎片。
(2)複製算法:把內存分爲大小相等的兩塊,每次存儲只用其中一塊,當這一塊用完了,就把存活的對象所有複製到另外一塊上,同時把使用過的這塊內存空間所有清理掉,往復循環,以下圖。
缺點:實際可以使用的內存空間縮小爲原來的一半,比較適合。
(3)標記-整理算法:先對可用的對象進行標記,而後全部被標記的對象向一段移動,最後清除可用對象邊界之外的內存,以下圖。
(4)分代收集算法:把堆內存分爲新生代和老年代,新生代又分爲Eden區、From Survivor和To Survivor。通常新生代中的對象基本上都是朝生夕滅的,每次只有少許對象存活,所以採用複製算法,只須要複製那些少許存活的對象就能夠完成垃圾收集;老年代中的對象存活率較高,就採用標記-清除和標記-整理算法來進行回收。
三、選擇垃圾收集的時間
當程序運行時,各類數據、對象、線程、內存等都時刻在發生變化,當下達垃圾收集命令後就馬上進行收集嗎?確定不是,他們要在保證線程安全的前提下進行垃圾回收
安全點:從線程角度看,安全點能夠理解爲是在代碼執行過程當中的一些特殊位置,當線程執行到安全點的時候,說明虛擬機當前的狀態是安全的,若是有須要,能夠在這裏暫停用戶線程。當垃圾收集時,若是須要暫停當前的用戶線程,但用戶線程當時沒在安全點上,則應該等待這些線程執行到安全點再暫停。理論上,解釋器的每條字節碼的邊界上均可以放一個安全點,實際上,安全點基本上以「是否具備讓程序長時間執行的特徵」爲標準進行選定。
安全區:安全點是相對於運行中的線程來講的,對於如sleep或blocked等狀態的線程,收集器不會等待這些線程被分配CPU時間,這時候只要線程處於安全區中,就能夠算是安全的。安全區就是在一段代碼片斷中,引用關係不會發生變化,能夠看做是被擴展、拉長了的安全點。
四、常見垃圾收集器
新生代收集器: Serial 、 ParNew 、 Parallel Scavenge
老年代收集器: Serial Old 、 CMS 、 Parallel Old
堆內存垃圾收集器: G1
這些垃圾收集器一樣很重要,能夠自行百度瞭解其原理。