1.JVM是什麼?java
JVM:Java Virtual Mechinal(Java虛擬機).它是一個虛構的計算機,是經過在實際的計算機上模擬各類功能來實現的。JVM的主要工做是解釋本身的指令集(字節碼,如java源碼編譯成class文件在虛擬機上運行)並映射到本地的CPU指令集或OS的系統調用。Java語言跨平臺的本質就是不一樣的操做系統使用不一樣的JVM映射規則,使其與操做系統無關,從而實現跨平臺。
數組
2.JVM的內存結構是什麼樣子?多線程
Java虛擬機在運行Java程序的時候,會把它所管理的內存劃分爲若干個不一樣的數據區域,如圖:jvm
3.那麼接下來每一個數據區域都是作什麼的呢?spa
(1).程序計數器:是一塊較小的內存空間,能夠看做是當前線程所執行字節碼的行號指示器。尤爲在多線程的狀況下,尤其重要。Java虛擬機的多線程是經過線程輪流切換並分配處理器執行時間的方式來實現的,即在任何一個肯定時刻,一個處理器只會執行一條線程,當線程切換後就須要恢復到正確位置,所以,程序計數器要實現線程隔離,每一個線程都有本身的專屬的計數器。值得注意的是:此內存區域是惟一一個在Java虛擬機規範中沒有規定任何OutOfMemoryError狀況的區域。操作系統
(2).堆:此內存區域是Java虛擬機管理的最大一塊內存,同時也是線程共有的,在虛擬機啓動時建立。它用來存儲Java中的對象實例(不管成員變量,局部變量仍是類變量,它們指向的對象都存儲在堆內存中),幾乎全部的對象實例都在這分配內存。同時這裏也是GC的主要區域。從內存回收的角度來看,因爲收集器基本都採用分代收集法,因此在Java堆中還能夠細分爲:新生代和老年代(能夠理解爲不用代的對象內存位置是不一樣的);再細緻可分爲:Eden空間,From Survivor空間,To Survivor空間(8:1:1)。從內存分配的角度來看,線程共享的Java堆中可能劃分出多個線程私有的分配緩衝區。劃分的目的是爲了更好地回收內存或更快的分配內存。
線程
注意:Java堆能夠處在物理上不連續的內存空間中,只要邏輯上連續便可。指針
(3).虛擬機棧:它是線程私有的,生命週期與線程相同。三部分組成:局部變量區、操做數棧、幀數據區。(文章後面會詳細介紹)對象
(4).本地方法棧:和虛擬機棧很相似,他們的區別不過是前者爲虛擬機執行Native方法服務,後者爲虛擬機執行Java方法服務。但不一樣虛擬機的實現不一樣,如Sun HotSpot虛擬機直接就把兩棧合二爲一。blog
(5).方法區:線程共享的內存區域,做用是存儲Java類的結構信息。當咱們建立對象實例後,對象的類型信息存儲在方法區中;實例數據存放在堆中;實例數據指的是Java中建立的各類實例對象以及他們的值,類型信息指的是定義在Java代碼中的常量、靜態變量以及在類中聲明的各類方法、方法字段等;同時可能包括即時編譯器編譯後產生的代碼數據。
4.深刻理解棧:
(1).讓咱們在來總結一下堆和棧的區別:
①.功能不一樣:棧內存用來存儲局部變量和方法調用;而堆用來存儲對象實例。
②.共享不一樣:棧是線程私有;而堆是線程共有。
③.異常錯誤不一樣:棧空間不足時爲StackOverFlowError;堆空間不足時爲OutOfMemoryError。
④.空間大小不一樣:堆遠遠大於棧的內存大小。
(2).咱們都知道棧的三部分組成:局部變量區、操做數棧和幀數據區。其中局部變量區和操做數棧要視對應的方法大小而定,是按字節計算的。但調用一個方法時,它從類型信息中獲得此方法局部變量區和操做數棧大小並分配棧內存,而後壓入棧中。
◆局部變量區:
是一個以字節爲單位、從0開始計數的數組,類型爲short、byte、char的值存入數組前要轉爲int值,long和double佔連續的倆字節,但訪問long和double時只須要取出連續的第一項索引便可。(能夠理解爲方法中局部變量根據類型的不一樣佔了不一樣大小的內存,並有本身的索引下標,refrence佔一個字節)
◆操做數棧:
同局部變量區同樣,即以字節爲單位的數組。但不一樣的是它不是經過索引來訪問,而是經過出棧和入棧來訪問。能夠把操做數棧理解爲存儲計算時的臨時數據的地方。
◆幀數據區:
除了上述兩個部分,Java棧幀還須要一些數據來支持常量池解析、正常方法返回以及異常派發機制。這些數據都儲存在幀數據區中。當JVM執行到須要常量池中的數據時,它會經過幀數據區中指向常量池的指針來訪問它。除此以外,幀數據區中的數據還要處理Java方法的正常結束和異常終止:若是經過return正常結束,當前棧幀彈棧,如有返回值,則把此值壓入發起調用方法的操做數棧;若是異常停止,幀中保存了一個對此方法異常引用表的引用,有異常時,JVM找catch代碼塊中的代碼,若沒有則方法當即停止,幀中的信息恢復發起調用的方法的幀,再發起調用方法的上下文從新拋出異常。
5.什麼是直接內存?
(1).直接內存也能夠稱爲堆外內存,和堆內內存相對應,堆內內存=新生代+老年代+持久代,徹底遵照JVM虛擬機的內存管理機制;而堆外內存就是把內存對象分配到虛擬機的堆之外的內存,直接受操做系統管理。
(2).如何申請堆外內存?
JDK的ByteBuffer(Java.nio.ByteBuffer)類提供了一個接口allocateDirect(int capacity),經過調用進行對外內存的申請,底層經過unsafe.allocateMemory(size)實現,如圖:
(3).申請後如何釋放內存呢?
JDK使用DirectByteBuffer對象來表示堆外存,每一個DirectByteBuffer對象在初始化時,都會建立一個對應的Cleaner對象,用於保存堆外存的元信息(開始地址、大小和容量等),當DirectByteBuffer對象被GC回收後,Cleaner對象被放入RefrenceQueue中,而後由ReferenceHandler守護線程調用unsafe.freeMemory(address),回收對外內存。
主動回收:對於Sun JDK,調用Cleaner(sun.misc.Cleaner)的clean()方法就行;
基於GC回收:如上述,DirectByteBuffer對象被GC時,會調用Cleaner回收其堆外的引用。在於YGC只會只會回收新生代中的不可達對象,若是有大量DirectByteBuffer對象進入Old區,而又一直沒有FGC,物理內存會被慢慢耗光,致使OOM。
(4).爲何DirectByteBuffer對象被GC,Cleaner對象會進入ReferenceQueue中呢?
緣由:Cleaner對象關聯了一個PhantomReference引用(jvm四種引用之一),若是GC過程當中某個對象有且只有PhantomReference對它進行引用,那將會把這個引用放到java.lang.ref.Reference.pending隊列裏,GC完畢後,則會經過上述的守護線程調用方法,回收堆外內存。
(5).堆外內存默認大小:(-Xmx)-(1個survivor大小)
(6).堆外內存溢出異常:java.lang.OutOfMemoryError:Direct buffer memory
(7).優勢:減小了垃圾回收;加快了複製速度(堆內存在flush到遠程時,會先複製到直接內存,而後再發送)
6.System.gc():
(1).此方法實際上是調用的Runtime.getRuntime().gc();再進入gc()則看到是native方法。運行此方法代表:java虛擬機擴展努力回收未使用的對象,以便內存能夠快速複用。
(2).做用:
●作一次full gc
●執行後會暫停整個進程
●此方法能夠禁掉,使用-XX:+DisableExplicitGC
●最多見的場景時RMI/NIO下的堆外內存分配
7.注意點:
(1).Java中的基本數據類型不必定存儲在棧中。棧內存用來存儲局部變量和方法調用,若是該局部變量是基本數據類型,則存儲在棧中;若是局部變量是一個對象(int[] arr = new int[]{1,2} ),則存儲在堆中。
(2).方法區,有人叫「永久代」,本質不等價,只是由於把GC分代收集擴展至方法。在該區域進行內存回收的目的主要是對常量池的回收和類型(內存數據)的卸載,但回收效率很低。運行時常量池是方法區的一部分,用來存儲java類文件常量池中的符號信息。
(3).HotSpot方法區的變化:JDK1.2-6,使用永久帶實現方法區,使用GC分代實現方法區;JDK7 Oracle HotSpot 移除永久代,JDK 7 中的符號表被移動到Native Heap中,字符串常量池和類引用被移動到Java Heap中;JDK 8 永久代已徹底被元空間取代。