JVM學習

強烈建議大家去網上下載《深入理解JAVA虛擬機》一書,基本上網上的JVM、內存等相關的博客都出自該書 !

參考資料:

java內存分配回收機制 : http://www.cnblogs.com/hnrainll/archive/2013/11/06/3410042.html

jvm類加載機制:http://blog.csdn.net/a19881029/article/details/17068191 ; https://my.oschina.net/xianggao/blog/87735

jvm啓動參數設置詳解:http://iamzhongyong.iteye.com/blog/1333100

classload類加載:http://www.cnblogs.com/ChrisWang/archive/2009/11/17/Inside-JVM-4-ClassLoader-Knowledge-Sharing.html ;http://www.cnblogs.com/loritin/p/5347979.html



java內存劃分 :

1、堆區 : 存儲類實例化對象,VM內存第一大塊 。 GC回收

2、方法區:通常也規劃在堆區,不過是永久代,存儲的是類對象、常量池、靜態變量等。 GC回收

3、java運行棧:存儲方法的局部變量表,方法執行的出入口,便於JVM進行調度。 方法執行完,自動回收內存

4、本地方法棧:存儲本地方法的局部變量表,本地方法的出入口,便於JVM調用native方法,方法執行完,自動回收內存

5、程序計數器:記錄線程執行到了那個代碼行 。線程執行完,自動回收內存

6、直接內存:計算機除去JVM的內存外的其他區域,包括了native方法的本地庫 GC回收,且是FULL-GC的時候順便回收,不在GC回收的範疇


GC回收

1、GC回收分爲新生代、老年代、永久代(比例基本1:8:1) ; 而新生代又分爲eden、survive0、survive1區(比例基本8:1:1),

2、當新建一個對象的時候,對象的引用放入java運行棧,對象本身會放入到堆中。 初創建的對象都放在堆的eden區中,當eden區滿時候,就會eden執行一次垃圾回收,稱爲minor GC,將其中不可達的對象清除掉,剩餘下來的就會存放到survive0區中 。

3、循環執行2中過程,如果某次minor GC,發現survive0區也滿了的時候,就會將eden區與survive0區中的存活對象都複製到survive1中,survive0區清空,eden區清空。 後續執行minor GC,會將eden區中的存活對象放到survive1中

4、如果某次minor GC,發現survive1區也滿了的時候,就會將eden區與survive1區中的存活對象都複製到survive0中,survive1區清空,eden區清空。 後續執行minor GC,會將eden區中的存活對象再放到survive0中

5、對象如此在爲eden、survive0、survive1區中進行切換 。 當survive0、survive1切換多次(Hotspot虛擬機默認是15次)後,會將依舊存活的對象複製到老年代

 6、當老年代滿了或者達到設置的full GC的時候,就會進行老年代full Gc 。 由於老年代內存空間大,它滿了證明內存對象很多,那麼Full Gc執行時間會很長。


問題:

1、如何判斷哪些對象是可以被回收的。

     從GC-Root向上尋找,只要相關聯的都記錄爲存活的,沒有關聯的就刪除 。GC-Root有且僅有:常量池,靜態變量,虛擬棧與本地棧中的本地變量表中引用的對象

2、年輕代回收算法

     年輕代使用複製回收算法,因爲98%的情況下每一次monitor-gc 都會回收89%的對象,只會有少量的對象存活,所以每次都是把存活的對象賦值到空的survivor區,然後清空eden和另外的survivor區。 不存在執行GC的時候還允許其他工作線程執行。 stop the word !  如果存活的對象大於survior的量,那麼會直接丟到老年代;

3、說說人們常說的CMS回收算法

     CMS是針對老年代的回收算法,只能有年輕的複製-回收算法共同合作,與以吞吐量爲主的年輕代Paraller Scavenge算法不能共存。它是3次標記回收,而且允許回收的同時工作線程也在繼續跑。 當然,在進行標記的時候,也會暫停其他所有的工作線程。 由於它在進行回收的時候,其他工作線程會繼續創造新的對象,因此一般內存使用達到60%就會開始回收工作了。最開始使用的是標記-刪除算法,當執行多次,內存全是碎片,空間分配不出一個大的整體碎片給一個大對象的時候,也會觸發GC,這時採用標記-整理-刪除算法。

4、如何確定GC-Root

     很顯然,如果每次GC都會掃描虛擬棧與本地棧,特別是當有大量方法並存執行的時候,GC會特別慢。 於是,GC會有一個對象oopMap記錄棧中引用的對象。當然,並不是每次方法執行、完成都會更新oopMap對象,這會造成大量的開銷。 而且並不是在任何時候都利於把工作線程停下來,我們稱適當的時候爲安全點,指標爲“線程指令能長時間的運行,方便復原”,一般爲循環點,異常點,方法調用點  。 那麼如何讓所有的線程同時都到達安全點呢? 實際上並不是同時到達,而是系統將要GC的時候會將GC標誌位置爲1,每次線程執行到安全點的時候就會去輪詢GC標誌位,如果爲1就自己暫停。當所有線程暫停後就開始進行GC了。



JVM 啓動與類加載

JVM的啓動大致有3個方式,

一個是.class單個文件,其中有main() 方法,使用java xxxx.class ;

加載過程: 找到jre----------->找到jvm.dll---------------->啓動JVM並進行初始化---------->產生bootstrp classloader -------->載入extClassLoader ---------->載入appClassLoader------>運行xxx.class的main()方法

一個是可運行jar包,執行 java -tar xxxx.jar 。

加載過程:找到jre------------>找到jvm.dll----------->啓動jvm並進行初始化---------->產生bootstrp classloader -------->載入extClassLoader ---------->載入appClassLoader------>運行可運行文件清單中的MANIFEST.MF 文件中的Main-Class中的main()方法 (下爲一個清單例子)

Manifest-Version: 1.0
Class-Path: . LottChartStBox_lib/aopalliance-1.0.jar LottChartStBox_li
 b/aspectjweaver.jar LottChartStBox_lib/commons-codec-1.10.jar LottCha
 rtStBox_lib/commons-dbcp-1.4.jar LottChartStBox_lib/commons-io-1.3.2.
 jar LottChartStBox_lib/commons-lang-2.6.jar LottChartStBox_lib/common
 s-lang.jar LottChartStBox_lib/commons-lang3-3.1.jar LottChartStBox_li
 b/commons-logging-1.1.jar LottChartStBox_lib/commons-pool-1.5.4.jar L
 ottChartStBox_lib/jackson-annotations-2.7.0.jar LottChartStBox_lib/ja
 ckson-core-2.7.0.jar
Main-Class: cn.com.sinodata.lottChart.Engine

一個是tomcat部署,過程同一,不過對應的啓動class是tomcat自己包中的Bootstrap.java中的main()方法




如上圖所示,JVM開啓,產生C語言底層寫的Bootstrp Loader,然後再載入java中的各個loader 。

載入類加載器後,開始執行主類的main()方法 ,其中的類加載過程稱爲 動態類加載(參考 http://www.ibm.com/developerworks/cn/java/j-dyn0429/)

其中按需加載到某個類的過程可以分爲    加載、驗證、準備、解析、初始化、使用、卸載  7個步驟

加載 : 獲取定義此類的二進制字節流(loader.getResource()),並將這個字節流所代表的靜態存儲結構轉化爲方法區的運行時數據結構,最後在Java方法區中生成一個代表這個類的java.lang.Class對象作爲方法區這些數據的訪問入口

驗證:驗證對應的.class文件,是否都是本JVM能夠識別並且不會影響JVM運行的(避免他人直接手編寫的class文件、其他JVM拷貝文件等等),主要驗證過程包括:文件格式驗證,元數據驗證,字節碼驗證以及符號引用驗證。

準備:爲(加載階段中的)靜態對象(static 修飾)分配內存並賦值默認值,如int ,賦值0 ,Object 賦值null。如果被static修飾同時被final修飾,那麼直接賦值目標值。

解析:將字符引用解析爲直接引用  (解析時虛擬機將常量池中的符號引用替換爲直接引用的過程。)

初始化:爲準備階段中的靜態對象賦值目標值,並分配目標值所需內存空間 。 靜態對象的賦值 和 靜態代碼塊是按照寫的順序執行的!如果靜態代碼塊在賦值前面,那麼輸出一個靜態對象的值的時候,是系統默認值,準備階段賦值的


例子:運行如下代碼的主類 。 可以在run configuration 中的參數配置中寫上 : -verbose class ,可以看各個類的加載順序

public class Engine {

	static {
		try {
			System.setProperty("log4j.configuration", ClassLoader.getSystemResource("config/log4j.xml").toURI().toString());
		} catch (URISyntaxException e) {
			e.printStackTrace();
		}
	}

	public static Browser browser;
	public static int mainStep = 0;

	public static Browser getBrowser() {
		return browser;
	}

	/**
	 * 主線程
	 * 0、清除本地數據
	 * 1、加載系統資源文件
	 * 2、終端電視機顯示緩衝loading頁面
	 * 2、同步投注機時間
	 * 3、加載log4j配置文件
	 * 4、取投注機站點機號、服務號、版本號
	 * 5、從中心同步同步站點信息、玩法、開獎號碼、兌獎期等信息
	 * 6、終端電視機顯示默認頁面
	 * 7、啓動實時獲取站點快開遊戲數據/補同步中心遺漏數據 線程
	 * 8、啓動實時監控終端文件的下載進度 線程
	 * @param args
	 */
	@SuppressWarnings("resource")
	public static void main(String args[]) {
                System.out.println(1);
               browser = new Browser(PublicParams.LOADING_PAGE); //優先加載,防止彈窗時候JS未加載完報錯
		new ClassPathXmlApplicationContext("config/applicationContext.xml");
        }

解析運行步驟

1、加載Engine類,並初始化engine類 ,則會先執行代碼塊,然後嘗試去加載Browser類,但是不會初始化Browser類 (Browser中的依賴類也會順勢加載)

2、運行到main()方法,輸出1,然後去初始化Browser,並且在構建一個Browser對象放到堆中 。 由於Browser的生成需要調用PublicParams的靜態變量,那麼會初始化Browser後,再初始化PublicParams,然後去構建Browser對象

3、執行Spring中的配置文件加載。


JVM的classLoader執行加載過程:摘自資料 http://www.cnblogs.com/loritin/p/5347979.html

Java ClassLoader加載機制

一.體系結構(自上向下) 

1.Bootstrap ClassLoader(BootStrapClassLoader) --- 啓動類加載器或者叫引導類加載器,加載jdk核心的APIs,這些APIs一般位於jdk_home/lib下;它是一個本地接口,所以不能從java代碼中得到它的信息。例如, log(java.lang.String.class.getClassLoader())得到的是null。

2.Extension ClassLoader(Launcher$ExtClassLoader) --- 擴展類加載器, 負責加載jdk_home/lib/ext目錄下的API;

3.System ClassLoader(Launcher$AppClassLoader) --- 系統類加載器  主要負責java -classpath所指的目錄下的APIs。

4.Custom ClassLoader --- 用戶自定義類加載器,是java.lang.ClassLoader的子類),負責在程序運行期間, 通過java.lang.ClassLoader的子類動態加載class文件。

 

二.加載機制

Java的ClassLoader是以父子結構展現的。當有請求向加載器請求類時,首先自底向上去尋找是否已經加載了所需的類,如果存在就返回這個類;如果不存在,就委託當前加載器的父加載器去完成加載類的請求,如果父加載器還有上一級,就向上一級的記載器請求去加載所需類,直到頂層,若頂層加載器(BootStrapClassLoader)無法加載類,就回到下一級,如果下一級仍無法加載,再下一級,直到當前的類加載器,如果仍加載不到,就會拋出ClassNotFoundExceptio。 注意:這裏的父子關係不是java中的繼承關係,可以理解爲一種委託關係。

 

三.類的唯一標識

JVM中,類的唯一標識是(類名,包名,加載器),由不同加載器加載的類,即使具有相同的類名和包名,也不會被認爲是同一個實例,不是類型可比型的。例如:ClassLoader A 有兩個子加載器ClassLoader B 和 ClassLoader C,類Foo對ClassLoaderB和ClassLoaderC是可見的,對ClassLoaderA是不可見的,當有請求向ClassLoaderB和ClassLoaderC請求Foo的實例時,由於兩個加載器會分別加載Foo,這樣JVM會產生兩個Foo的實例(foo1,foo2),而且它們是不可比的, 語句foo1=(Foo)foo2會拋出ClassCastException;當然,如果Foo對ClassLoaderA是可見的,Foo就會由ClassLoaderA加載,就不會產生ClassCastException了。


JVM參數配置參考   摘自(http://www.cnblogs.com/edwardlauxh/archive/2010/04/25/1918603.html)

典型設置:

  • java -Xmx3550m -Xms3550m -Xmn2g -Xss128k
    -Xmx3550m
    :設置JVM最大可用內存爲3550M。
    -Xms3550m:設置JVM促使內存爲3550m。此值可以設置與-Xmx相同,以避免每次垃圾回收完成後JVM重新分配內存。
    -Xmn2g:設置年輕代大小爲2G。整個堆大小=年輕代大小 + 年老代大小 + 持久代大小。持久代一般固定大小爲64m,所以增大年輕代後,將會減小年老代大小。此值對系統性能影響較大,Sun官方推薦配置爲整個堆的3/8。
    -Xss128k:設置每個線程的堆棧大小。JDK5.0以後每個線程堆棧大小爲1M,以前每個線程堆棧大小爲256K。更具應用的線程所需內存大小進行調整。在相同物理內存下,減小這個值能生成更多的線程。但是操作系統對一個進程內的線程數還是有限制的,不能無限生成,經驗值在3000~5000左右。
  • java -Xmx3550m -Xms3550m -Xss128k -XX:NewRatio=4 -XX:SurvivorRatio=4 -XX:MaxPermSize=16m -XX:MaxTenuringThreshold=0
    -XX:NewRatio=4
    :設置年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代)。設置爲4,則年輕代與年老代所佔比值爲1:4,年輕代佔整個堆棧的1/5
    -XX:SurvivorRatio=4:設置年輕代中Eden區與Survivor區的大小比值。設置爲4,則兩個Survivor區與一個Eden區的比值爲2:4,一個Survivor區佔整個年輕代的1/6
    -XX:MaxPermSize=16m:設置持久代大小爲16m。
    -XX:MaxTenuringThreshold=0:設置垃圾最大年齡。如果設置爲0的話,則年輕代對象不經過Survivor區,直接進入年老代。對於年老代比較多的應用,可以提高效率。如果將此值設置爲一個較大值,則年輕代對象會在Survivor區進行多次複製,這樣可以增加對象再年輕代的存活時間,增加在年輕代即被回收的概論。











發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章