對於使用 C、C++ 的程序員來說,在內存管理領域,他們既是擁有最高權力的皇帝又是從事最基礎工作的勞動人民——擁有每一個對象的“所有權”,又擔負着每一個對象生命開始到終結的維護責任。對於 Java 程序員來說,在虛擬機自動內存管理機制的幫助下,不再需要為每一個 new 操作去寫配對的 delete/free 代碼,不容易出現內存泄漏和內存溢出,看起一切都很美好。不過,也正因為 Java 程序員把控制內存的權力交給了 Java 虛擬機,一旦出現內存泄漏和溢出,如果不了解虛擬機是怎樣使用內存的,那排查錯誤將會成為一項異常艱難的工作。
一、Java虛擬機和Java內存區域概述
1、虛擬機:
定義:模擬某種計算機體系結構,執行特定指令集的軟件。
分為系統虛擬機(Virtual Box、VMware),進程虛擬機
2、進程虛擬機
並不會完整的模擬一個操作系統的運行環境,僅僅是提供特定指令集的運行環境。如JVM、Adobe Flash Player、FC模擬器
3、高級語言虛擬機
把特定指令集的范圍限定為高級語言,如JVM、.NET CLR、P-Code(pascal)
4、Java語言虛擬機
可以執行Java語言的高級語言虛擬機,Java語言的虛擬機並不一定就可以成為JVM,譬如:Apache Harmony
5、JavaTM虛擬機
必須通過Java TCK(Technology Compatibility kit)的兼容性測試的Java語言虛擬機才能稱為“JavaTM虛擬機”
JavaTM虛擬機並非一定要執行“Java”程序,他跟Java語言並沒有嚴格相關,他跟java的編譯文件class文件產生關聯,即其他語言編譯的符合格式的class文件也可以運行
業界三大商用JVM:
Oracle HotSpot:JDK中使用的虛擬機,命名來自它的“熱點代碼探測”技術、
Oracle JRockit VM:、
IBM J9 VM:
6、共有設計,私有實現
《Java虛擬機規范》定義了概念模型,但同時也聲明了這些概念不約束虛擬機的具體實現
7、Java虛擬機運行時數據區
程序計數器、Java堆、Java虛擬機棧、本地方法棧、方法區

8、程序計數器(Program Counter Register)
一塊較小的內存空間,它的作用可以看作是當前線程所執行的字節碼的行號指示器
如果線程正在執行的是一個Java方法,這個計數器記錄的是正在執行的虛擬機字節碼指令的地址;如果正在執行的是Native方法,這個計數器值則為空。
此內存區域是唯一一個在Java虛擬機規范中沒有規定任何OutOfMemoryError情況的區域
二、Java虛擬機棧和本地方法棧
1、Java虛擬機棧的概念和特征
線程私有,后進先出棧,存儲棧幀,支撐Java方法的調用、 執行和退出
可能出現StackOverflowError異常,如果棧被設計成可動態擴展,而在擴展的時候又申請不到足夠的內存,還可能出現OutOfMemoryError異常
2、本地方法棧的概念和特征
線程私有,后進先出棧,作用是支撐Native方法的調用、執行和退出
可能出現StackOverflowError和OutOfMemoryError異常
有一些虛擬機如HotSpot將Java虛擬機棧和本地方法棧合並實現
3、棧幀的概念和特征
Java虛擬機棧中存儲的內容,它被用於存儲數據和部分過程結果的數據結構,同時也被用來處理動態鏈接、方法返回值和異常分派
一個完整的棧幀包含:局部變量表、操作數棧、動態連接信息、方法正常完成和異常完成信息
局部變量表:由若干個Slot組成,長度由編譯期決定
單個Slot可以存儲一個類型為Boolean,byte,char,short,float,reference和returnAddress的數據,兩個Slot可以存儲一個類型為long或double的數據
局部變量表用於方法間參數傳遞,以及方法執行過程中存儲基礎數據類型的值和對象的引用
操作數棧:由若干個Entry組成,長度由編譯期決定
三、Java堆
1、Java堆的概念
特征:全局共享,通常是Java虛擬機中最大的一塊內存區域,作用是作為java對象的主要存儲區域。JVMS明確要求該區域需要實現自動內存管理,即常說的GC,但並不限制采用哪種算法和技術去實現,可以在物理上不連續,可動態擴展,可能出現OutOfMemoryError
2、棧與堆的討論
四、方法區和運行時常量池
1、方法區的概念
全局共享,作用是存儲Java類的結構信息,JVMS不要求該區域實現自動內存管理,但是商用虛擬機都能夠自動管理該區域的內存
2、運行時常量池的概念
全局共享,是方法區的一部分,作用是存儲Java類文件常量池中的符號信息
3、HotSpot方法區實現的變遷
在jdk1.2-jdk6,HotSpot使用永久代實現方法區
jdk7開始,HotSpot開始移除永久代的計划,符號表被移到Native Heap中,字符串常量和類的靜態引用被移到Java Heap中
jdk8開始,永久代已被元空間(Metaspace)所代替
五、直接內存
並非JVMS定義的標准Java運行時內存區域
隨JDK1.4中加入的NIO被引入,目的是避免在Java堆和Native堆中來回復制數據帶來的性能損耗
全局共享,能被自動管理,但是在檢測手段上可能會有一些簡陋
可能出現OutOfMemoryError異常
六、垃圾回收機制(GC Garbage Collection)
1、什么是垃圾回收機制
① 顯示內存管理(C/C++):內存管理是程序開發者的職責
常見問題:
野指針:使用了一個指針,但是該指針指向的內存空間已經被free
內存泄露:內存空間已經申請,使用完畢后為主動釋放,會一直占用內存
② 自動內存管理(Java/C#/一些腳本語言):內存空間由垃圾回收期自動管理
優點:增加了程序的可靠性,減小了內存泄露和野指針的情況,提高了程序員的效率
缺點:程序員無法控制GC的時間
判斷哪些內存需要回收需要耗費系統開銷
邏輯上的內存泄露依然會存在
2、GC的工作原理
找出不再使用的對象,進行回收,所有不再使用的對象都是垃圾
3、GC算法:
Ⅰ.引用計數法:
給堆中的每一個對象增加一個引用計數器,當每一次創建一個對象並賦值給一個變量時,引用計數器就加1;當對象不再使用時(出了作用域),引用計數器減1,當引用計數器為0,對象就滿足了垃圾回收的條件。
特點:實現簡單
缺點:循環引用的垃圾無法回收
Ⅱ.根搜索算法(Root Tracing):
在主流的商用程序處理語言中,都是使用根搜索算法來判斷隨想是否存活的。
該算法的基本思路就是通過一系列的名為GC Root的對象作為起點,從這些節點開始向下搜索,搜索所走的路徑稱為引用鏈(Reference chain)。當一個對象到GC Root之間沒有任何引用鏈相連(用圖論的話來說就是GC Roots到這些對象不可達)時,證明該對象是不可用的,GC程序即可回收這些對象
專業術語:
Shallow size:就是最像本身占用的內存大小,也就是對象頭加成員變量占用內存大小的總和
Retained size:是該對象自己的shallow size加上僅可以從該對象訪問(直接或間接訪問)的對象的shallow size之和。Retained size是該對象被GC之后所能回收的內存的總和
Ⅲ.標記清除算法(Mark Sweep Algorithm):
Dalvik使用標記清除算法實現垃圾回收
1、mark:標記出被引用的對象
2、sweep:清除哪些沒有任何引用的對象
所有引用默認標記為0,每清除掉一個Root時,系統將從其他Roots能到達的引用都標記為1,垃圾回收將標記為0的對象回收掉,然后所有的標記置位0,重新開始新一輪垃圾回收
4、GC觸發的時機
申請Heap space失敗后會觸發GC,但此時已晚
系統進入idle后一段時間會進行回收
主動調用System.gc();進行回收