JVM內存區域與垃圾回收

JVM內存區域與垃圾回收

 

 

 


1、JAVA內存區域與內存溢出

1.1、概述

Java中JVM提供了內存管理機制,Java虛擬機在執行Java程序的過程中會把內分分爲不同的數據區,如圖:

JVM內存區域.png

 

 

1.2、程序計數器

程序計數器是當前線程所執行的字節碼的行號指示器,作用就是根據計數器的值獲取下一條要執行的字節碼指令。當執行的是java方法,則記錄的是正在執行的虛擬機字節碼指令的地址,如果是Native方法,則這個計數器的值爲空。不存在任務OutOfMemoryError。

1.3、虛擬機棧

每個普通Java方法(除去Native方法)在執行的時候都會同時創建棧幀,用於存儲局部變量表、操作棧、動態鏈接、方法出口等信息,每個方法被調用直到完成的過程對應着棧幀在JVM棧中的入棧與出棧。其中局部變量表所需要的內存空間在編譯器間完成分配。

跟虛擬機棧相關聯的異常有兩種:

  • StackOverflowError

線程請求的棧深度大於虛擬機允許的最大深度。

  • OutOfMemoryError

虛擬機棧擴展時無法申請到足夠的內存。

1.4、本地方法棧

用於虛擬機執行Native方法,其他和本地方法棧相同。也會有StackOverflowError和OutOfMemoryError。

1.5、堆

虛擬機啓動後創建堆,用於存放對象實例。堆時垃圾回收器的主要工作區域,主要分爲新生代和老年代,新生代又可以細分爲Eden空間、From Survivor空間、To Survivor空間。java程序啓動時,可用-Xmx與-Xms控制堆的大小。如果堆中沒有內存完成實例分配並且堆也無法擴展時會拋出OutOfMemoryError。

1.6、方法區

方法區主要存儲類的元數據,如虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼,JDK1.8之前以永久代實現,JDK1.8之後使用元空間,而且元空間使用的是系統內存。如果無法申請內存時,會拋出OutOfMemoryError。

2、垃圾回收

2.1、如何判斷對象已經死亡?

2.1.1、引用計數法

在對象中添加一個引用計數器,當有個地方引用時,計數器值 1,當引用失效時,計數器值-1。計數器爲0的對象就是死亡的,但是這裏有個問題:對象循環引用,兩個對象互相引用着對方,導致它們的引用計數器不爲0,於是無法通知GC回收。

2.1.2、GC Roots搜索

Java中採用的是GC ROOT搜索,思路就是通過一系列名爲“GC Roots”的對象作爲起點,從這些節點開始向下搜索,搜索中所走過的路徑稱爲引用鏈,當GC Roots對某一對象不可達時,則證明此對象不可用。

GC Roots包括以下幾種:

  • 棧幀中的本地變量表中引用的對象
  • 方法區中類靜態屬性引用的對象
  • 方法區中常量引用的對象
  • 本地方法棧Native方法引用的對象

2.2、垃圾收集算法

2.2.1、標記-清除算法

標記-清除算法分爲兩個階段:

  • 標記:首先標記出所有需要回收的對象。
  • 清除:統一回收被標記的對象。

 

標記-清除.png

 

 

這個算法有連個缺點:

  1. 效率不高
  2. 會產生不連續的內存碎片

2.2.2、複製算法

複製算法的效率很高,其將可用內存按照容量劃分爲大小相等的兩塊,每次只使用其中的一塊,當一塊用完時,就將還存活的對象複製到另一塊上面,然後清除掉使用過的內存空間。

 

複製算法.png

 

 

這種算法很適合回收新生代,在新生代中分爲Eden空間、From Survivor空間、To Survivor空間,一般分配的內存比例爲8:1:1,當回收時,將Eden與From Survivor中還存活的對象一次性拷貝到To Survivor中,之後清理掉Eden與From Servivor空間,當To Survivor空間不夠時,需要依賴老年代。

2.2.3、標記-整理算法

在老年代,對象的存活率比較高,所以標記-整理算法被提出來了,首先標記出要回收的對象,然後將所有存活的對象都向一端移動,然後直接清理掉死亡的對象:

 

標記-整理.jpg

 

 

2.2.4、分代收集算法

分代回收的思想就是根據對象的存活週期,將不同的內存劃分爲幾塊,根據每塊內存的特點採用適當的收集算法,比如新生代採用複製算法,老年代採用標記-整理算法。

2.3、垃圾收集器

下圖展示了7種不同分代的收集器,如果兩個收集器之間存在連線,則表示可以搭配使用。

 

垃圾回收器.jpg

 

 

通過以下命令可以查看垃圾回收器信息:

java -XX: PrintCommandLineFlags -version
複製代碼

我的測試服務器結果:

-XX:InitialHeapSize=524503488 -XX:MaxHeapSize=8392055808 -XX: PrintCommandLineFlags -XX: UseCompressedClassPointers -XX: UseCompressedOops -XX: UseParallelGC 
java version "1.8.0_152"
Java(TM) SE Runtime Environment (build 1.8.0_152-b16)
Java HotSpot(TM) 64-Bit Server VM (build 25.152-b16, mixed mode)
複製代碼

可以看到使用的是:ParallelGC。

JVM參數對應關係:

 

JVM參數對應關係.png

 

 

下面簡單介紹7中垃圾回收器:

2.3.1、Serial收集器

新生代單線程收集器,簡單高效。

2.3.2、ParNew收集器

Serial收集器的多線程版本,除了在垃圾回收時使用多線程,其餘都和Serial收集器相同。

2.3.3、Parallel Scavenge收集器

Parallel Scavenge收集器是並行的採用複製算法的新生代收集器,着重於系統的吞吐量,適合後臺運算而不需要太多用戶交互的任務。

2.3.4、Serial Old收集器

Serial Old是單線程的老年代垃圾收集器,使用標記-整理算法。具有簡單高效的特點。

2.3.5、Parallel Old收集器

Parallel Old收集器是Parallel Scanenge的老年代版本,多線程垃圾回收,也是用標記-整理算法。

2.3.6、CMS收集器

CMS注重服務的響應時間,是基於標記-清除算法實現。具有併發收集、低停頓的特點。

2.3.7、G1收集器

Garbage First,基於標記-整理算法,其將整個Java堆(包括新生代和老年代)劃分爲多個大小固定的獨立區域,並且跟蹤這些區域裏的垃圾堆積程度,在後臺維護一個優先列表,每次根據允許的收集時間,優先回收垃圾最多的區域。

3、CPU佔用過高問題排查

3.1、 linux查看進程信息

top
複製代碼

 

linux查看進程信息.png

 

 

3.2、查看進程佔用cpu最多的線程

ps -mp 23967 -o THREAD,tid,time

複製代碼

 

查看進程佔用cpu最多的線程.png

 

 

3.3、線程ID轉16進制

printf "%x\n" 23968

複製代碼

 

線程ID轉16進制.png

 

 

3.4、查看線程信息

jstack  23967  |grep -A  10  5da0

複製代碼

 

查看線程信息.png

 

 

jstack 23967  |grep 5da0 -A 30

複製代碼

 

查看線程信息-2.png

 

 

3.5、 查看進程的對象信息

jmap -histo:live 23967 | more

複製代碼

 

查看進程的對象信息.png

 

 

3.6、查看進程的GC情況

jstat -gcutil 23967 1000 100

複製代碼

 

查看進程的GC情況.png

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