JVM內存模型以及垃圾回收算法的基本認識

 

在Java語言中,採用的是共享內存模型來實現多線程之間的信息交換和數據同步的;如下圖所示

 

 

程序計數器:字節碼的行號指示器。

作用:

  1. 字節碼解釋器通過改變程序計數器來依次讀取指令,從而實現代碼的流程控制,如:順序執行、選擇、循環、異常處理。
  2. 在多線程的情況下,程序計數器用於記錄當前線程執行的位置,從而當線程被切換回來的時候能夠知道該線程上次運行到哪兒了。

程序計數器是唯一一個不會出現 OutOfMemoryError 的內存區域,它的生命週期隨着線程的創建而創建,隨着線程的結束而死亡。

 

虛擬機棧

私有,生命週期=線程;java執行的內存模型,每次方法調用的數據都是通過棧傳遞的

組成:由一個個棧幀組成,每個棧幀有:局部變量表、操作數棧、動態鏈接、方法出入口信息

局部變量表:存放各種數據類型、對象引用

 

本地方法棧

和虛擬機棧相似,區別:虛擬機棧爲虛擬機執行方法服務,本地方法棧爲虛擬機使用到的Native方法服務

 

內存中最大的一塊,虛擬機啓動時創建

目的:存放對象實例,數組

垃圾回收的主要區域

 

方法區,存放

1.類的信息(名稱、修飾符等)、

2.類中的靜態常量、

3.類中定義爲final類型的常量、

4.類中的Field信息、

5.類中的方法信息

分爲常量池

字符串、final變量

類和接口名,字段名,描述符,方法名

靜態變量

父類靜態成員變量,子類靜態成員變量,父類構造方法,子類構造方法

 

元空間:類信息;編譯代碼

 

主內存和工作內存

JVM規定了所有的變量都存儲在主內存(Main Memory)中。每個線程還有自己的工作內存(Working Memory),線程的工作內存中保存了該線程使用到的變量的主內存的副本拷貝,線程對變量的所有操作(讀取、賦值等)都必須在工作內存中進行,而不能直接讀寫主內存中的變量(volatile變量仍然有工作內存的拷貝,但是由於它特殊的操作順序性規定,所以看起來如同直接在主內存中讀寫訪問一般)。不同的線程之間也無法直接訪問對方工作內存中的變量,線程之間值的傳遞都需要通過主內存來完成

那麼兩個線程之間如何能夠達到可見性,可以參考文章volatile的使用:https://blog.csdn.net/Goligory/article/details/89648177

 

內存溢出原因:

垃圾過多jvm沒有及時回收

1.引用變量過多使用static

2.大量的遞歸和無限遞歸(遞歸中新建對象)

3.大量循環(循環中新建對象)/ 死循環產生過多實體

4.數據庫查詢一次性查所有記錄超過10萬條可能造成溢出

5.String 是否使用過多+的操作

啓動參數內存值設定過小

 

棧溢出原因:

1.是否遞歸調用,遞歸調用方法

2.大量循環/死循環

3.全局變量是否過多

4.數組,list,map是否過大

堆溢出:遞歸new對象

 

內存泄漏

申請內存後,無法釋放已申請的內存空間,內存泄漏堆積後果很嚴重

1.常發性內存泄漏:多次執行

2.偶發性內存泄漏:

3.一次性內存泄漏:只執行一次,如構造函數中分配內存沒有釋放

4.隱式內存泄漏:程序不停分配內存,結束後才釋放內存,嚴格說沒有泄漏,但是如果不及時釋放也可能導致耗盡所有內存

泄漏原因

長生命週期對象持有短生命週期對象導致持有其引用不能回收,發生內存泄漏

1.靜態集合類引起內存泄漏:如HashMap,vector

2.監聽器,釋放對象時沒有刪除

3.各種鏈接沒有close

4.單例模式持有外部對象引用

 

 

============GC垃圾回收=================================================================

垃圾回收我們要熟悉,我們要知道它是如何工作的,爲什麼這麼工作,好處是什麼?

堆是GC的主要區域

jdk1.8之後永久代改成了元空間,元空間使用的是直接內存

 

JVM如何確定哪些對象應該進行回收?

兩種方式

1.引用計數法:給對象加計數器,用時+1,不用-1,但是無法解決對象之間循環引用的問題,java沒用

2.可達性分析算法:

通過一些被稱爲引用鏈GC Roots的對象作爲起點,從這些節點開始向下搜索,走過的路徑被成爲Reference Chain,當一個對象到GC Roots沒有任何引用鏈相連時(即從GC Roots到該節點不可達),證明該對象是不可用的

可作爲GC Root的對象包括一下幾種:

  • 虛擬機棧中引用的對象
  • 方法區類靜態屬性引用的對象
  • 方法區中常量引用對象

知道了需要回收的對象,那什麼時候回收?怎麼回收?


什麼時候進行回收?

cpu空閒時自動回收

堆內存滿了後

主動調用System.gc()

 

3大回收算法

1.標記/清除算法【最基礎】
2.複製算法
3.標記/整理算法
jvm採用`分代收集算法`對不同區域採用不同的回收算法。

 

  • 標記-清除算法:先標記,後清除,標記所有需要回收的對象,後統一回收帶有標記得對象,簡單,但是缺乏效率,空間問題,標記清除後產生大量不連續的內存碎片,當程序運行時需要分配較大對象時無法找到足夠的連續內存而造成空間浪費
  • 標記-整理算法:和標記-清除算法類似,區別在於標記-清除對於剩下的對象不進行操作造成內存碎片,標記-整理在清楚後把剩下的對象進行整理,不會產生內存碎片

 

新生代:複製算法

Eden,From Survivor,To Survivor

java對象一般在新生代出生,新生代也是GC的頻繁區域。深入理解JVM虛擬機上說98%的對象存活率很低,適用於複製算法

它優化了標記/清除算法的效率和內存碎片問題,且JVM不以5:5分配內存【由於存活率低,不需要複製保留那麼大的區域造成空間上的浪費,因此不需要按1:1【原有區域:保留空間】劃分內存區域,而是將內存分爲一塊Eden空間和From Survivor、To Survivor【保留空間】,三者默認比例爲8:1:1

目標儘快收集掉生命週期短的對象,一般情況Eden區中新生成的對象首先放在新生代。垃圾回收時,先將eden區存活的對象複製到survivor0區,然後清空eden區,當survivor0區滿了時,將eden區和survivor0區存活對象複製到survivor1區,然後清空eden和survivor0區,交換survivor0區和survivor1區角色,下次垃圾回收時會掃描eden區和survivor1區,如此往復。

如果當survivor1區也不足以放eden區和survivor0區的存活對象,就將存活對象直接存放到老年代。如果老年代也滿了,就會觸發一次FullGC,也就是新生代、老年代都回收。

新生代發生的GC也叫MinorGC,MinorGC發生頻率比較高,不一定等eden區滿了才觸發

 

老年代:標記-清除;標記-整理

  • 堆大小:新生代+老年代。堆大小可根據-xms(堆初始容量),-xmx(堆最大容量)來指定
  • 新生代默認Eden:from:to=8:1:1
  • jvm每次只跟Eden和其中一塊Survivor區域爲對象服務,無論什麼時候,總有一塊Survivor是閒着的
  • 新生代實際可用的內存空間爲9/10的新生代空間

老年代存放的都是生命週期較長的對象,在新生代中經歷了n次垃圾回收後仍然存活的對象就會被放到老年代中

 

永久代

主要存放靜態文件,如java類,方法等。永久代對垃圾回收沒有顯著影響。但是反射,動態代理,CGLib等可能動態生成class文件,這時需要設置比較大的永久代空間存放

大對象、長期存活的對象進入老年代

 

注意:jdk1.8廢棄了永久代,提供了相似的元空間技術

去永久代的原因有: 

(1)字符串存在永久代中,容易出現性能問題和內存溢出。 

(2)類及方法的信息等比較難確定其大小,因此對於永久代的大小指定比較困難,太小容易出現永久代溢出,太大則容易導致老年代溢出。 

(3)永久代會爲 GC 帶來不必要的複雜度,並且回收效率偏低

 

JVM參數

堆內存最小和最大指定

-Xms2G -Xmx5G

 

將新對象預留在新生代,由於 Full GC 的成本遠高於 Minor GC,因此儘可能將對象分配在新生代是明智的做法,實際項目中根據 GC 日誌分析新生代空間大小分配是否合理,適當通過“-Xmn”命令調節新生代大小,最大限度降低新對象直接進入老年代的情況。

 

顯式新生代內存  兩種指定方式

1.-XX:newSize=256m

-XX:MaxNewSize=1024m

2.-Xmn256m

 

顯示指定元空間大小

jdk8要指定metabase大小,因爲使用直接內存,虛擬機可能會耗盡系統內存

 

垃圾回收器

1.串行垃圾回收器

2.並行垃圾回收器

3.CMS垃圾回收器

4.G1垃圾回收器

堆參數

 

回收器參數

 

常用命令

 

GC調優原則

多數的 Java 應用不需要在服務器上進行 GC 優化; 多數導致 GC 問題的 Java 應用,都不是因爲我們參數設置錯誤,而是代碼問題; 在應用上線之前,先考慮將機器的 JVM 參數設置到最優(最適合); 減少創建對象的數量; 減少使用全局變量和大對象(佔用連續內存空間); GC 優化是到最後不得已才採用的手段; 在實際使用中,分析 GC 情況優化代碼比優化 GC 參數要多得多

 

調優策略

1.新對象預留在新生代,通過-Xmn調價新生代大小,最大限度降低新對象直接進入老年代的情況

2.大對象進入老年代

3.合理設置老年代對象的年齡-XX:MaxTenuringThreshold

4.設置穩定的堆大小

5.注意:滿足下面說法則不需要GC優化

MinorGC 執行時間不到50ms; Minor GC 執行不頻繁,約10秒一次; Full GC 執行時間不到1s; Full GC 執行頻率不算頻繁,不低於10分鐘1次

 

 

 

 

引用:https://www.cnblogs.com/shenjianjun/p/9512949.html

           GC https://www.cnblogs.com/ITPower/p/7929010.html

這個引用蠻全的推薦:https://www.jianshu.com/p/76959115d486

發佈了49 篇原創文章 · 獲贊 44 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章