JVM內存區域與垃圾回收


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

1.一、概述

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

1.二、程序計數器

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

1.三、虛擬機棧

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

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

  • StackOverflowError

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

  • OutOfMemoryError

虛擬機棧擴展時沒法申請到足夠的內存。服務器

1.四、本地方法棧

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

1.五、堆

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

1.六、方法區

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

二、垃圾回收

2.一、如何判斷對象已經死亡?

2.1.一、引用計數法

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

2.1.二、GC Roots搜索

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

GC Roots包括如下幾種:

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

2.二、垃圾收集算法

2.2.一、標記-清除算法

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

  • 標記:首先標記出全部須要回收的對象。
  • 清除:統一回收被標記的對象。

標記-清除.png

這個算法有連個缺點:

  1. 效率不高
  2. 會產生不連續的內存碎片
2.2.二、複製算法

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

複製算法.png

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

2.2.三、標記-整理算法

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

標記-整理.jpg

2.2.四、分代收集算法

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

2.三、垃圾收集器

下圖展現了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.一、Serial收集器

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

2.3.二、ParNew收集器

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

2.3.三、Parallel Scavenge收集器

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

2.3.四、Serial Old收集器

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

2.3.五、Parallel Old收集器

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

2.3.六、CMS收集器

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

2.3.七、G1收集器

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

三、CPU佔用太高問題排查

3.一、 linux查看進程信息

top

linux查看進程信息.png

3.二、查看進程佔用cpu最多的線程

ps -mp 23967 -o THREAD,tid,time

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

3.三、線程ID轉16進制

printf "%x\n" 23968

線程ID轉16進制.png

3.四、查看線程信息

jstack  23967  |grep -A  10  5da0

查看線程信息.png

jstack 23967  |grep 5da0 -A 30

查看線程信息-2.png

3.五、 查看進程的對象信息

jmap -histo:live 23967 | more

查看進程的對象信息.png

3.六、查看進程的GC狀況

jstat -gcutil 23967 1000 100

查看進程的GC狀況.png

參考

利用jmap和MAT等工具查看JVM運行時堆內存

tencent.jpg

相關文章
相關標籤/搜索