1、Java虛拟機原理
所謂虛拟機,就是一台虛拟的機器。他是一款軟體,用來執行一系列虛拟計算指令,大體上虛拟機可以分為
系統虛拟機和程式虛拟機,大名鼎鼎的Visual Box、Vmare就屬于系統虛拟機,他們完全是對實體計算的仿真,
提供了一個可以運作完整作業系統的軟體平台。
程式虛拟機典型代碼就是Java虛拟機,它專門為執行單個計算程式而計算,在Java虛拟機中執行的指令我們成為Java
自己碼指令。無論是系統虛拟機還是程式虛拟機,在上面運作的軟體都被限制于虛拟機提供的資源中。
Java發展至今,出現過很多虛拟機,最初Sun使用的一款叫ClassIc的Java虛拟機,到現在引用最廣泛的是HotSpot虛拟
機,除了Sum以外,還有BEA的Jrockit,目前Jrockit和HostSopt都被oralce收入旗下,大有整合的趨勢。
2、Java記憶體結構

1、 類加載子系統:負責從檔案系統或者網絡加載Class資訊,加載的資訊存放在一塊稱之方法區的記憶體空間。
2、 方法區:就是存放類的資訊、常量資訊、常量池資訊、包括字元串字面量和數字常量等。
3、 Java堆:在Java虛拟機啟動的時候建立Java堆,它是Java程式最主要的記憶體工作區域,幾乎所有的對象執行個體都存放到
Java堆中,堆空間是所有線程共享。
4、 直接記憶體:JavaNio庫允許Java程式直接記憶體,進而提高性能,通常直接記憶體速度會優于Java堆。讀寫頻繁的場合可能會考慮使用。
5、 每個虛拟機線程都有一個私有棧,一個線程的Java棧線上程建立的時候被建立,Java棧儲存着局部變量、方法參數、Java的方法調用、傳回值等。
6、 本地方法棧,最大不同為本地方法棧用于本地方法調用。Java虛拟機允許Java直接調用本地方法(通過使用C語言寫)
7、 垃圾收集系統是Java的核心,也是不可少的,Java有一套自己進行垃圾清理的機制,開發人員無需手工清理,下一節課詳細講。
8、 PC(Program Counter)寄存器也是每個線程私有的空間, Java虛拟機會為每個線程建立PC寄存器,在任意時刻,
一個Java線程總是在執行一個方法,這個方法稱為目前方法,如果目前方法不是本地方法,PC寄存器總會執行目前正在被執行的指令,
如果是本地方法,則PC寄存器值為Underfined,寄存器存放如果目前執行環境指針、程式技術器、操作棧指針、計算的變量指針等資訊。
9、 虛拟機核心的元件就是執行引擎,它負責執行虛拟機的位元組碼,一般先進行編譯成機器碼後執行。
堆、棧、方法區概念差別
Java堆
堆記憶體用于存放由new建立的對象和數組。在堆中配置設定的記憶體,由java虛拟機自動垃圾回收器來管理。在堆中産生了一個數組或者對象後,還可以在棧中定義一個特殊的變量,這個變量的取值等于數組或者對象在堆記憶體中的首位址,在棧中的這個特殊的變量就變成了數組或者對象的引用變量,以後就可以在程式中使用棧記憶體中的引用變量來通路堆中的數組或者對象,引用變量相當于為數組或者對象起的一個别名,或者代号。
根據垃圾回收機制的不同,Java堆有可能擁有不同的結構,最為常見的就是将整個Java堆分為
新生代和老年代。其中新生帶存放新生的對象或者年齡不大的對象,老年代則存放老年對象。
新生代分為den區、s0區、s1區,s0和s1也被稱為from和to區域,他們是兩塊大小相等并且可以互相角色的空間。
絕大多數情況下,對象首先配置設定在eden區,在新生代回收後,如果對象還存活,則進入s0或s1區,之後每經過一次
新生代回收,如果對象存活則它的年齡就加1,對象達到一定的年齡後,則進入老年代。
Java棧
Java棧是一塊線程私有的空間,一個棧,一般由三部分組成:局部變量表、操作資料棧和幀資料區
局部變量表:用于報存函數的參數及局部變量
操作數棧:主要儲存計算過程的中間結果,同時作為計算過程中的變量臨時的存儲空間。
幀資料區:除了局部變量表和操作資料棧以外,棧還需要一些資料來支援常量池的解析,這裡幀資料區儲存着
通路常量池的指針,友善程式通路常量池,另外當函數傳回或出現異常時賣虛拟機子必須有一個異常處理表,友善發送異常
的時候找到異常的代碼,是以異常處理表也是幀資料區的一部分。
Java方法區
Java方法區和堆一樣,方法區是一塊所有線程共享的記憶體區域,他儲存系統的類資訊。比如類的字段、方法、常量池等。方法區的大小決定系統可以儲存多少個類。如果系統定義太多的類,導緻方法區溢出。虛拟機同樣抛出記憶體溢出的錯誤。方法區可以了解為永久區。
虛拟機參數配置
在虛拟機運作的過程中,如果可以跟蹤系統的運作狀态,那麼對于問題的故障排查會有一定的幫助,為此,在虛拟機提供了一些跟蹤系統狀态的參數,使用
給定的參數執行Java虛拟機,就可以在系統運作時列印相關日志,用于分析實際問題。我們進行虛拟機參數配置,其實就是圍繞着堆、棧、方法區、進行配置。
堆的參數配置
-XX:+PrintGC 每次觸發GC的時候列印相關日志
-XX:+UseSerialGC 串行回收
-XX:+PrintGCDetails 更詳細的GC日志
-Xms 堆初始值
-Xmx 堆最大可用值
-Xmn 新生代堆最大可用值
-XX:SurvivorRatio 用來設定新生代中eden空間和from/to空間的比例.
含以-XX:SurvivorRatio=eden/from=den/to
總結:在實際工作中,我們可以直接将初始的堆大小與最大堆大小相等,
這樣的好處是可以減少程式運作時垃圾回收次數,進而提高效率。
-XX:SurvivorRatio 用來設定新生代中eden空間和from/to空間的比例.
設定最大堆記憶體
參數: -Xms5m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags
-Xmn 新生代大小,一般設為整個堆的1/3到1/4左右
-XX:SurvivorRatio 設定新生代中eden區和from/to空間的比例關系n/1
參數: -Xms20m -Xmx20m -Xmn1m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC
-Xms20m -Xmx20m -XX:SurvivorRatio=2 -XX:+PrintGCDetails -XX:+UseSerialGC
-XX:NewRatio=2
總結:不同的堆分布情況,對系統執行會産生一定的影響,在實際工作中,
應該根據系統的特點做出合理的配置,基本政策:盡可能将對象預留在新生代,
減少老年代的GC次數。
除了可以設定新生代的絕對大小(-Xmn),可以使用(-XX:NewRatio)設定新生代和老年
代的比例:-XX:NewRatio=老年代/新生代
錯誤原因: java.lang.OutOfMemoryError: Java heap space
解決辦法:設定堆記憶體大小 -Xms1m -Xmx70m -XX:+HeapDumpOnOutOfMemoryError
錯誤原因: java.lang.StackOverflowError
棧溢出 産生于遞歸調用,循環周遊是不會的,但是循環方法裡面産生遞歸調用, 也會發生棧溢出。
解決辦法:設定線程最大調用深度
-Xss5m 設定最大調用深度
Tomcat記憶體溢出在catalina.sh 修改JVM堆記憶體大小
JAVA_OPTS="-server -Xms800m -Xmx800m -XX:PermSize=256m
-XX:MaxPermSize=512m -XX:MaxNewSize=512m"
在JVM啟動參數中,可以設定跟記憶體、垃圾回收相關的一些參數設定,預設情況不做任何設定JVM會工作的很好,但對一些配置很好的Server和具體的應用必須仔細調優才能獲得最佳性能。通過設定我們希望達到一些目标:
GC的時間足夠的小
GC的次數足夠的少
發生Full GC的周期足夠的長
前兩個目前是相悖的,要想GC時間小必須要一個更小的堆,要保證GC次數足夠少,必須保證一個更大的堆,我們隻能取其平衡。
(1)針對JVM堆的設定,一般可以通過-Xms -Xmx限定其最小、最大值,為了防止垃圾收集器在最小、最大之間收縮堆而産生額外的時間,我們通常把最大、最小設定為相同的值
(2)年輕代和年老代将根據預設的比例(1:2)配置設定堆記憶體,可以通過調整二者之間的比率NewRadio來調整二者之間的大小,也可以針對回收代,比如年輕代,通過 -XX:newSize -XX:MaxNewSize來設定其絕對大小。同樣,為了防止年輕代的堆收縮,我們通常會把-XX:newSize -XX:MaxNewSize設定為同樣大小
(3)年輕代和年老代設定多大才算合理?這個我問題毫無疑問是沒有答案的,否則也就不會有調優。我們觀察一下二者大小變化有哪些影響
更大的年輕代必然導緻更小的年老代,大的年輕代會延長普通GC的周期,但會增加每次GC的時間;小的年老代會導緻更頻繁的Full GC
更小的年輕代必然導緻更大年老代,小的年輕代會導緻普通GC很頻繁,但每次的GC時間會更短;大的年老代會減少Full GC的頻率
如何選擇應該依賴應用程式對象生命周期的分布情況:如果應用存在大量的臨時對象,應該選擇更大的年輕代;如果存在相對較多的持久對象,年老代應該适當增大。但很多應用都沒有這樣明顯的特性,在抉擇時應該根據以下兩點:(A)本着Full GC盡量少的原則,讓年老代盡量緩存常用對象,JVM的預設比例1:2也是這個道理
(B)通過觀察應用一段時間,看其他在峰值時年老代會占多少記憶體,在不影響Full GC的前提下,根據實際情況加大年輕代,比如可以把比例控制在1:1。但應該給年老代至少預留1/3的增長空間