Linux進程內存統計
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編譯後的代碼緩存)、每個類結構(如運行時常數池、字段和方法數據)以及方法和構造方法 的代碼都在非堆內存中。
-
JVM 本身需要的內存,包括其加載的第三方庫以及這些庫分配的內存
-
NIO 的 DirectBuffer 是分配的 native memory
-
內存映射文件,包括 JVM 加載的一些 JAR 和第三方庫,以及程序內部用到的。上面 pmap 輸出的內容裏,有一些靜態文件所占用的大小不在 Java 的 heap 裏,因此作為一個Web服務器,趕緊把靜態文件從這個Web服務器中人移開吧,放到nginx或者CDN裏去吧。
-
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
-
JNI,一些JNI接口調用的native庫也會分配一些內存,如果遇到JNI庫的內存泄露,可以使用valgrind等內存泄露工具來檢測
-
線程棧,每個線程都會有自己的棧空間,如果線程一多,這個的開銷就很明顯了
- 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進程內存統計