一.內存模型
堆 :存放實例對象,堆區分爲老年代和新生代;
棧: 存儲局部變量,堆中對象的引用, 棧分爲java虛擬棧,和本地方法棧:存儲jnative的服務;每個方法的執行都對應者一個棧幀入棧到出棧。
方法區: 存放類的信息,常量池,編譯後的字節碼。靜態常量;
程序計數器: 存放程序運行的位置。
二 堆區
堆區分爲新生代和老年代,新生代又分爲 Eden fromsurvivor toSurvivor,8:1:1,
每當新生去滿了的時候,就會觸發YGC, 先將存活的對象都放入to survivor中,然後把eden和另一個fromsurivior進行清空;等下一次進行垃圾清理的時候,就會使用另一個survvor,以此循環利用;
當做每次存放到to中的時候,age就會加1,等加到指定的闕值的時候就會放入到老年代,from 和to是相對的;(複製算法)
老年區中存放的是時間較久對象或者較大的對象,當jvm內存滿了的時候。老年區也進行觸發fullgc。
三 對象創建的過程
1.當jvm 讀取一個 new 的時候,會先去常量池中看下是否存在該類的符號引用,並檢測是否已經被加載解析初始化,如果沒有進行累的加載;
2.加載完之後,爲其分配內存;在堆中分配有兩種方法
①指針碰撞
當java堆區的內存是規整的,使用過的內存放在一邊,空閒內存的放另一邊,中間是指針分界點,只要把指針像空閒內存移動與對象大小相等的距離;
② 空閒列表
當java堆區的內存是不規整的,虛擬機把可用內存維護在一個表上,在表上找到一個大與對象小相等的內存空間分配,並更新表。
防止併發:
①採用cas,保證原子操作的原子性;
②把內存分配按照線程放在不同的空間,爲每個線程在堆中提前分配一小塊內存,稱爲本地線程緩存分配,Thread Local Allocation Buffer,TLAB;只要當TLAB的內存進行重新分配時才加鎖。
3.在分配好內存之後,需要對內存進行初始化;
對象頭:
將對象的類 哈希碼 gc分代年齡等信息 設置到對象頭中。
執行init方法。
內存佈局:
對象頭: 分爲2部分,存儲自身信息: 哈希碼,gc年代分領等;另一部分 是類型指針,確定是屬於哪個類;
實例對象: 存儲對象中各個字段的內容
對齊填充: 可有可無,當實例對象沒有的時候進行對齊填充。
4 對象的訪問定位
對java堆中的對象操作,需要的是棧中的對象引用變量,reference。而具體的操作方式有兩種:使用句柄和直接指針;
使用句柄:
在java堆中畫出一塊地方 叫做句柄,存放的是對象實例數據的地址,類型數據的地址。而reference 存儲的是句柄的地址;
優點: 對象移動只需要修改句柄池中的數據,不需要修改reference;
直接指針 :
直接指向的是對象的地址。優點 就是快;
四 GC判定垃圾對象
1.根搜索法
以GCRoot對象爲起點,所有可達的就是活對象,對於無法到達的則進行gc回收;
可以作爲gcRoot的對象的有:
虛擬機棧中引用的對象;本地棧中引用對象;方法區中常量池引用的對象;方法區靜態屬性引用的對象;
但是當沒某個對象無法到達時,不會立即回收 而是進行標記一次;然後看該對象是否該執行finalize();如果需要執行則把該對象放入到F-queue隊列當中,此時虛擬機會觸發線程執行finalize()方法,然後將隊列中的對象進行二次標記,回收。
2.引用計數法
對每個對象設置一個引用計數器,每當被引用就+1,失效就減1,一直到0的時候,進行回收。
缺點是 :循環引用,A引用B,B引用A;
五. GC的收集方法
標記-清除算法:
原理: 先進行標記,然後清除。
缺點: 效率低,內存碎片化嚴重,當需要較大完整內存時,有可能因爲內存碎片化無法提供,而提前fullgc
使用: 適用於老年代垃圾回收,CMS收集器就是採用該算法進行回收的。
標記-整理算法:
原理: 首先進行標記,然後讓存活的對象統一移動到一邊,然後清除端界以外的地方。
特點 :不產生碎片但是花費一定時間
使用: 適用與老年代的垃圾回收。parallel Old(針對parallel scanvange gc的) gc和Serial old收集器就
複製算法:
原理 :按容量劃分內存,單獨拿出一部分內存,當其他內存用完了,就將存活的對象放在該內存中,然後把使用過的內存進行清理掉,下一次在把當前內存存活對象存入另一邊,反覆循環使用。
特點: 犧牲一部分空間,移動堆頂指針
使用: 新生代,按照8:1:1的比例,將存回的對象放入其中一個sutvivor內存中,然後清除。缺點是,當survivor內存過小放不下時,一部分會直接進入老年代。
分代收集算法 :
將內存區域分爲幾個部分,新生代中的對象大都生命週期較短,所以採用複製算法,老年代中存活時間較長所以採用標記-整理的算法。
六 什麼時候觸發MinorGC,?什麼時候觸發FullGC?
新生代不夠用的時候觸發ygc,jvm不夠用的時候觸發FullGc;
老年代空間不足 發生fullgc。
通過Minor GC後進入老年代的平均大小大於老年代的可用內存
由Eden區、From Space區向To Space區複製時,對象大小大於To Space可用內存,則把該對象轉存到老年代,且老年代的可用內存小於該對象大小
方法區內存不足。
七.類加載過程
1.加載
①將根據類的權限名獲取二進制字節流。
②將二進制字節流中的所代表的的靜態存儲結構轉化方法區中運行時的數據結構。
③在堆區生成一個class對象,作爲方法區這些數據的訪問入口。
2.驗證
確保class文件的字節流信息符合當前虛擬機的要求。
①文件格式驗證
確保class文件能夠正確的解析並存儲到方法區中。經過這個驗證,字節流纔會進入內存的方法區中。
②元數據驗證
對字節碼描述的信息進行語義分析,保證符合java語言規範。
③字節碼驗證
是對校驗類的方法的驗證
④符號引用驗證
這個過程發生在虛擬機將符號引用轉化爲直接引用的時候,確保符號引用的正確性。
3準備
爲靜態變量分配內存並且附上默認初始值。
4.解析
將常量池中的符號引用替換爲直接引用。比如A類中的b方法,對於我們來說,b只是一個符號,因此調用b需要的是指針指向b的直接內存地址。這就是直接引用。
5.初始化
爲靜態變量附上正確的值。
八 類加載器
類加載器就是根據class的權限名獲取二進制流文件。
類加載器:
啓動加載器 :加載核心的java類庫
擴展類加載器: java提供一個擴展類目錄,加載目錄下的類
系統類加載器: 加載具體的應用類
自定義加載器
雙親委派機制:
在系統類加載器進行加載的時候,會首先查看一下是否已經加載過了,如果沒有加載過, 訪問父類,看父類是否能夠加載,父類在在繼續委派父類的父類,如果父類無法加載,那麼纔會最開始的類去加載。
爲何使用:
避免出現重複即同樣的字節碼,假如不使用雙親委派機制,各自使用各自的類加載器,無法保證類的唯一性。
如何自定義類加載器:
繼承classLoader,重寫findclass;因爲在父加載器無法加載類的時候,就會調用我們自定義的類加載器中的findeClass
函數。
如果沒有指定, 那麼就默認系統類加載器是其父類。
九GC策略
新生代策略:
串行GC: 單線程 serial_copying 優點:效率較高,不必考慮線程問題。
並行gc: 多線程 parallel scavenge 適用於多cpu上,對暫停時間要求較短。
並行gc : 結合老年代的cmsGc
老年代策略:
CMSGC:使用標記清除算法 ,犧牲吞吐量換區最短時間響應,對於服務器響應速度要求高的適用。
缺點:產生碎片,搶佔cpu和應用線程,
優點: 只有第一次標記和重新標記的時候纔會暫停整個應用,這對應用程序影響較小。
paraller mark sweep 標記壓縮算法,把老年代分爲若干個子區域,多個線程同時對每個子區域進行活躍對象標記,然後清除不活躍對象,把活躍對象整合在一起。
優點: 縮短了暫停時間,但是老年區的空間較大,標記花費時間較多。