首頁 > 軟體

Linux進程記憶體統計

2020-06-16 16:48:47

一、 進程記憶體統計

cat /proc/[pid]/status
通過/proc/[pid]/status可以檢視進程的記憶體使用情況,包括虛擬記憶體大小(VmSize),實體記憶體大小(VmRSS),資料段大小(VmData),棧的大小(VmStk),程式碼段的大小(VmExe),共用庫的程式碼段大小(VmLib)等等。

* Name: java /*進程的程式名*/
* State: S (sleeping) /*進程的狀態資訊,具體參見*/
* Tgid: 9744 /*執行緒組號*/
* Pid: 9744 /*進程pid*/
* PPid: 7672 /*父進程的pid*/
* TracerPid: 0 /*跟蹤進程的pid*/
* VmPeak: 60184 kB /*進程地址空間的大小*/
* VmSize: 60180 kB /*進程虛擬地址空間的大小reserved_vm:進程在預留或特殊的記憶體間的物理頁*/
* VmLck: 0 kB /*進程已經鎖住的實體記憶體的大小.鎖住的實體記憶體不能交換到硬碟*/
* VmHWM: 18020 kB /*檔案記憶體對映和匿名記憶體對映的大小*/
* VmRSS: 18020 kB /*應用程式正在使用的實體記憶體的大小,就是用ps命令的引數rss的值 (rss)*/
* VmData: 12240 kB /*程式資料段的大小(所佔虛擬記憶體的大小),存放初始化了的資料*/
* VmStk: 84 kB /*進程在使用者態的棧的大小*/
* VmExe: 576 kB /*程式所擁有的可執行虛擬記憶體的大小,程式碼段,不包括任務使用的庫 */
* VmLib: 21072 kB /*被映像到任務的虛擬記憶體空間的庫的大小*/
* VmPTE: 56 kB /*該進程的所有頁表的大小*/
* Threads: 1 /*共用使用該信號描述符的任務的個數*/

二、JVM 記憶體分配

java記憶體組成介紹:堆(Heap)和非堆(Non-heap)記憶體
按照官方的說法:“Java 虛擬機器具有一個堆,堆是執行時資料區域,所有類範例和陣列的記憶體均從此處分配。堆是在 Java 虛擬機器啟動時建立的。” “在JVM中堆之外的記憶體稱為非堆記憶體(Non-heap memory)”。

可以看出JVM主要管理兩種型別的記憶體:堆和非堆。

簡單來說堆就是Java程式碼可及的記憶體,是留給開發人員使用的;非堆就是JVM留給自己用的。

所以方法區、JVM內部處理或優化所需的記憶體(如JIT編譯後的程式碼快取)、每個類結構(如執行時常數池、欄位和方法資料)以及方法和構造方法 的程式碼都在非堆記憶體中。

1.JVM 本身需要的記憶體,包括其載入的第三方庫以及這些庫分配的記憶體

2.NIO 的 DirectBuffer 是分配的 native memory

3.記憶體對映檔案,包括 JVM 載入的一些 JAR 和第三方庫,以及程式內部用到的。上面 pmap 輸出的內容裡,有一些靜態檔案所占用的大小不在 Java 的 heap 裡,因此作為一個Web伺服器,趕緊把靜態檔案從這個Web伺服器中人移開吧,放到nginx或者CDN裡去吧。

4.JIT, JVM會將Class編譯成native程式碼,這些記憶體也不會少,如果使用了Spring的AOP,CGLIB會生成更多的類,JIT的記憶體開銷也會隨之變大,而且Class本身JVM的GC會將其放到Perm Generation裡去,很難被回收掉,面對這種情況,應該讓JVM使用ConcurrentMarkSweep GC,並啟用這個GC的相關引數允許將不使用的class從Perm Generation中移除, 引數設定:-XX:+UseConcMarkSweepGC -X:+CMSPermGenSweepingEnabled -X:+CMSClassUnloadingEnabled,如果不需要移除而Perm Generation空間不夠,可以加大一點:-X:PermSize=256M -X:MaxPermSize=512M

5.JNI,一些JNI介面呼叫的native庫也會分配一些記憶體,如果遇到JNI庫的記憶體洩露,可以使用valgrind等記憶體洩露工具來檢測

6.執行緒棧,每個執行緒都會有自己的棧空間,如果執行緒一多,這個的開銷就很明顯了

7.jmap/jstack 取樣,頻繁的取樣也會增加記憶體占用,如果你有伺服器健康監控,記得這個頻率別太高,否則健康監控變成致病監控了。

1. 方法區

也稱”永久代” 、“非堆”,它用於儲存虛擬機器載入的類資訊、常數、靜態變數、是各個執行緒共用的記憶體區域。預設最小值為 16 MB,最大值為 64 MB,可以通過-XX: PermSize 和 -XX: MaxPermSize 引數限制方法區的大小。
執行時常數池:是方法區的一部分,Class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常數池,用於存放編譯器生成的各種符號參照,這部分內容將在類載入後放到方法區的執行時常數池中。

2. 虛擬機器棧

描述的是java 方法執行的記憶體模型:每個方法被執行的時候 都會建立一個“棧幀”用於儲存區域性變數表(包括引數)、操作棧、方法出口等資訊。

每個方法被呼叫到執行完的過程,就對應著一個棧幀在虛擬機器棧中從入棧到出棧的過程。宣告周期與執行緒相同,是執行緒私有的。

區域性變數表存放了編譯器可知的各種基本資料型別(boolean、byte、char、short、int、float、long、double)、物件參照(參照指標,並非物件本身),其中64位元長度的long和double型別的資料會佔用2個區域性變數的空間,其餘資料型別只佔1個。

區域性變數表所需的記憶體空間在編譯期間完成分配,當進入一個方法時,這個方法需要在棧幀中分配多大的區域性變數是完全確定的,在執行期間棧幀不會改變區域性變數表的大小空間。

3. 本地方法棧

與虛擬機器棧基本類似,區別在於虛擬機器棧為虛擬機器執行的java方法服務,而本地方法棧則是為Native方法服務。

4. 堆

也叫做java 堆、GC堆是java虛擬機器所管理的記憶體中最大的一塊記憶體區域,也是被各個執行緒共用的記憶體區域,在JVM啟動時建立。

該記憶體區域存放了物件範例及陣列(所有 new 的物件)。其大小通過 -Xms (最小值) 和 -Xmx (最大值) 引數設定,-Xms為 JVM 啟動時申請的最小記憶體,預設為作業系統實體記憶體的 1/64 但小於 1G;

-Xmx 為 JVM 可申請的最大記憶體,預設為實體記憶體的1/4但小於 1G,預設當空餘堆記憶體小於 40% 時,JVM 會增大 Heap 到 -Xmx 指定的大小,可通過 -XX:MinHeapFreeRation= 來指定這個比列;

當空餘堆記憶體大於70%時,JVM 會減小 heap 的大小到 -Xms 指定的大小,可通過XX:MaxHeapFreeRation= 來指定這個比列,對於執行系統,為避免在執行時頻繁調整 Heap 的大小,通常 -Xms 與 -Xmx 的值設成一樣。

由於現在收集器都是採用分代收集演算法,堆被劃分為新生代和老年代。新生代主要儲存新建立的物件和尚未進入老年代的物件。老年代儲存經過多次新生代GC(Minor GC)任然存活的物件。

5. 程式計數器

是最小的一塊記憶體區域,它的作用是當前執行緒所執行的位元組碼的行號指示器,在虛擬機器的模型裡,位元組碼直譯器工作時就是通過改變這個計數器的值來選取下一條需要執行的位元組碼指令,分支、迴圈、例外處理、執行緒恢復等基礎功能都需要依賴計數器完成。

三、直接記憶體

直接記憶體並不是虛擬機器記憶體的一部分,也不是Java虛擬機器規範中定義的記憶體區域。jdk1.4中新加入的NIO,引入了通道與緩衝區的IO方式,它可以呼叫Native方法直接分配堆外記憶體,這個堆外記憶體就是本機記憶體,不會影響到堆記憶體的大小。

四、JVM 記憶體分析

1. 檢視 JVM 堆記憶體情況

[root@server ~]$ jmap -heap 837
Attaching to process ID 837, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.71-b01
using thread-local object allocation.
Parallel GC with 4 thread(s)//GC 方式
Heap Configuration: //堆記憶體初始化設定
  MinHeapFreeRatio = 0 //對應jvm啟動引數-XX:MinHeapFreeRatio設定JVM堆最小空閒比率(default 40)
  MaxHeapFreeRatio = 100 //對應jvm啟動引數 -XX:MaxHeapFreeRatio設定JVM堆最大空閒比率(default 70)
  MaxHeapSize      = 2082471936 (1986.0MB) //對應jvm啟動引數-XX:MaxHeapSize=設定JVM堆的最大大小
  NewSize          = 1310720 (1.25MB)//對應jvm啟動引數-XX:NewSize=設定JVM堆的‘新生代’的預設大小
  MaxNewSize      = 17592186044415    MB//對應jvm啟動引數-XX:MaxNewSize=設定JVM堆的‘新生代’的最大大小
  OldSize          = 5439488 (5.1875MB)//對應jvm啟動引數-XX:OldSize=<value>:設定JVM堆的‘老生代’的大小
  NewRatio        = 2 //對應jvm啟動引數-XX:NewRatio=:‘新生代’和‘老生代’的大小比率
  SurvivorRatio    = 8 //對應jvm啟動引數-XX:SurvivorRatio=設定年輕代中Eden區與Survivor區的大小比值
  PermSize        = 21757952 (20.75MB)  //對應jvm啟動引數-XX:PermSize=<value>:設定JVM堆的‘永生代’的初始大小
  MaxPermSize      = 85983232 (82.0MB)//對應jvm啟動引數-XX:MaxPermSize=<value>:設定JVM堆的‘永生代’的最大大小
  G1HeapRegionSize = 0 (0.0MB)
Heap Usage://堆記憶體使用情況
PS Young Generation
Eden Space://Eden區記憶體分布
  capacity = 33030144 (31.5MB)//Eden區總容量
  used    = 1524040 (1.4534378051757812MB)  //Eden區已使用
  free    = 31506104 (30.04656219482422MB)  //Eden區剩餘容量
  4.614088270399305% used //Eden區使用比率
From Space:  //其中一個Survivor區的記憶體分布
  capacity = 5242880 (5.0MB)
  used    = 0 (0.0MB)
  free    = 5242880 (5.0MB)
  0.0% used
To Space:  //另一個Survivor區的記憶體分布
  capacity = 5242880 (5.0MB)
  used    = 0 (0.0MB)
  free    = 5242880 (5.0MB)
  0.0% used
PS Old Generation //當前的Old區記憶體分布
  capacity = 86507520 (82.5MB)
  used    = 0 (0.0MB)
  free    = 86507520 (82.5MB)
  0.0% used
PS Perm Generation//當前的 “永生代” 記憶體分布
  capacity = 22020096 (21.0MB)
  used    = 2496528 (2.3808746337890625MB)
  free    = 19523568 (18.619125366210938MB)
  11.337498256138392% used
670 interned Strings occupying 43720 bytes.

前面jmap輸出的內容裡,MaxHeapSize 是在命令列上配的,-Xmx4096m,這個java程式可以用到的最大堆記憶體。

VSZ是指已分配的線性空間大小,這個大小通常並不等於程式實際用到的記憶體大小,產生這個的可能性很多,比如記憶體對映,共用的動態庫,或者向系統申請了更多的堆,都會擴充套件線性空間大小,要檢視一個進程有哪些記憶體對映,可以使用 pmap 命令來檢視:
pmap -x [pid]

[root@server ~]$ pmap -x 837
837:  java
Address          Kbytes    RSS  Dirty Mode  Mapping
0000000040000000      36      4      0 r-x--  java
0000000040108000      8      8      8 rwx--  java
00000000418c9000  13676  13676  13676 rwx--    [ anon ]
00000006fae00000  83968  83968  83968 rwx--    [ anon ]
0000000700000000  527168  451636  451636 rwx--    [ anon ]
00000007202d0000  127040      0      0 -----    [ anon ]
...
...
00007f55ee124000      4      4      0 r-xs-  az.png
00007fff017ff000      4      4      0 r-x--    [ anon ]
ffffffffff600000      4      0      0 r-x--    [ anon ]
----------------  ------  ------  ------
total kB        7796020 3037264 3023928

這裡可以看到很多anon,這些表示這塊記憶體是由mmap分配的。

RSZ是Resident Set Size,常駐記憶體大小,即進程實際占用的實體記憶體大小, 在現在這個例子當中,RSZ和實際堆記憶體占用差了2.3G,這2.3G的記憶體組成分別為:

檢視 JVM 堆各個分割區的記憶體情況

jstat -gcutil [pid]

[root@server ~]$ jstat -gcutil 837 1000 20
 S0    S1    E      O      P    YGC    YGCT    FGC    FGCT    GCT 
 0.00  80.43  24.62  87.44  98.29  7101  119.652    40  19.719  139.371
 0.00  80.43  33.14  87.44  98.29  7101  119.652    40  19.719  139.371

分析 JVM 堆記憶體中的物件

檢視存活的物件統計

jmap -histo:live [pid]

dump 記憶體

jmap -dump:format=b,file=heapDump [pid]

然後用jhat命令可以參看

jhat -port 5000 heapDump
在瀏覽器中存取:http://localhost:5000/ 檢視詳細資訊

Linux公社的RSS地址:https://www.linuxidc.com/rssFeed.aspx

本文永久更新連結地址https://www.linuxidc.com/Linux/2018-08/153571.htm


IT145.com E-mail:sddin#qq.com