查看JVM進(jìn)程的內(nèi)存情況

// DirectMemory.java
package com.infuq.memory;

import org.jctools.util.UnsafeAccess;
import sun.misc.Unsafe;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import java.util.Scanner;

public class DirectMemory {

    public static void main(String[] args) throws Exception {

        Scanner scanner = new Scanner(System.in);

        long _30M = 30 * 1024 * 1024;
        long direct = 0;
        Unsafe unsafe = UnsafeAccess.UNSAFE;
        while (scanner.hasNext()) {
            String input = scanner.next();

            if (input.equals("1")) {
                System.out.println("malloc...");
                // 向操作系統(tǒng)申請(qǐng)內(nèi)存,底層調(diào)用glibc庫(kù)的malloc庫(kù)函數(shù)
                direct = unsafe.allocateMemory(_30M);
            }
            else if (input.equals("2")) {
                System.out.println("init...");
                byte b = 6;
                // 使用上一步向操作系統(tǒng)申請(qǐng)的內(nèi)存
                unsafe.setMemory(direct, _30M, b);
            }
        }
    }
}

上面這個(gè)程序的功能, 執(zhí)行之后, 等待用戶輸入, 如果輸入1,那么程序會(huì)向操作系統(tǒng)申請(qǐng)30M的內(nèi)存, 如果輸入2, 那么程序會(huì)初始化申請(qǐng)的30M內(nèi)存.

這里說(shuō)的初始化的言外之意是模擬程序使用向操作系統(tǒng)申請(qǐng)的內(nèi)存

程序運(yùn)行之后, 我們會(huì)通過(guò)使用JDK自帶的jconsole(或jvisualvm)工具查看進(jìn)程內(nèi)存情況, 使用top,ps等命令查看進(jìn)程內(nèi)存情況, 使用JDK自帶的jcmd命令查看進(jìn)程內(nèi)存情況, 使用pmap命令查看進(jìn)程內(nèi)存情況, 使用阿里云的arms查看進(jìn)程的內(nèi)存情況, 使用smem工具查看進(jìn)程的內(nèi)存情況. 從多維度查看內(nèi)存情況 .

本次實(shí)驗(yàn)的環(huán)境: JDK1.8 Win10下的WSL2的Ubuntu20
還會(huì)使用2個(gè)三方包: jctools-core-2.1.2.jar jol-core-0.9.jar

文件結(jié)構(gòu)如下圖

圖片.png

run.py中的內(nèi)容如下

圖片.png

其實(shí)就是調(diào)用了 javac 和 java 命令而已,
設(shè)置堆空間50M, -XX:MaxMetaspaceSize=16M

訪問(wèn) https://www.selenic.com/smem/download/ 下載一個(gè)smem工具, 可以用于查看進(jìn)程的內(nèi)存

圖片.png

解壓下載的 smem-1.4.tar.gz

最后我們的目錄結(jié)構(gòu)如下


圖片.png

運(yùn)行程序


圖片.png

運(yùn)行之后, 程序阻塞, 等待用戶的輸入

使用 jps 查看進(jìn)程的PID = 15933


圖片.png

我們先使用 smem 工具查看下內(nèi)存, 如下圖

./smem -t -k


圖片.png

以上輸出當(dāng)前系統(tǒng)所有進(jìn)程的內(nèi)存情況, 由于我實(shí)驗(yàn)使用的是Win10的WSL系統(tǒng), 所以系統(tǒng)里的進(jìn)程很少. 能夠看出進(jìn)程15933使用的內(nèi)存, USS=27.8M, PSS=28M, RSS=30.3M

圖片.png

USS,PSS,RSS都是表示進(jìn)程實(shí)際使用的內(nèi)存. 更多關(guān)于USS,PSS,RSS關(guān)系和區(qū)別, 讀者自行了解.

我們經(jīng)常聽(tīng)到RSS/RES, 在使用top和ps命令的時(shí)候會(huì)看到, 如下圖

圖片.png
圖片.png

如上圖, 使用 top 和 ps 查看進(jìn)程15933的RSS/RES = 54552KB, 即53.27M, 約等于使用 smem 工具查看的RSS=54.2M內(nèi)存.

RSS是常駐于內(nèi)存的內(nèi)存, RSS中還會(huì)包含與其他進(jìn)程一起共享的內(nèi)存.

我們使用如下shell命令可以每隔2秒打印進(jìn)程15933的實(shí)際使用的內(nèi)存情況
i=0;while true; do echo $((i++)) $(./smem -t -k | tail -3 | head -1); sleep 2; done

我們還會(huì)使用如下shell命令每隔2秒打印進(jìn)程15933的committed內(nèi)存
i=0;while true; do echo $((i++)) $(pmap -d 15933 | tail -1); sleep 2; done

關(guān)于reserved(預(yù)留內(nèi)存), committed(提交內(nèi)存), used(已使用內(nèi)存)的關(guān)系如下圖, 更詳細(xì)內(nèi)容讀者自行了解


圖片.png

比如我們向操作系統(tǒng)申請(qǐng)30M的內(nèi)存, 則committed=30M. 但是操作系統(tǒng)并不會(huì)馬上將真實(shí)的30M內(nèi)存全部分配給進(jìn)程, 只會(huì)先分配一小部分真實(shí)內(nèi)存給進(jìn)程使用, 當(dāng)再次需要真實(shí)內(nèi)存的時(shí)候再次分配. 因此一個(gè)進(jìn)程的committed內(nèi)存一定大于等于used的內(nèi)存.

好了, 我們把上面兩個(gè)shell命令運(yùn)行起來(lái)

然后我們捕捉某一時(shí)刻的內(nèi)存情況如下圖


圖片.png

進(jìn)程15933當(dāng)前時(shí)刻實(shí)際使用的內(nèi)存54.2M, 虛擬內(nèi)存1641572K=1603M, committed內(nèi)存114552K=111.86M

接下來(lái)輸入1,那么我們的程序會(huì)向操作系統(tǒng)申請(qǐng)30M的內(nèi)存

圖片.png

如上圖, 我們向操作系統(tǒng)申請(qǐng)了30M的內(nèi)存, 而進(jìn)程的已使用內(nèi)存并沒(méi)有變化, 但是進(jìn)程commited內(nèi)存從114552K->145276K, 相差30724K=30M.

我們繼續(xù)再輸入1, 結(jié)果如下圖


圖片.png

如上圖, 繼續(xù)向操作系統(tǒng)申請(qǐng)30M內(nèi)存, 進(jìn)程已使用的內(nèi)存也沒(méi)有變化, 而進(jìn)程committed內(nèi)存又從145276增長(zhǎng)到176000K, 又相差了30M.

我們不做任何操作, 時(shí)間過(guò)去了一會(huì)...

進(jìn)程的內(nèi)存如下圖所示


圖片.png

在這一段時(shí)間我們并沒(méi)有任何操作, 內(nèi)存有了一些小變化, 這很正常, 畢竟JVM進(jìn)程里面還有一些JVM自身的線程也要隨著程序的運(yùn)行需要申請(qǐng)一些內(nèi)存, 后面我們使用 jconsole 連接到進(jìn)程, 內(nèi)存也會(huì)發(fā)生一些增長(zhǎng), 這都是正常情況.

我們使用JDK自帶的 jcmd 命令查看內(nèi)存


圖片.png

committed=173226KB 與上圖使用pmap顯示的176000KB有一些差. 畢竟它們是兩個(gè)不同的命令, 統(tǒng)計(jì)的角度不一樣.

pmap 命令統(tǒng)計(jì)的會(huì)比 jcmd統(tǒng)計(jì)的更準(zhǔn)確. 查看man手冊(cè), pmap統(tǒng)計(jì)的是進(jìn)程自身的smaps文件

圖片.png
圖片.png

接下來(lái)


圖片.png

如上圖, 重點(diǎn)需要關(guān)注Heap和Internal內(nèi)存的情況

我們使用JDK自帶的 jconsole 工具查看內(nèi)存

圖片.png

上圖查看的是堆空間的內(nèi)存情況, committed=49152KB, 與使用 jcmd 命令查看的51200KB有一些差, 可以忽略, 畢竟是2個(gè)不同的工具統(tǒng)計(jì)的. 上圖同時(shí)也說(shuō)明了, 雖然向操作系統(tǒng)申請(qǐng)了50M的堆空間, 但是目前實(shí)際使用了Used=10578KB, 此時(shí)操作系統(tǒng)也只是把部分真實(shí)內(nèi)存分配給進(jìn)程, 只有隨著進(jìn)程的運(yùn)行需要的內(nèi)存越多, 操作系統(tǒng)才會(huì)分配更多的真實(shí)內(nèi)存給進(jìn)程, 當(dāng)分配的真實(shí)內(nèi)存一旦超過(guò)committed時(shí), 也就會(huì)報(bào)OOM了.

我們?cè)俅尾蹲侥骋粫r(shí)刻的內(nèi)存情況


圖片.png

接下來(lái)我們輸入2, 我們寫(xiě)的程序就會(huì)使用申請(qǐng)到的內(nèi)存

圖片.png

如上圖, 當(dāng)我們真正使用內(nèi)存的時(shí)候, committed(374664KB)內(nèi)存沒(méi)有發(fā)生變化, 而使用內(nèi)存發(fā)生了變化, 增大了30M, 和我們之前申請(qǐng)的30M是一致的.

這個(gè)時(shí)候我們看一下通過(guò) jconsole 統(tǒng)計(jì)的非堆內(nèi)存的情況

圖片.png

我們繼續(xù)輸入1, 再申請(qǐng)30M內(nèi)存, 再輸入2, 使用申請(qǐng)的內(nèi)存,

看一下內(nèi)存的變化

圖片.png

和之前的實(shí)驗(yàn)一樣, 當(dāng)輸入1申請(qǐng)內(nèi)存時(shí), committed內(nèi)存發(fā)生了變化, 已經(jīng)使用內(nèi)存沒(méi)有發(fā)生變化

輸入2之后


圖片.png

committed內(nèi)存沒(méi)有發(fā)生變化, 已使用內(nèi)存增長(zhǎng)了30M

而且我們?cè)俅慰匆幌路嵌褍?nèi)存, 與之前的統(tǒng)計(jì)幾乎一樣, 沒(méi)變化.

圖片.png

我們所說(shuō)的非堆內(nèi)存包括Metaspace, CodeCache, CCS, 使用ByteBuffer.allocateDirect(),使用unsafe.allocateMemory(), 使用FileChannel.map(), 使用FileChannel.transferTo()等申請(qǐng)的內(nèi)存.其中重點(diǎn)要說(shuō)的是ByteBuffer.allocateDirect()和unsafe.allocateMemory().雖然都是申請(qǐng)的直接內(nèi)存, 也就是操作系統(tǒng)本地內(nèi)存, 但是 jconsole 只能統(tǒng)計(jì)到使用ByteBuffer.allocateDirect()申請(qǐng)的直接內(nèi)存, 它是無(wú)法統(tǒng)計(jì)到使用unsafe.allocateMemory()申請(qǐng)的直接內(nèi)存. 我們使用的-XX:MaxMetaspaceSize也是控制ByteBuffer.allocateDirect()申請(qǐng)的直接內(nèi)存大小, 無(wú)法控制unsafe.allocateMemory()申請(qǐng)的直接內(nèi)存大小.比如我們使用的Dubbo, RocketMQ等底層網(wǎng)絡(luò)通信都是使用Netty, Netty就是通過(guò)unsafe.allocateMemory()向操作系統(tǒng)申請(qǐng)內(nèi)存并自己管理這塊內(nèi)存, Netty也會(huì)自己管理向操作系統(tǒng)申請(qǐng)內(nèi)存的空間大小, 畢竟不能無(wú)限制向操作系統(tǒng)申請(qǐng)內(nèi)存.

FileChannel.map() 和 FileChannel.transferTo() 涉及到零拷貝知識(shí), 讀者朋友可以去了解下, 在我的 https://www.yuque.com/infuq/others/miqbcc 文章也有記錄

如果讀者朋友所在公司的服務(wù)器部署在阿里云上, 通過(guò)阿里云的arms監(jiān)控平臺(tái)查看服務(wù)器的內(nèi)存情況

圖片.png

上圖右下角的直接緩沖區(qū)與 jconsole 統(tǒng)計(jì)的直接內(nèi)存一樣, 它們都無(wú)法統(tǒng)計(jì)到使用unsafe.allocateMemory()申請(qǐng)的內(nèi)存.

如果要查看堆內(nèi)存的使用情況, 可以使用 jconsole 或者 arms 查看堆內(nèi)存的情況, 它們的統(tǒng)計(jì)沒(méi)問(wèn)題.

如果要查看直接內(nèi)存的情況, 或者查看進(jìn)程的內(nèi)存情況, 僅僅使用 jconsole 或者 arms 是不完全的, 看到的內(nèi)存是比實(shí)際要少的.

上圖并非此次實(shí)驗(yàn)程序的內(nèi)存統(tǒng)計(jì), 我是從線上找的一個(gè)服務(wù)器

接下來(lái)

當(dāng)我一直輸入1, 也就是一直向操作系統(tǒng)申請(qǐng)內(nèi)存, 只能表明進(jìn)程的committed內(nèi)存一直在增長(zhǎng)

圖片.png

而且我的宿主機(jī)Win10的內(nèi)存也不會(huì)隨著committed內(nèi)存增長(zhǎng)而增長(zhǎng)


圖片.png

接下來(lái)我們輸入2, 讓進(jìn)程使用申請(qǐng)到的內(nèi)存


圖片.png

進(jìn)程已使用的內(nèi)存到了1.5G


圖片.png

宿主機(jī)的內(nèi)存也從之前的6.6增長(zhǎng)到了7.1G, 進(jìn)程已使用內(nèi)存也從828M增長(zhǎng)到了1.5G, 兩者增長(zhǎng)量基本吻合的.

【總結(jié)1】
通過(guò)實(shí)驗(yàn), 零零散散介紹了如何查看進(jìn)程的內(nèi)存, 包括committed內(nèi)存, 已使用內(nèi)存等. 進(jìn)程使用unsafe.allocateMemory()申請(qǐng)內(nèi)存只是屬于committed內(nèi)存, 只有在進(jìn)程真正使用這塊內(nèi)存的時(shí)候, 操作系統(tǒng)才會(huì)一部分一部分的將真實(shí)的內(nèi)存分配給進(jìn)程使用. 通過(guò)實(shí)驗(yàn)也能知道, 使用unsafe.allocateMemory()方式申請(qǐng)內(nèi)存是不受-XX:MaxMetaspaceSize參數(shù)控制的, 實(shí)驗(yàn)中設(shè)置-XX:MaxMetaspaceSize=16M , 但是我們程序已經(jīng)申請(qǐng)使用了好幾百M(fèi)的內(nèi)存.

【總結(jié)2】

1.如果要查看進(jìn)程的committed內(nèi)存, 使用pmap -d <進(jìn)程ID>查看
2.如果要查看進(jìn)程已使用的內(nèi)存(USS,PSS,RSS), 使用smem工具查看, 使用top和ps命令也可以查看到RSS值, 但這個(gè)RSS值包含共享的內(nèi)存, 因此我們也要關(guān)注PSS,USS
3.如果要查看JVM的直接內(nèi)存, 可以使用 jcmd <進(jìn)程ID> VM.native_memory scale=KB
4.當(dāng)使用unsafe.allocateMemory(30M)申請(qǐng)內(nèi)存的時(shí)候, committed內(nèi)存會(huì)增長(zhǎng)30M, 但是已使用內(nèi)存不會(huì)增長(zhǎng)
5.當(dāng)程序使用unsafe.allocateMemory(30M)申請(qǐng)到的內(nèi)存時(shí), 已使用內(nèi)存會(huì)增長(zhǎng)
6.jconsole , jvisualvm, 阿里云arms 是監(jiān)控不到unsafe.allocateMemory()方式申請(qǐng)的內(nèi)存

關(guān)于如何監(jiān)控遠(yuǎn)程Java進(jìn)程可以查看我的這篇語(yǔ)雀文章
https://www.yuque.com/infuq/default/wwmdfk#rJSoP

關(guān)于JVM內(nèi)存的布局圖可以在下面這篇語(yǔ)雀文章中查找到
https://www.yuque.com/infuq/default/bzu9ef

再貼一張圖


進(jìn)程實(shí)際內(nèi)存.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末利花,一起剝皮案震驚了整個(gè)濱河市恋拷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宵凌,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件隅居,死亡現(xiàn)場(chǎng)離奇詭異钠至,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)胎源,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)棉钧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人涕蚤,你說(shuō)我怎么就攤上這事宪卿。” “怎么了万栅?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵佑钾,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我烦粒,道長(zhǎng)休溶,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任扰她,我火速辦了婚禮兽掰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘徒役。我一直安慰自己禾进,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布廉涕。 她就那樣靜靜地躺著,像睡著了一般艇拍。 火紅的嫁衣襯著肌膚如雪狐蜕。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,985評(píng)論 1 291
  • 那天卸夕,我揣著相機(jī)與錄音层释,去河邊找鬼。 笑死快集,一個(gè)胖子當(dāng)著我的面吹牛贡羔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播个初,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼乖寒,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了院溺?” 一聲冷哼從身側(cè)響起楣嘁,我...
    開(kāi)封第一講書(shū)人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后逐虚,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體聋溜,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年叭爱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了撮躁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡买雾,死狀恐怖把曼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情凝果,我是刑警寧澤祝迂,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站器净,受9級(jí)特大地震影響型雳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜山害,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一纠俭、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧浪慌,春花似錦冤荆、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至汹想,卻和暖如春外邓,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背古掏。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工损话, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人槽唾。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓丧枪,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親庞萍。 傳聞我的和親對(duì)象是個(gè)殘疾皇子拧烦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容