<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                本課時我們主要講解讓面試官刮目相看的堆外內存排查。 第 02 課時講了 JVM 的內存布局,同時也在第 08 課時中看到了由于 Metaspace 設置過小而引起的問題,接著,第 10 課時講了一下元空間和直接內存引起的內存溢出實例。 Metaspace 屬于堆外內存,但由于它是單獨管理的,所以排查起來沒什么難度。你平常可能見到的使用堆外內存的場景還有下面這些: * JNI 或者 JNA 程序,直接操縱了本地內存,比如一些加密庫; * 使用了Java 的 Unsafe 類,做了一些本地內存的操作; * Netty 的直接內存(Direct Memory),底層會調用操作系統的 malloc 函數。 使用堆外內存可以調用一些功能完備的庫函數,而且減輕了 GC 的壓力。這些代碼,有可能是你了解的人寫的,也有可能隱藏在第三方的 jar 包里。雖然有一些好處,但是問題排查起來通常會比較的困難。 在第 10 課時,介紹了 MaxDirectMemorySize 可以控制直接內存的申請。其實,通過這個參數,仍然限制不住所有堆外內存的使用,它只是限制了使用 DirectByteBuffer 的內存申請。很多時候(比如直接使用了 sun.misc.Unsafe 類),堆外內存會一直增長,直到機器物理內存爆滿,被 oom killer。 ``` import?sun.misc.Unsafe; import?java.lang.reflect.Field; public?class?UnsafeDemo?{ ????public?static?final?int?_1MB?=?1024?*?1024; ????public?static?void?main(String[]?args)?throws?Exception?{ ????????Field?field?=?Unsafe.class.getDeclaredField("theUnsafe"); ????????field.setAccessible(true); ????????Unsafe?unsafe?=?(Unsafe)?field.get(null); ????????for?(;?;?)?{ ????????????unsafe.allocateMemory(_1MB); ????????} ????} ``` 上面這段代碼,就會持續申請堆外內存,但它返回的是 long 類型的地址句柄,所以堆內內存的使用會很少。 我們使用下面的命令去限制堆內和直接內存的使用,結果發現程序占用的操作系統內存在一直上升,這兩個參數在這種場景下沒有任何效果。這段程序搞死了我的機器很多次,運行的時候要小心。 ``` java?-XX:MaxDirectMemorySize=10M?-Xmx10M??UnsafeDemo ``` 相信這種情況也困擾了你,因為使用一些 JDK 提供的工具,根本無法發現這部門內存的使用。我們需要一些更加底層的工具來發現這些游離的內存分配。其實,很多內存和性能問題,都逃不過下面要介紹的這些工具的聯合分析。本課時將會結合一個實際的例子,來看一下一個堆外內存的溢出情況,了解常見的套路。 #### 1. 現象 我們有一個服務,非常的奇怪,在某個版本之后,占用的內存開始增長,直到虛擬機分配的內存上限,但是并不會 OOM。如果你開啟了 SWAP,會發現這個應用也會毫不猶豫的將它吞掉,有多少吞多少。 說它的內存增長,是通過 top 命令去觀察的,看它的 RES 列的數值;反之,如果使用 jmap 命令去看內存占用,得到的只是堆的大小,只能看到一小塊可憐的空間。 ![](https://img.kancloud.cn/87/3e/873e087ed9ef38466f5233c5b2491087_1344x534.png) 使用 ps 也能看到相同的效果。我們觀測到,除了虛擬內存比較高,達到了 17GB 以外,實際使用的內存 RSS 也夸張的達到了 7 GB,遠遠超過了 -Xmx 的設定。 ``` [root]$?ps?-p?75?-o?rss,vsz?? RSS????VSZ?7152568?17485844 ``` 使用 jps?查看啟動參數,發現分配了大約 3GB 的堆內存。實際內存使用超出了最大內存設定的一倍還多,這明顯是不正常的,肯定是使用了堆外內存。 #### 2. 模擬程序 為了能夠使用這些工具實際觀測這個內存泄漏的過程,我這里準備了一份小程序。程序將會持續的使用 Java 的 Zip 函數進行壓縮和解壓,這種操作在一些對傳輸性能較高的的場景經常會用到。 程序將會申請 1kb 的隨機字符串,然后持續解壓。為了避免讓操作系統陷入假死狀態,我們每次都會判斷操作系統內存使用率,在達到 60% 的時候,我們將掛起程序;通過訪問 8888 端口,將會把內存閾值提高到 85%。我們將分析這兩個處于相對靜態的虛擬快照。 ``` import?com.sun.management.OperatingSystemMXBean; import?com.sun.net.httpserver.HttpContext; import?com.sun.net.httpserver.HttpServer; import?java.io.*; import?java.lang.management.ManagementFactory; import?java.net.InetSocketAddress; import?java.util.Random; import?java.util.concurrent.ThreadLocalRandom; import?java.util.zip.GZIPInputStream; import?java.util.zip.GZIPOutputStream; /** ?*?@author?xjjdog ?*/ public?class?LeakExample?{ ????/** ?????*?構造隨機的字符串 ?????*/ ????public?static?String?randomString(int?strLength)?{ ????????Random?rnd?=?ThreadLocalRandom.current(); ????????StringBuilder?ret?=?new?StringBuilder(); ????????for?(int?i?=?0;?i?<?strLength;?i++)?{ ????????????boolean?isChar?=?(rnd.nextInt(2)?%?2?==?0); ????????????if?(isChar)?{ ????????????????int?choice?=?rnd.nextInt(2)?%?2?==?0???65?:?97; ????????????????ret.append((char)?(choice?+?rnd.nextInt(26))); ????????????}?else?{ ????????????????ret.append(rnd.nextInt(10)); ????????????} ????????} ????????return?ret.toString(); ????} ????public?static?int?copy(InputStream?input,?OutputStream?output)?throws?IOException?{ ????????long?count?=?copyLarge(input,?output); ????????return?count?>?2147483647L???-1?:?(int)?count; ????} ????public?static?long?copyLarge(InputStream?input,?OutputStream?output)?throws?IOException?{ ????????byte[]?buffer?=?new?byte[4096]; ????????long?count?=?0L; ????????int?n; ????????for?(;?-1?!=?(n?=?input.read(buffer));?count?+=?(long)?n)?{ ????????????output.write(buffer,?0,?n); ????????} ????????return?count; ????} ????public?static?String?decompress(byte[]?input)?throws?Exception?{ ????????ByteArrayOutputStream?out?=?new?ByteArrayOutputStream(); ????????copy(new?GZIPInputStream(new?ByteArrayInputStream(input)),?out); ????????return?new?String(out.toByteArray()); ????} ????public?static?byte[]?compress(String?str)?throws?Exception?{ ????????ByteArrayOutputStream?bos?=?new?ByteArrayOutputStream(); ????????GZIPOutputStream?gzip?=?new?GZIPOutputStream(bos); ????????try?{ ????????????gzip.write(str.getBytes()); ????????????gzip.finish(); ????????????byte[]?b?=?bos.toByteArray(); ????????????return?b; ????????}finally?{ ????????????try?{?gzip.close();?}catch?(Exception?ex?){} ????????????try?{?bos.close();?}catch?(Exception?ex?){} ????????} ????} ????private?static?OperatingSystemMXBean?osmxb?=?(OperatingSystemMXBean)?ManagementFactory.getOperatingSystemMXBean(); ????public?static?int?memoryLoad()?{ ????????double?totalvirtualMemory?=?osmxb.getTotalPhysicalMemorySize(); ????????double?freePhysicalMemorySize?=?osmxb.getFreePhysicalMemorySize(); ????????double?value?=?freePhysicalMemorySize?/?totalvirtualMemory; ????????int?percentMemoryLoad?=?(int)?((1?-?value)?*?100); ????????return?percentMemoryLoad; ????} ????private?static?volatile?int?RADIO?=?60; ????public?static?void?main(String[]?args)?throws?Exception?{ ????????HttpServer?server?=?HttpServer.create(new?InetSocketAddress(8888),?0); ????????HttpContext?context?=?server.createContext("/"); ????????context.setHandler(exchange?->?{ ????????????try?{ ????????????????RADIO?=?85; ????????????????String?response?=?"OK!"; ????????????????exchange.sendResponseHeaders(200,?response.getBytes().length); ????????????????OutputStream?os?=?exchange.getResponseBody(); ????????????????os.write(response.getBytes()); ????????????????os.close(); ????????????}?catch?(Exception?ex)?{ ????????????} ????????}); ????????server.start(); ????????//1kb ????????int?BLOCK_SIZE?=?1024; ????????String?str?=?randomString(BLOCK_SIZE?/?Byte.SIZE); ????????byte[]?bytes?=?compress(str); ????????for?(;?;?)?{ ????????????int?percent?=?memoryLoad(); ????????????if?(percent?>?RADIO)?{ ????????????????Thread.sleep(1000); ????????????}?else?{ ????????????????decompress(bytes); ????????????????Thread.sleep(1); ????????????} ``` 程序將使用下面的命令行進行啟動。為了簡化問題,這里省略了一些無關的配置。 ``` java?-Xmx1G?-Xmn1G?-XX:+AlwaysPreTouch??-XX:MaxMetaspaceSize=10M?-XX:MaxDirectMemorySize=10M?-XX:NativeMemoryTracking=detail?LeakExample ``` #### 3. NMT 首先介紹一下上面的幾個 JVM 參數,分別使用 Xmx、MaxMetaspaceSize、MaxDirectMemorySize 這三個參數限制了堆、元空間、直接內存的大小。 然后,使用 AlwaysPreTouch 參數。其實,通過參數指定了 JVM 大小,只有在 JVM 真正使用的時候,才會分配給它。這個參數,在 JVM 啟動的時候,就把它所有的內存在操作系統分配了。在堆比較大的時候,會加大啟動時間,但在這個場景中,我們為了減少內存動態分配的影響,把這個值設置為 True。 接下來的 NativeMemoryTracking,是用來追蹤 Native 內存的使用情況。通過在啟動參數上加入 -XX:NativeMemoryTracking=detail 就可以啟用。使用 jcmd 命令,就可查看內存分配。 ``` jcmd?$pid??VM.native_memory?summary ``` 我們在一臺 4GB 的虛擬機上使用上面的命令。啟動程序之后,發現進程使用的內存迅速升到 2.4GB。 ``` #?jcmd?2154??VM.native_memory?summary 2154: Native?Memory?Tracking: Total:?reserved=2370381KB,?committed=1071413KB -?????????????????Java?Heap?(reserved=1048576KB,?committed=1048576KB) ????????????????????????????(mmap:?reserved=1048576KB,?committed=1048576KB) -?????????????????????Class?(reserved=1056899KB,?committed=4995KB) ????????????????????????????(classes?#432) ????????????????????????????(malloc=131KB?#328) ????????????????????????????(mmap:?reserved=1056768KB,?committed=4864KB) -????????????????????Thread?(reserved=10305KB,?committed=10305KB) ????????????????????????????(thread?#11) ????????????????????????????(stack:?reserved=10260KB,?committed=10260KB) ????????????????????????????(malloc=34KB?#52) ????????????????????????????(arena=12KB?#18) -??????????????????????Code?(reserved=249744KB,?committed=2680KB) ????????????????????????????(malloc=144KB?#502) ????????????????????????????(mmap:?reserved=249600KB,?committed=2536KB) -????????????????????????GC?(reserved=2063KB,?committed=2063KB) ????????????????????????????(malloc=7KB?#80) ????????????????????????????(mmap:?reserved=2056KB,?committed=2056KB) -??????????????????Compiler?(reserved=138KB,?committed=138KB) ????????????????????????????(malloc=8KB?#38) ????????????????????????????(arena=131KB?#5) -??????????????????Internal?(reserved=789KB,?committed=789KB) ????????????????????????????(malloc=757KB?#1272) ????????????????????????????(mmap:?reserved=32KB,?committed=32KB) -????????????????????Symbol?(reserved=1535KB,?committed=1535KB) ????????????????????????????(malloc=983KB?#114) ????????????????????????????(arena=552KB?#1) -????Native?Memory?Tracking?(reserved=159KB,?committed=159KB) ????????????????????????????(malloc=99KB?#1399) ????????????????????????????(tracking?overhead=60KB) -???????????????Arena?Chunk?(reserved=174KB,?committed=174KB) ????????????????????????????(mall ``` 可惜的是,這個名字讓人振奮的工具并不能如它描述的一樣,看到我們這種泄漏的場景。下圖這點小小的空間,是不能和 2GB 的內存占用相比的。 ![](https://img.kancloud.cn/0a/ff/0affefc94c7e028beccebe8fb3dda280_834x256.png) NMT 能看到堆內內存、Code 區域或者使用 unsafe.allocateMemory 和 DirectByteBuffer 申請的堆外內存,雖然是個好工具但問題并不能解決。 使用 jmap 工具,dump 一份堆快照,然后使用 MAT 分析,依然不能找到這部分內存。 #### 4. pmap 像是 EhCache 這種緩存框架,提供了多種策略,可以設定將數據存儲在非堆上,我們就是要排查這些影響因素。如果能夠在代碼里看到這種可能性最大的代碼塊,是最好的。 為了進一步分析問題,我們使用 pmap 命令查看進程的內存分配,通過 RSS 升序序排列。結果發現除了地址 00000000c0000000 上分配的 1GB 堆以外(也就是我們的堆內存),還有數量非常多的 64M 一塊的內存段,還有巨量小的物理內存塊映射到不同的虛擬內存段上。但到現在為止,我們不知道里面的內容是什么,是通過什么產生的。 ``` #?pmap?-x?2154??|?sort?-n?-k3 Address???????????Kbytes?????RSS???Dirty?Mode??Mapping ----------------?-------?-------?------- 0000000100080000?1048064???????0???????0?-----???[?anon?] 00007f2d4fff1000??????60???????0???????0?-----???[?anon?] 00007f2d537fb000????8212???????0???????0?-----???[?anon?] 00007f2d57ff1000??????60???????0???????0?-----???[?anon?] .....省略N行 00007f2e3c000000???65524???22064???22064?rw---???[?anon?] 00007f2e00000000???65476???22068???22068?rw---???[?anon?] 00007f2e18000000???65476???22072???22072?rw---???[?anon?] 00007f2e30000000???65476???22076???22076?rw---???[?anon?] 00007f2dc0000000???65520???22080???22080?rw---???[?anon?] 00007f2dd8000000???65520???22080???22080?rw---???[?anon?] 00007f2da8000000???65524???22088???22088?rw---???[?anon?] 00007f2e8c000000???65528???22088???22088?rw---???[?anon?] 00007f2e64000000???65520???22092???22092?rw---???[?anon?] 00007f2e4c000000???65520???22096???22096?rw---???[?anon?] 00007f2e7c000000???65520???22096???22096?rw---???[?anon?] 00007f2ecc000000???65520???22980???22980?rw---???[?anon?] 00007f2d84000000???65476???23368???23368?rw---???[?anon?] 00007f2d9c000000??131060???43932???43932?rw---???[?anon?] 00007f2d50000000???57324???56000???56000?rw---???[?anon?] 00007f2d4c000000???65476???64160???64160?rw---???[?anon?] 00007f2d5c000000???65476???64164???64164?rw---???[?anon?] 00007f2d64000000???65476???64164???64164?rw---???[?anon?] 00007f2d54000000???65476???64168???64168?rw---???[?anon?] 00007f2d7c000000???65476???64168???64168?rw---???[?anon?] 00007f2d60000000???65520???64172???64172?rw---???[?anon?] 00007f2d6c000000???65476???64172???64172?rw---???[?anon?] 00007f2d74000000???65476???64172???64172?rw---???[?anon?] 00007f2d78000000???65520???64176???64176?rw---???[?anon?] 00007f2d68000000???65520???64180???64180?rw---???[?anon?] 00007f2d80000000???65520???64184???64184?rw---???[?anon?] 00007f2d58000000???65520???64188???64188?rw---???[?anon?] 00007f2d70000000???65520???64192???64192?rw---???[?anon?] 00000000c0000000?1049088?1049088?1049088?rw---???[?anon?] total?kB?????????8492740?3511008?3498584 ``` 通過 Google,找到以下資料 Linux glibc >= 2.10 (RHEL 6) malloc may show excessive virtual memory usage)?。 文章指出造成應用程序大量申請 64M 大內存塊的原因是由 Glibc 的一個版本升級引起的,通過 export MALLOC_ARENA_MAX=4 可以解決 VSZ 占用過高的問題。雖然這也是一個問題,但卻不是我們想要的,因為我們增長的是物理內存,而不是虛擬內存,程序在這一方面表現是正常的。 #### 5. gdb 非常好奇 64M 或者其他小內存塊中是什么內容,接下來可以通過 gdb?工具將其 dump 出來。 讀取 /proc 目錄下的 maps 文件,能精準地知曉目前進程的內存分布。以下腳本通過傳入進程 id,能夠將所關聯的內存全部 dump 到文件中。注意,這個命令會影響服務,要慎用。 ``` pid=$1;grep?rw-p?/proc/$pid/maps?|?sed?-n?'s/^\([0-9a-f]*\)-\([0-9a-f]*\)?.*$/\1?\2/p'?|?while?read?start?stop;?do?gdb?--batch?--pid?$pid?-ex?"dump?memory?$1-$start-$stop.dump?0x$start?0x$stop";?done ``` 這個命令十分霸道,甚至把加載到內存中的 class 文件、堆文件一塊給 dump 下來。這是機器的原始內存,大多數文件我們打不開。? ![](https://img.kancloud.cn/ef/d4/efd40a067ce2234be785fa62dee83f3f_968x405.png) 更多時候,只需要 dump 一部分內存就可以。再次提醒操作會影響服務,注意 dump 的內存塊大小,線上一定要慎用。 我們復制 pman 的一塊 64M 內存,比如 00007f2d70000000,然后去掉前面的 0,使用下面代碼得到內存塊的開始和結束地址。 ``` cat?/proc/2154/maps?|?grep?7f2d70000000 7f2d6fff1000-7f2d70000000?---p?00000000?00:00?0?7f2d70000000-7f2d73ffc000?rw-p?00000000?00:00?0 ``` 接下來就 dump 這 64MB 的內存。 ``` gdb?--batch?--pid?2154?-ex?"dump?memory?a.dump?0x7f2d70000000?0x7f2d73ffc000" ``` 使用 du 命令查看具體的內存塊大小,不多不少正好 64M。 ``` #?du?-h?a.dump 64M?a.dump ``` 是時候查看里面的內容了,使用 strings 命令可以看到內存塊里一些可以打印的內容。 ``` #?strings?-10?a.dump 0R4f1Qej1ty5GT8V1R8no6T44564wz499E6Y582q2R9h8CC175GJ3yeJ1Q3P5Vt757Mcf6378kM36hxZ5U8uhg2A26T5l7f68719WQK6vZ2BOdH9lH5C7838qf1 ... ``` 等等?這些內容不應該在堆里面么?為何還會使用額外的內存進行分配?那么還有什么地方在分配堆外內存呢? 這種情況,只可能是 native 程序對堆外內存的操作。 #### 6. perf 下面介紹一個神器 perf,除了能夠進行一些性能分析,它還能幫助我們找到相應的 native 調用。這么突出的堆外內存使用問題,肯定能找到相應的調用函數。 使用?perf record -g -p 2154?開啟監控棧函數調用,然后訪問服務器的 8888 端口,這將會把內存使用的閾值增加到 85%,我們的程序會逐漸把這部分內存占滿,你可以 syi。perf 運行一段時間后 Ctrl+C 結束,會生成一個文件 perf.data。 執行 `perf report -i perf.data` 查看報告。? ![](https://img.kancloud.cn/8f/12/8f12dc85b0302d01ca9e8e73d45ebbd2_1200x985.png) 如圖,一般第三方 JNI 程序,或者 JDK 內的模塊,都會調用相應的本地函數,在 Linux 上,這些函數庫的后綴都是 so。 我們依次瀏覽用的可疑資源,發現了“libzip.so”,還發現了不少相關的調用。搜索 zip(輸入 / 進入搜索模式),結果如下: ![](https://img.kancloud.cn/7f/82/7f82728eb49efe1454a7e39edf132d80_700x158.png) 查看 JDK 代碼,發現 bzip 大量使用了 native ?方法。也就是說,有大量內存的申請和銷毀,是在堆外發生的。? ![](https://img.kancloud.cn/f7/b0/f7b0946438d3a3417bee222c38144f6c_960x526.png) 進程調用了Java_java_util_zip_Inflater_inflatBytes() 申請了內存,卻沒有調用 Deflater 釋放內存。與 pmap 內存地址相比對,確實是 zip 在搞鬼。 #### 7. gperftools google 還有一個類似的、非常好用的工具,叫做 gperftools,我們主要用到它的 Heap Profiler,功能更加強大。 它的啟動方式有點特別,安裝成功之后,你只需要輸出兩個環境變量即可。 ``` mkdir?-p?/opt/test? export?LD_PRELOAD=/usr/lib64/libtcmalloc.so? export?HEAPPROFILE=/opt/test/heap ``` 在同一個終端,再次啟動我們的應用程序,可以看到內存申請動作都被記錄到了 opt 目錄下的 test 目錄。 ![](https://img.kancloud.cn/cb/da/cbda783b668883cbf1a1b6889af5b6b8_1588x765.png) 接下來,我們就可以使用 pprof 命令分析這些文件。 ``` cd?/opt/test pprof?-text?*heap??|?head?-n?200 ``` 使用這個工具,能夠一眼追蹤到申請內存最多的函數。Java_java_util_zip_Inflater_init 這個函數立馬就被發現了。 ``` Total:?25205.3?MB ?20559.2??81.6%??81.6%??20559.2??81.6%?inflateBackEnd ??4487.3??17.8%??99.4%???4487.3??17.8%?inflateInit2_ ????75.7???0.3%??99.7%?????75.7???0.3%?os::malloc@8bbaa0 ????70.3???0.3%??99.9%???4557.6??18.1%?Java_java_util_zip_Inflater_init ?????7.1???0.0%?100.0%??????7.1???0.0%?readCEN ?????3.9???0.0%?100.0%??????3.9???0.0%?init ?????1.1???0.0%?100.0%??????1.1???0.0%?os::malloc@8bb8d0 ?????0.2???0.0%?100.0%??????0.2???0.0%?_dl_new_object ?????0.1???0.0%?100.0%??????0.1???0.0%?__GI__dl_allocate_tls ?????0.1???0.0%?100.0%??????0.1???0.0%?_nl_intern_locale_data ?????0.0???0.0%?100.0%??????0.0???0.0%?_dl_check_map_versions ?????0.0???0.0%?100.0%??????0.0???0.0%?__GI___strdup ?????0.0???0.0%?100.0%??????0.1???0.0%?_dl_map_object_deps ?????0.0???0.0%?100.0%??????0.0???0.0%?nss_parse_service_list ?????0.0???0.0%?100.0%??????0.0???0.0%?__new_exitfn ?????0.0???0.0%?100.0%??????0.0???0.0%?getpwuid ?????0.0???0.0%?100.0%??????0.0???0.0%?expand_dynamic_string_token ``` #### 8. 解決 這就是我們模擬內存泄漏的整個過程,到此問題就解決了。 GZIPInputStream 使用 Inflater 申請堆外內存、Deflater 釋放內存,調用 close() 方法來主動釋放。如果忘記關閉,Inflater 對象的生命會延續到下一次 GC,有一點類似堆內的弱引用。在此過程中,堆外內存會一直增長。 把 decompress 函數改成如下代碼,重新編譯代碼后觀察,問題解決。 ``` public?static?String?decompress(byte[]?input)?throws?Exception?{ ????????ByteArrayOutputStream?out?=?new?ByteArrayOutputStream(); ????????GZIPInputStream?gzip?=?new?GZIPInputStream(new?ByteArrayInputStream(input)); ????????try?{ ????????????copy(gzip,?out); ????????????return?new?String(out.toByteArray()); ????????}finally?{ ????????????try{?gzip.close();?}catch?(Exception?ex){} ????????????try{?out.close();?}catch?(Exception?ex){} ????????} ????} ``` #### 9. 小結 本課時使用了非常多的工具和命令來進行堆外內存的排查,可以看到,除了使用 jmap 獲取堆內內存,還對堆外內存的獲取也有不少辦法。 現在,我們可以把堆外內存進行更加細致地劃分了。 元空間屬于堆外內存,主要是方法區和常量池的存儲之地,使用數“MaxMetaspaceSize”可以限制它的大小,我們也能觀測到它的使用。 直接內存主要是通過 DirectByteBuffer 申請的內存,可以使用參數“MaxDirectMemorySize”來限制它的大小(參考第 10 課時)。 其他堆外內存,主要是指使用了 Unsafe 或者其他 JNI 手段直接直接申請的內存。這種情況,就沒有任何參數能夠阻擋它們,要么靠它自己去釋放一些內存,要么等待操作系統對它的審判了。 還有一種情況,和內存的使用無關,但是也會造成內存不正常使用,那就是使用了 Process 接口,直接調用了外部的應用程序,這些程序對操作系統的內存使用一般是不可預知的。 本課時介紹的一些工具,很多高級研發,包括一些面試官,也是不知道的;即使了解這個過程,不實際操作一遍,也很難有深刻的印象。通過這個例子,你可以看到一個典型的堆外內存問題的排查思路。 堆外內存的泄漏是非常嚴重的,它的排查難度高、影響大,甚至會造成宿主機的死亡。在排查內存問題時,不要忘了這一環。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看