#JVM
---
**內存模型以及分區,需要詳細到每個區放什么。**
[http://blog.csdn.net/ns_code/article/details/17565503](http://blog.csdn.net/ns_code/article/details/17565503)
JVM所管理的內存分為以下幾個運行時數據區:程序計數器、Java虛擬機棧、本地方法棧、Java堆、方法區。

程序計數器(Program Counter Register)
一塊較小的內存空間,它是當前線程所執行的字節碼的行號指示器,字節碼解釋器工作時通過改變該計數器的值來選擇下一條需要執行的字節碼指令,分支、跳轉、循環等基礎功能都要依賴它來實現。每條線程都有一個獨立的的程序計數器,各線程間的計數器互不影響,因此該區域是線程私有的。
當線程在執行一個Java方法時,該計數器記錄的是正在執行的虛擬機字節碼指令的地址,當線程在執行的是Native方法(調用本地操作系統方法)時,該計數器的值為空。另外,該內存區域是唯一一個在Java虛擬機規范中么有規定任何OOM(內存溢出:OutOfMemoryError)情況的區域。
Java虛擬機棧(Java Virtual Machine Stacks)
該區域也是線程私有的,它的生命周期也與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法被執行的時候都會同時創建一個棧幀,棧它是用于支持續虛擬機進行方法調用和方法執行的數據結構。對于執行引擎來講,活動線程中,只有棧頂的棧幀是有效的,稱為當前棧幀,這個棧幀所關聯的方法稱為當前方法,執行引擎所運行的所有字節碼指令都只針對當前棧幀進行操作。棧幀用于存儲局部變量表、操作數棧、動態鏈接、方法返回地址和一些額外的附加信息。在編譯程序代碼時,棧幀中需要多大的局部變量表、多深的操作數棧都已經完全確定了,并且寫入了方法表的Code屬性之中。因此,一個棧幀需要分配多少內存,不會受到程序運行期變量數據的影響,而僅僅取決于具體的虛擬機實現。
本地方法棧(Native Method Stacks)
該區域與虛擬機棧所發揮的作用非常相似,只是虛擬機棧為虛擬機執行Java方法服務,而本地方法棧則為使用到的本地操作系統(Native)方法服務。
Java堆(Java Heap)
Java Heap是Java虛擬機所管理的內存中最大的一塊,它是所有線程共享的一塊內存區域。幾乎所有的對象實例和數組都在這類分配內存。Java Heap是垃圾收集器管理的主要區域,因此很多時候也被稱為“GC堆”。
根據Java虛擬機規范的規定,Java堆可以處在物理上不連續的內存空間中,只要邏輯上是連續的即可。如果在堆中沒有內存可分配時,并且堆也無法擴展時,將會拋出OutOfMemoryError異常。
方法區(Method Area)
方法區也是各個線程共享的內存區域,它用于存儲已經被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。方法區域又被稱為“永久代”,但這僅僅對于Sun HotSpot來講,JRockit和IBM J9虛擬機中并不存在永久代的概念。Java虛擬機規范把方法區描述為Java堆的一個邏輯部分,而且它和Java Heap一樣不需要連續的內存,可以選擇固定大小或可擴展,另外,虛擬機規范允許該區域可以選擇不實現垃圾回收。相對而言,垃圾收集行為在這個區域比較少出現。該區域的內存回收目標主要針是對廢棄常量的和無用類的回收。運行時常量池是方法區的一部分,Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池(Class文件常量池),用于存放編譯器生成的各種字面量和符號引用,這部分內容將在類加載后存放到方法區的運行時常量池中。運行時常量池相對于Class文件常量池的另一個重要特征是具備動態性,Java語言并不要求常量一定只能在編譯期產生,也就是并非預置入Class文件中的常量池的內容才能進入方法區的運行時常量池,運行期間也可能將新的常量放入池中,這種特性被開發人員利用比較多的是String類的intern()方法。
根據Java虛擬機規范的規定,當方法區無法滿足內存分配需求時,將拋出OutOfMemoryError異常。
**內存泄漏和內存溢出的差別**
內存泄露是指分配出去的內存沒有被回收回來,由于失去了對該內存區域的控制,因而造成了資源的浪費。Java中一般不會產生內存泄露,因為有垃圾回收器自動回收垃圾,但這也不絕對,當我們new了對象,并保存了其引用,但是后面一直沒用它,而垃圾回收器又不會去回收它,這邊會造成內存泄露,
內存溢出是指程序所需要的內存超出了系統所能分配的內存(包括動態擴展)的上限。
**類型擦除**
[http://blog.csdn.net/ns_code/article/details/18011009](http://blog.csdn.net/ns_code/article/details/18011009)
Java語言在JDK1.5之后引入的泛型實際上只在程序源碼中存在,在編譯后的字節碼文件中,就已經被替換為了原來的原生類型,并且在相應的地方插入了強制轉型代碼,因此對于運行期的Java語言來說,`ArrayList<String>`和`ArrayList<Integer>`就是同一個類。所以泛型技術實際上是Java語言的一顆語法糖,Java語言中的泛型實現方法稱為類型擦除,基于這種方法實現的泛型被稱為偽泛型。
下面是一段簡單的Java泛型代碼:
```
Map<Integer,String> map = new HashMap<Integer,String>();
map.put(1,"No.1");
map.put(2,"No.2");
System.out.println(map.get(1));
System.out.println(map.get(2));
````
將這段Java代碼編譯成Class文件,然后再用字節碼反編譯工具進行反編譯后,將會發現泛型都變回了原生類型,如下面的代碼所示:
```
Map map = new HashMap();
map.put(1,"No.1");
map.put(2,"No.2");
System.out.println((String)map.get(1));
System.out.println((String)map.get(2));
```
為了更詳細地說明類型擦除,再看如下代碼:
```
import java.util.List;
public class FanxingTest{
public void method(List<String> list){
System.out.println("List String");
}
public void method(List<Integer> list){
System.out.println("List Int");
}
}
```
當我用Javac編譯器編譯這段代碼時,報出了如下錯誤:
```
FanxingTest.java:3: 名稱沖突:method(java.util.List<java.lang.String>) 和 method
(java.util.List<java.lang.Integer>) 具有相同疑符
public void method(List<String> list){
^
FanxingTest.java:6: 名稱沖突:method(java.util.List<java.lang.Integer>) 和 metho
d(java.util.List<java.lang.String>) 具有相同疑符
public void method(List<Integer> list){
^
```
2 錯誤
這是因為泛型List<String>和List<Integer>編譯后都被擦除了,變成了一樣的原生類型List,擦除動作導致這兩個方法的特征簽名變得一模一樣,在Class類文件結構一文中講過,Class文件中不能存在特征簽名相同的方法。
把以上代碼修改如下:
```
import java.util.List;
public class FanxingTest{
public int method(List<String> list){
System.out.println("List String");
return 1;
}
public boolean method(List<Integer> list){
System.out.println("List Int");
return true;
}
}
```
發現這時編譯可以通過了(注意:Java語言中true和1沒有關聯,二者屬于不同的類型,不能相互轉換,不存在C語言中整數值非零即真的情況)。兩個不同類型的返回值的加入,使得方法的重載成功了。這是為什么呢?
我們知道,Java代碼中的方法特征簽名只包括了方法名稱、參數順序和參數類型,并不包括方法的返回值,因此方法的返回值并不參與重載方法的選擇,這樣看來為重載方法加入返回值貌似是多余的。對于重載方法的選擇來說,這確實是多余的,但我們現在要解決的問題是讓上述代碼能通過編譯,讓兩個重載方法能夠合理地共存于同一個Class文件之中,這就要看字節碼的方法特征簽名,它不僅包括了Java代碼中方法特征簽名中所包含的那些信息,還包括方法返回值及受查異常表。為兩個重載方法加入不同的返回值后,因為有了不同的字節碼特征簽名,它們便可以共存于一個Class文件之中。
**堆里面的分區:Eden,survival from to,老年代,各自的特點。**
**對象創建方法,對象的內存分配,對象的訪問定位。**
對內存分配情況分析最常見的示例便是對象實例化:
```
Object obj = new Object();
```
這段代碼的執行會涉及java棧、Java堆、方法區三個最重要的內存區域。假設該語句出現在方法體中,及時對JVM虛擬機不了解的Java使用這,應該也知道obj會作為引用類型(reference)的數據保存在Java棧的本地變量表中,而會在Java堆中保存該引用的實例化對象,但可能并不知道,Java堆中還必須包含能查找到此對象類型數據的地址信息(如對象類型、父類、實現的接口、方法等),這些類型數據則保存在方法區中。
另外,由于reference類型在Java虛擬機規范里面只規定了一個指向對象的引用,并沒有定義這個引用應該通過哪種方式去定位,以及訪問到Java堆中的對象的具體位置,因此不同虛擬機實現的對象訪問方式會有所不同,主流的訪問方式有兩種:使用句柄池和直接使用指針。
**GC的兩種判定方法:引用計數與引用鏈。**
引用計數方式最基本的形態就是讓每個被管理的對象與一個引用計數器關聯在一起,該計數器記錄著該對象當前被引用的次數,每當創建一個新的引用指向該對象時其計數器就加1,每當指向該對象的引用失效時計數器就減1。當該計數器的值降到0就認為對象死亡。
Java的內存回收機制可以形象地理解為在堆空間中引入了重力場,已經加載的類的靜態變量和處于活動線程的堆棧空間的變量是這個空間的牽引對象。這里牽引對象是指按照Java語言規范,即便沒有其它對象保持對它的引用也不能夠被回收的對象,即Java內存空間中的本原對象。當然類可能被去加載,活動線程的堆棧也是不斷變化的,牽引對象的集合也是不斷變化的。對于堆空間中的任何一個對象,如果存在一條或者多條從某個或者某幾個牽引對象到該對象的引用鏈,則就是可達對象,可以形象地理解為從牽引對象伸出的引用鏈將其拉住,避免掉到回收池中。
**GC的三種收集方法:標記清除、標記整理、復制算法的原理與特點,分別用在什么地方,如果讓你優化收集方法,有什么思路?**
標記清除算法是最基礎的收集算法,其他收集算法都是基于這種思想。標記清除算法分為“標記”和“清除”兩個階段:首先標記出需要回收的對象,標記完成之后統一清除對象。它的主要缺點:①.標記和清除過程效率不高 。②.標記清除之后會產生大量不連續的內存碎片。
標記整理,標記操作和“標記-清除”算法一致,后續操作不只是直接清理對象,而是在清理無用對象完成后讓所有存活的對象都向一端移動,并更新引用其對象的指針。主要缺點:在標記-清除的基礎上還需進行對象的移動,成本相對較高,好處則是不會產生內存碎片。
復制算法,它將可用內存容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊用完之后,就將還存活的對象復制到另外一塊上面,然后在把已使用過的內存空間一次理掉。這樣使得每次都是對其中的一塊進行內存回收,不會產生碎片等情況,只要移動堆訂的指針,按順序分配內存即可,實現簡單,運行高效。主要缺點:內存縮小為原來的一半。
**Minor GC與Full GC分別在什么時候發生?**
Minor GC:通常是指對新生代的回收。指發生在新生代的垃圾收集動作,因為 Java 對象大多都具備朝生夕滅的特性,所以 Minor GC 非常頻繁,一般回收速度也比較快
Major GC:通常是指對年老代的回收。
Full GC:Major GC除并發gc外均需對整個堆進行掃描和回收。指發生在老年代的 GC,出現了 Major GC,經常會伴隨至少一次的 Minor GC(但非絕對的,在 ParallelScavenge 收集器的收集策略里就有直接進行 Major GC 的策略選擇過程) 。MajorGC 的速度一般會比 Minor GC 慢 10倍以上。
**幾種常用的內存調試工具:jmap、jstack、jconsole。**
jmap(linux下特有,也是很常用的一個命令)觀察運行中的jvm物理內存的占用情況。
參數如下:
-heap:打印jvm heap的情況
-histo:打印jvm heap的直方圖。其輸出信息包括類名,對象數量,對象占用大小。
-histo:live :同上,但是只答應存活對象的情況
-permstat:打印permanent generation heap情況
jstack(linux下特有)可以觀察到jvm中當前所有線程的運行情況和線程當前狀態
jconsole一個圖形化界面,可以觀察到java進程的gc,class,內存等信息
jstat最后要重點介紹下這個命令。這是jdk命令中比較重要,也是相當實用的一個命令,可以觀察到classloader,compiler,gc相關信息
具體參數如下:
-class:統計class loader行為信息
-compile:統計編譯行為信息
-gc:統計jdk gc時heap信息
-gccapacity:統計不同的generations(不知道怎么翻譯好,包括新生區,老年區,permanent區)相應的heap容量情況
-gccause:統計gc的情況,(同-gcutil)和引起gc的事件
-gcnew:統計gc時,新生代的情況
-gcnewcapacity:統計gc時,新生代heap容量
-gcold:統計gc時,老年區的情況
-gcoldcapacity:統計gc時,老年區heap容量
-gcpermcapacity:統計gc時,permanent區heap容量
-gcutil:統計gc時,heap情況
-printcompilation:不知道干什么的,一直沒用過。
**類加載的五個過程:加載、驗證、準備、解析、初始化。**
類加載過程
類從被加載到虛擬機內存中開始,到卸載出內存為止,它的整個生命周期包括加載、驗證、準備、解析、初始化、使用、卸載。
其中類加載的過程包括了加載、驗證、準備、解析、初始化五個階段。在這五個階段中,加載、驗證、準備和初始化這四個階段發生的順序是確定的,而解析階段則不一定,它在某些情況下可以在初始化階段之后開始,這是為了支持Java語言的運行時綁定(也成為動態綁定或晚期綁定)。另外注意這里的幾個階段是按順序開始,而不是按順序進行或完成,因為這些階段通常都是互相交叉地混合進行的,通常在一個階段執行的過程中調用或激活另一個階段。
這里簡要說明下Java中的綁定:綁定指的是把一個方法的調用與方法所在的類(方法主體)關聯起來,對java來說,綁定分為靜態綁定和動態綁定:
* 靜態綁定:即前期綁定。在程序執行前方法已經被綁定,此時由編譯器或其它連接程序實現。針對java,簡單的可以理解為程序編譯期的綁定。java當中的方法只有final,static,private和構造方法是前期綁定的。
* 動態綁定:即晚期綁定,也叫運行時綁定。在運行時根據具體對象的類型進行綁定。在java中,幾乎所有的方法都是后期綁定的。
“加載”(Loading)階段是“類加載”(Class Loading)過程的第一個階段,在此階段,虛擬機需要完成以下三件事情:
1. 通過一個類的全限定名來獲取定義此類的二進制字節流。
2. 將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構。
3. 在Java堆中生成一個代表這個類的java.lang.Class對象,作為方法區這些數據的訪問入口。
驗證是連接階段的第一步,這一階段的目的是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,并且不會危害虛擬機自身的安全。
準備階段是為類的靜態變量分配內存并將其初始化為默認值,這些內存都將在方法區中進行分配。準備階段不分配類中的實例變量的內存,實例變量將會在對象實例化時隨著對象一起分配在Java堆中。
解析階段是虛擬機將常量池內的符號引用替換為直接引用的過程。
類初始化是類加載過程的最后一步,前面的類加載過程,除了在加載階段用戶應用程序可以通過自定義類加載器參與之外,其余動作完全由虛擬機主導和控制。到了初始化階段,才真正開始執行類中定義的Java程序代碼。
**雙親委派模型:Bootstrap ClassLoader、Extension ClassLoader、ApplicationClassLoader。**
1. 啟動類加載器,負責將存放在<JAVA_HOME>\lib目錄中的,或者被-Xbootclasspath參數所指定的路徑中,并且是虛擬機識別的(僅按照文件名識別,如rt.jar,名字不符合的類庫即時放在lib目錄中也不會被加載)類庫加載到虛擬機內存中。啟動類加載器無法被java程序直接引用。
2. 擴展類加載器:負責加載<JAVA_HOME>\lib\ext目錄中的,或者被java.ext.dirs系統變量所指定的路徑中的所有類庫,開發者可以直接使用該類加載器。
3. 應用程序類加載器:負責加載用戶路徑上所指定的類庫,開發者可以直接使用這個類加載器,也是默認的類加載器。
三種加載器的關系:啟動類加載器->擴展類加載器->應用程序類加載器->自定義類加載器。
這種關系即為類加載器的雙親委派模型。其要求除啟動類加載器外,其余的類加載器都應當有自己的父類加載器。這里類加載器之間的父子關系一般不以繼承關系實現,而是用組合的方式來復用父類的代碼。
雙親委派模型的工作過程:如果一個類加載器接收到了類加載的請求,它首先把這個請求委托給他的父類加載器去完成,每個層次的類加載器都是如此,因此所有的加載請求都應該傳送到頂層的啟動類加載器中,只有當父加載器反饋自己無法完成這個加載請求(它在搜索范圍中沒有找到所需的類)時,子加載器才會嘗試自己去加載。
好處:java類隨著它的類加載器一起具備了一種帶有優先級的層次關系。例如類java.lang.Object,它存放在rt.jar中,無論哪個類加載器要加載這個類,最終都會委派給啟動類加載器進行加載,因此Object類在程序的各種類加載器環境中都是同一個類。相反,如果用戶自己寫了一個名為java.lang.Object的類,并放在程序的Classpath中,那系統中將會出現多個不同的Object類,java類型體系中最基礎的行為也無法保證,應用程序也會變得一片混亂。
實現:在java.lang.ClassLoader的loadClass()方法中,先檢查是否已經被加載過,若沒有加載則調用父類加載器的loadClass()方法,若父加載器為空則默認使用啟動類加載器作為父加載器。如果父加載失敗,則拋出ClassNotFoundException異常后,再調用自己的findClass()方法進行加載。
**分派:靜態分派與動態分派。**
靜態分派與重載有關,虛擬機在重載時是通過參數的靜態類型,而不是運行時的實際類型作為判定依據的;靜態類型在編譯期是可知的;
動態分派與重寫(Override)相關,invokevirtual(調用實例方法)指令執行的第一步就是在運行期確定接收者的實際類型,根據實際類型進行方法調用;
**GC收集器有哪些?CMS收集器與G1收集器的特點。**
**自動內存管理機制,GC算法,運行時數據區結構,可達性分析工作原理,如何分配對象內存**
**反射機制,雙親委派機制,類加載器的種類**
**Jvm內存模型,先行發生原則,violate關鍵字作用**
- JavaSE(Java基礎)
- Java基礎知識
- Java中的內存泄漏
- String源碼分析
- Java集合結構
- ArrayList源碼剖析
- HashMap源碼剖析
- Hashtable簡介
- Vector源碼剖析
- LinkedHashMap簡介
- LinkedList簡介
- JVM(Java虛擬機)
- JVM基礎知識
- JVM類加載機制
- Java內存區域與內存溢出
- 垃圾回收算法
- Java并發(JavaConcurrent)
- Java并發基礎知識
- 生產者和消費者問題
- Thread和Runnable實現多線程的區別
- 線程中斷
- 守護線程與阻塞線程的情況
- Synchronized
- 多線程環境中安全使用集合API
- 實現內存可見的兩種方法比較:加鎖和volatile變量
- 死鎖
- 可重入內置鎖
- 使用wait/notify/notifyAll實現線程間通信
- NIO
- 數據結構(DataStructure)
- 數組
- 棧和隊列
- Algorithm(算法)
- 排序
- 選擇排序
- 冒泡排序
- 快速排序
- 歸并排序
- 查找
- 順序查找
- 折半查找
- Network(網絡)
- TCP/UDP
- HTTP
- Socket
- OperatingSystem(操作系統)
- Linux系統的IPC
- android中常用設計模式
- 面向對象六大原則
- 單例模式
- Builder模式
- 原型模式
- 簡單工廠
- 策略模式
- 責任鏈模式
- 觀察者模式
- 代理模式
- 適配器模式
- 外觀模式
- Android(安卓面試點)
- Android基礎知識
- Android內存泄漏總結
- Handler內存泄漏分析及解決
- Android性能優化
- ListView詳解
- RecyclerView和ListView的異同
- AsyncTask源碼分析
- 插件化技術
- 自定義控件
- ANR問題
- Art和Dalvik的區別
- Android關于OOM的解決方案
- Fragment
- SurfaceView
- Android幾種進程
- APP啟動過程
- 圖片三級緩存
- Bitmap的分析與使用
- 熱修復的原理
- AIDL
- Binder機制
- Zygote和System進程的啟動過程
- Android中的MVC,MVP和MVVM
- MVP
- Android開機過程
- EventBus用法詳解
- 查漏補缺
- Git操作