一、JVN內存結構
方法區和對是全部線程共享的內存區域;而java棧、本地方法棧和程序員計數器是運行是線程私有的內存區域。java
- Java堆(Heap),是Java虛擬機所管理的內存中最大的一塊。Java堆是被全部線程共享的一塊內存區域,在虛擬機啓動時建立。此內存區域的惟一目的就是存放對象實例,幾乎全部的對象實例都在這裏分配內存。
- 方法區(Method Area),方法區(Method Area)與Java堆同樣,是各個線程共享的內存區域,它用於存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據。
- 程序計數器(Program Counter Register),程序計數器(Program Counter Register)是一塊較小的內存空間,它的做用能夠看作是當前線程所執行的字節碼的行號指示器。
- JVM棧(JVM Stacks),與程序計數器同樣,Java虛擬機棧(Java Virtual Machine Stacks)也是線程私有的,它的生命週期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每一個方法被執行的時候都會同時建立一個棧幀(Stack Frame)用於存儲局部變量表、操做棧、動態連接、方法出口等信息。每個方法被調用直至執行完成的過程,就對應着一個棧幀在虛擬機棧中從入棧到出棧的過程。
- 本地方法棧(Native Method Stacks),本地方法棧(Native Method Stacks)與虛擬機棧所發揮的做用是很是類似的,其區別不過是虛擬機棧爲虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則是爲虛擬機使用到的Native方法服務。
二、對象分配規則
- 對象優先分配在Eden區,若是Eden區沒有足夠的空間時,虛擬機執行一次Minor GC。
- 大對象直接進入老年代(大對象是指須要大量連續內存空間的對象)。這樣作的目的是避免在Eden區和兩個Survivor區之間發生大量的內存拷貝(新生代採用複製算法收集內存)。
- 長期存活的對象進入老年代。虛擬機爲每一個對象定義了一個年齡計數器,若是對象通過了1次Minor GC那麼對象會進入Survivor區,以後每通過一次Minor GC那麼對象的年齡加1,知道達到閥值對象進入老年區。
- 動態判斷對象的年齡。若是Survivor區中相同年齡的全部對象大小的總和大於Survivor空間的一半,年齡大於或等於該年齡的對象能夠直接進入老年代。
- 空間分配擔保。每次進行Minor GC時,JVM會計算Survivor區移至老年區的對象的平均大小,若是這個值大於老年區的剩餘值大小則進行一次Full GC,若是小於檢查HandlePromotionFailure設置,若是true則只進行Monitor GC,若是false則進行Full GC。
三、解釋內存中的棧(stack)、堆(heap)和靜態區(static area)的用法
一般咱們定義一個基本數據類型的變量,一個對象的引用,還有就是函數調用的現場保存都使用內存中的棧空間;而經過new關鍵字和構造器建立的對象放在堆空間;程序中的字面量(literal)如直接書寫的100、」hello」和常量都是放在靜態區中。棧空間操做起來最快可是棧很小,一般大量的對象都是放在堆空間,理論上整個內存沒有被其餘進程使用的空間甚至硬盤上的虛擬內存均可以被當成堆空間來使用。程序員
String str = new String("hello");
上面的語句中變量str放在棧上,用new建立出來的字符串對象放在堆上,而」hello」這個字面量放在靜態區。算法
四、Perm Space中保存什麼數據?會引發OutOfMemory嗎?
Perm Space中保存的是加載class文件。數據庫
會引發OutOfMemory,出現異常能夠設置 -XX:PermSize 的大小。JDK 1.8後,字符串常量不存放在永久帶,而是在堆內存中,JDK8之後沒有永久代概念,而是用元空間替代,元空間不存在虛擬機中,二是使用本地內存。編程
五、什麼是類的加載
類的加載指的是將類的.class文件中的二進制數據讀入到內存中,將其放在運行時數據區的方法區內,而後在堆區建立一個java.lang.Class對象,用來封裝類在方法區內的數據結構。類的加載的最終產品是位於堆區中的Class對象,Class對象封裝了類在方法區內的數據結構,而且向Java程序員提供了訪問方法區內的數據結構的接口。bootstrap
類加載器數組
- 啓動類加載器:Bootstrap ClassLoader,負責加載存放在JDK\jre\lib(JDK表明JDK的安裝目錄,下同)下,或被-Xbootclasspath參數指定的路徑中的,而且能被虛擬機識別的類庫
- 擴展類加載器:Extension ClassLoader,該加載器由sun.misc.Launcher$ExtClassLoader實現,它負責加載DK\jre\lib\ext目錄中,或者由java.ext.dirs系統變量指定的路徑中的全部類庫(如javax.*開頭的類),開發者能夠直接使用擴展類加載器。
- 應用程序類加載器:Application ClassLoader,該類加載器由sun.misc.Launcher$AppClassLoader來實現,它負責加載用戶類路徑(ClassPath)所指定的類,開發者能夠直接使用該類加載器
雙親委派機制:類加載器收到類加載請求,本身不加載,向上委託給父類加載,父類加載不了,再本身加載。優點就是避免Java核心API篡改。瀏覽器
六、如何⾃定義⼀個類加載器?你使⽤過哪些或者你在什麼場景下須要⼀個⾃ 定義的類加載器嗎?
自定義類加載的意義:緩存
- 加載特定路徑的class文件
- 加載一個加密的網絡class文件
- 熱部署加載class文件
七、描述一下JVM加載class文件的原理機制?
JVM中類的裝載是由類加載器(ClassLoader)和它的子類來實現的,Java中的類加載器是一個重要的Java運行時系統組件,它負責在運行時查找和裝入類文件中的類。安全
因爲Java的跨平臺性,通過編譯的Java源程序並非一個可執行程序,而是一個或多個類文件。當Java程序須要使用某個類時,JVM會確保這個類已經被加載、鏈接(驗證、準備和解析)和初始化。
類的加載是指把類的.class文件中的數據讀入到內存中,一般是建立一個字節數組讀入.class文件,而後產生與所加載類對應的Class對象。加載完成後,Class對象還不完整,因此此時的類還不可用。當類被加載後就進入鏈接階段,這一階段包括驗證、準備(爲靜態變量分配內存並設置默認的初始值)和解析(將符號引用替換爲直接引用)三個步驟。最後JVM對類進行初始化,包括:1)若是類存在直接的父類而且這個類尚未被初始化,那麼就先初始化父類;2)若是類中存在初始化語句,就依次執行這些初始化語句。類的加載是由類加載器完成的,類加載器包括:根加載器(BootStrap)、擴展加載器(Extension)、系統加載器(System)和用戶自定義類加載器(java.lang.ClassLoader的子類)。從Java 2(JDK 1.2)開始,類加載過程採起了父親委託機制(PDM)。PDM更好的保證了Java平臺的安全性,在該機制中,JVM自帶的Bootstrap是根加載器,其餘的加載器都有且僅有一個父類加載器。類的加載首先請求父類加載器加載,父類加載器無能爲力時才由其子類加載器自行加載。JVM不會向Java程序提供對Bootstrap的引用。
下面是關於幾個類加載器的說明:
-
-
- bootstrap:通常用本地代碼實現,負責加載JVM基礎核心類庫(rt.jar);
- Extension:從java.ext.dirs系統屬性所指定的目錄中加載類庫,它的父加載器是Bootstrap;
- System:又叫應用類加載器,其父類是Extension。它是應用最普遍的類加載器。它從環境變量classpath或者系統屬性java.class.path所指定的目錄中記載類,是用戶自定義加載器的默認父加載器。
八、Java對象建立過程
- JVM遇到一條新建對象的指令時首先去檢查這個指令的參數是否能在常量池中定義到一個類的符號引用。而後加載這個類(類加載過程在後邊講)
- 爲對象分配內存。一種辦法「指針碰撞」、一種辦法「空閒列表」,最終經常使用的辦法「本地線程緩衝分配(TLAB)」
- 將除對象頭外的對象內存空間初始化爲0
- 對對象頭進行必要設置
九、類的生命週期
類的生命週期包括這幾個部分,加載、鏈接、初始化、使用和卸載,其中前三部是類的加載的過程,以下圖:
- 加載,查找並加載類的二進制數據,在Java堆中也建立一個java.lang.Class類的對象
- 鏈接,鏈接又包含三塊內容:驗證、準備、初始化。 1)驗證,文件格式、元數據、字節碼、符號引用驗證; 2)準備,爲類的靜態變量分配內存,並將其初始化爲默認值; 3)解析,把類中的符號引用轉換爲直接引用
- 初始化,爲類的靜態變量賦予正確的初始值
- 使用,new出對象程序中使用
- 卸載,執行垃圾回收
十、Java 中會存在內存泄漏嗎,請簡單描述。
理論上Java由於有垃圾回收機制(GC)不會存在內存泄露問題(這也是Java被普遍使用於服務器端編程的一個重要緣由);然而在實際開發中,可能會存在無用但可達的對象,這些對象不能被GC回收,所以也會致使內存泄露的發生。例如hibernate的Session(一級緩存)中的對象屬於持久態,垃圾回收器是不會回收這些對象的,然而這些對象中可能存在無用的垃圾對象,若是不及時關閉(close)或清空(flush)一級緩存就可能致使內存泄露。下面例子中的代碼也會致使內存泄露。
import java.util.Arrays;
import java.util.EmptyStackException;
public class MyStack<T> {
private T[] elements;
private int size = 0;
private static final int INIT_CAPACITY = 16;
public MyStack() {
elements = (T[]) new Object[INIT_CAPACITY];
}
public void push(T elem) {
ensureCapacity();
elements[size++] = elem;
}
public T pop() {
if(size == 0)
throw new EmptyStackException();
return elements[--size];
}
private void ensureCapacity() {
if(elements.length == size) {
elements = Arrays.copyOf(elements, 2 * size + 1);
}
}
}
上面的代碼實現了一個棧(先進後出(FILO))結構,乍看之下彷佛沒有什麼明顯的問題,它甚至能夠經過你編寫的各類單元測試。 然而其中的pop方法卻存在內存泄露的問題,當咱們用pop方法彈出棧中的對象時,該對象不會被看成垃圾回收,即便使用棧的程序再也不引用這些對象,由於棧內部維護着對這些對象的過時引用(obsolete reference)。在支持垃圾回收的語言中,內存泄露是很隱蔽的,這種內存泄露其實就是無心識的對象保持。 若是一個對象引用被無心識的保留起來了,那麼垃圾回收器不會處理這個對象,也不會處理該對象引用的其餘對象,即便這樣的對象只有少數幾個,也可能會致使不少的對象被排除在垃圾回收以外,從而對性能形成重大影響,極端狀況下會引起Disk Paging(物理內存與硬盤的虛擬內存交換數據),甚至形成OutOfMemoryError。
十一、GC是什麼?爲何要有GC?
GC是垃圾收集的意思,內存處理是編程人員容易出現問題的地方,忘記或者錯誤的內存回收會致使程序或系統的不穩定甚至崩潰,Java提供的GC功能能夠自動監測對象是否超過做用域從而達到自動回收內存的目的,Java語言沒有提供釋放已分配內存的顯示操做方法。 Java程序員不用擔憂內存管理,由於垃圾收集器會自動進行管理。要請求垃圾收集,能夠調用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM能夠屏蔽掉顯示的垃圾回收調用。 垃圾回收能夠有效的防止內存泄露,有效的使用可使用的內存。垃圾回收器一般是做爲一個單獨的低優先級的線程運行,不可預知的狀況下對內存堆中已經死亡的或者長時間沒有使用的對象進行清除和回收,程序員不能實時的調用垃圾回收器對某個對象或全部對象進行垃圾回收。 在Java誕生初期,垃圾回收是Java最大的亮點之一,由於服務器端的編程須要有效的防止內存泄露問題,然而時過境遷,現在Java的垃圾回收機制已經成爲被詬病的東西。移動智能終端用戶一般以爲iOS的系統比Android系統有更好的用戶體驗,其中一個深層次的緣由就在於Android系統中垃圾回收的不可預知性。
補充:垃圾回收機制有不少種,包括:分代複製垃圾回收、標記垃圾回收、增量垃圾回收等方式。標準的Java進程既有棧又有堆。棧保存了原始型局部變量,堆保存了要建立的對象。Java平臺對堆內存回收和再利用的基本算法被稱爲標記和清除,可是Java對其進行了改進,採用「分代式垃圾收集」。這種方法會跟Java對象的生命週期將堆內存劃分爲不一樣的區域,在垃圾收集過程當中,可能會將對象移動到不一樣區域:
- 伊甸園(Eden):這是對象最初誕生的區域,而且對大多數對象來講,這裏是它們惟一存在過的區域。
- 倖存者樂園(Survivor):從伊甸園倖存下來的對象會被挪到這裏。
- 終身頤養園(Tenured):這是足夠老的倖存對象的歸宿。年輕代收集(Minor-GC)過程是不會觸及這個地方的。當年輕代收集不能把對象放進終身頤養園時,就會觸發一次徹底收集(Major-GC),這裏可能還會牽扯到壓縮,以便爲大對象騰出足夠的空間。
與垃圾回收相關的JVM參數:
- -Xms / -Xmx — 堆的初始大小 / 堆的最大大小
- -Xmn — 堆中年輕代的大小
- -XX:-DisableExplicitGC — 讓System.gc()不產生任何做用
- -XX:+PrintGCDetails — 打印GC的細節
- -XX:+PrintGCDateStamps — 打印GC操做的時間戳
- -XX:NewSize / XX:MaxNewSize — 設置新生代大小/新生代最大大小
- -XX:NewRatio — 能夠設置老生代和新生代的比例
- -XX:PrintTenuringDistribution — 設置每次新生代GC後輸出倖存者樂園中對象年齡的分佈
- -XX:InitialTenuringThreshold / -XX:MaxTenuringThreshold:設置老年代閥值的初始值和最大值
- -XX:TargetSurvivorRatio:設置倖存區的目標使用率
十二、作GC時,⼀個對象在內存各個Space中被移動的順序是什麼?
標記清除法,複製算法,標記整理、分代算法。
新生代通常採用複製算法 GC,老年代使用標記整理算法。
垃圾收集器:串行新生代收集器、串行老生代收集器、並行新生代收集器、並行老年代收集器。
CMS(Current Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器,它是一種併發收集器,採用的是Mark-Sweep算法。
1三、你知道哪些垃圾回收算法?
GC最基礎的算法有三種: 標記 -清除算法、複製算法、標記-壓縮算法,咱們經常使用的垃圾回收器通常都採用分代收集算法。
- 標記-清除算法,「標記-清除」(Mark-Sweep)算法,如它的名字同樣,算法分爲「標記」和「清除」兩個階段:首先標記出全部須要回收的對象,在標記完成後統一回收掉全部被標記的對象。
- 複製算法,「複製」(Copying)的收集算法,它將可用內存按容量劃分爲大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活着的對象複製到另一塊上面,而後再把已使用過的內存空間一次清理掉。
- 標記-壓縮算法,標記過程仍然與「標記-清除」算法同樣,但後續步驟不是直接對可回收對象進行清理,而是讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存
- 分代收集算法,「分代收集」(Generational Collection)算法,把Java堆分爲新生代和老年代,這樣就能夠根據各個年代的特色採用最適當的收集算法。
1四、垃圾回收器
- Serial收集器,串行收集器是最古老,最穩定以及效率高的收集器,可能會產生較長的停頓,只使用一個線程去回收。
- ParNew收集器,ParNew收集器其實就是Serial收集器的多線程版本。
- Parallel收集器,Parallel Scavenge收集器相似ParNew收集器,Parallel收集器更關注系統的吞吐量。
- Parallel Old 收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多線程和「標記-整理」算法
- CMS收集器,CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。
- G1收集器,G1 (Garbage-First)是一款面向服務器的垃圾收集器,主要針對配備多顆處理器及大容量內存的機器. 以極高機率知足GC停頓時間要求的同時,還具有高吞吐量性能特徵
1五、如何判斷一個對象是否應該被回收
判斷對象是否存活通常有兩種方式:
- 引用計數:每一個對象有一個引用計數屬性,新增一個引用時計數加1,引用釋放時計數減1,計數爲0時能夠回收。此方法簡單,沒法解決對象相互循環引用的問題。
- 可達性分析(Reachability Analysis):從GC Roots開始向下搜索,搜索所走過的路徑稱爲引用鏈。當一個對象到GC Roots沒有任何引用鏈相連時,則證實此對象是不可用的,不可達對象。
1六、JVM的永久代中會發生垃圾回收麼?
垃圾回收不會發生在永久代,若是永久代滿了或者是超過了臨界值,會觸發徹底垃圾回收(Full GC)。若是你仔細查看垃圾收集器的輸出信息,就會發現永久代也是被回收的。這就是爲何正確的永久代大小對避免Full GC是很是重要的緣由。請參考下Java8:從永久代到元數據區 (注:Java8中已經移除了永久代,新加了一個叫作元數據區的native內存區)
1七、引用的分類
- 強引用:GC時不會被回收
- 軟引用:描述有用但不是必須的對象,在發生內存溢出異常以前被回收
- 弱引用:描述有用但不是必須的對象,在下一次GC時被回收
- 虛引用(幽靈引用/幻影引用):沒法經過虛引用得到對象,用PhantomReference實現虛引用,虛引用用來在GC時返回一個通知。
1八、調優命令
Sun JDK監控和故障處理命令有jps jstat jmap jhat jstack jinfo
- jps,JVM Process Status Tool,顯示指定系統內全部的HotSpot虛擬機進程。
- jstat,JVM statistics Monitoring是用於監視虛擬機運行時狀態信息的命令,它能夠顯示出虛擬機進程中的類裝載、內存、垃圾收集、JIT編譯等運行數據。
- jmap,JVM Memory Map命令用於生成heap dump文件
- jhat,JVM Heap Analysis Tool命令是與jmap搭配使用,用來分析jmap生成的dump,jhat內置了一個微型的HTTP/HTML服務器,生成dump的分析結果後,能夠在瀏覽器中查看
- jstack,用於生成java虛擬機當前時刻的線程快照。
- jinfo,JVM Configuration info 這個命令做用是實時查看和調整虛擬機運行參數。
1九、調優工具
經常使用調優工具分爲兩類,jdk自帶監控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。
- jconsole,Java Monitoring and Management Console是從java5開始,在JDK中自帶的java監控和管理控制檯,用於對JVM中內存,線程和類等的監控
- jvisualvm,jdk自帶全能工具,能夠分析內存快照、線程快照;監控內存變化、GC變化等。
- MAT,Memory Analyzer Tool,一個基於Eclipse的內存分析工具,是一個快速、功能豐富的Java heap分析工具,它能夠幫助咱們查找內存泄漏和減小內存消耗
- GChisto,一款專業分析gc日誌的工具
20、jstack 是⼲什麼的? jstat 呢?若是線上程序週期性地出現卡頓,你懷疑可 能是 GC 致使的,你會怎麼來排查這個問題?線程⽇志⼀般你會看其中的什麼 部分?
jstack 用來查詢 Java 進程的堆棧信息。
jvisualvm 監控內存泄露,跟蹤垃圾回收、執行時內存、cpu分析、線程分析。
2一、Minor GC與Full GC分別在何時發生?
新生代內存不夠用時候發生MGC也叫YGC,JVM內存不夠的時候發生FGC
2二、你有沒有遇到過OutOfMemory問題?你是怎麼來處理這個問題的?處理 過程當中有哪些收穫?
permgen space、heap space 錯誤。
常見的緣由
-
-
- 內存加載的數據量太大:一次性從數據庫取太多數據;
- 集合類中有對對象的引用,使用後未清空,GC不能進行回收;
- 代碼中存在循環產生過多的重複對象;
- 啓動參數堆內存值小。
2三、JDK 1.8以後Perm Space有哪些變更? MetaSpace⼤⼩默認是⽆限的麼? 仍是大家會經過什麼⽅式來指定⼤⼩?
JDK 1.8後用元空間替代了 Perm Space;字符串常量存放到堆內存中。
MetaSpace大小默認沒有限制,通常根據系統內存的大小。JVM會動態改變此值。
-XX:MetaspaceSize:分配給類元數據空間(以字節計)的初始大小(Oracle邏輯存儲上的初始高水位,the initial high-water-mark)。此值爲估計值,MetaspaceSize的值設置的過大會延長垃圾回收時間。垃圾回收事後,引發下一次垃圾回收的類元數據空間的大小可能會變大。
-XX:MaxMetaspaceSize:分配給類元數據空間的最大值,超過此值就會觸發Full GC,此值默認沒有限制,但應取決於系統內存的大小。JVM會動態地改變此值。
2四、StackOverflow異常有沒有遇到過?⼀般你猜想會在什麼狀況下被觸發?如何指定⼀個線程的堆棧⼤⼩?⼀般大家寫多少?
棧內存溢出,通常由棧內存的局部變量過爆了,致使內存溢出。出如今遞歸方法,參數個數過多,遞歸過深,遞歸沒有出口。