<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>

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                本課時我們主要自己模擬一個 JVM 內存溢出的場景。在模擬 JVM 內存溢出之前我們先來看下這樣的幾個問題。 * 老年代溢出為什么那么可怕? * 元空間也有溢出?怎么優化? * 如何配置棧大小?避免棧溢出? * 進程突然死掉,沒有留下任何信息時如何進行排查? 年輕代由于有老年代的擔保,一般在內存占滿的時候,并沒什么問題。但老年代滿了就比較嚴重了,它沒有其他的空間用來做擔保,只能 OOM 了,也就是發生 Out Of Memery Error。JVM 會在這種情況下直接停止工作,是非常嚴重的后果。 OOM 一般是內存泄漏引起的,表現在 GC 日志里,一般情況下就是 GC 的時間變長了,而且每次回收的效果都非常一般。GC 后,堆內存的實際占用呈上升趨勢。接下來,我們將模擬三種溢出場景,同時使用我們了解的工具進行觀測。 在開始之前,請你下載并安裝一個叫作 VisualVM 的工具,我們使用這個圖形化的工具看一下溢出過程。 雖然 VisualVM 工具非常好用,但一般生產環境都沒有這樣的條件,所以大概率使用不了。新版本 JDK 把這個工具單獨抽離了出去,需要自行下載。 這里需要注意下載安裝完成之后請在插件選項中勾選 Visual GC 下載,它將可視化內存布局。 #### 堆溢出模擬 首先,我們模擬堆溢出的情況,在模擬之前我們需要準備一份測試代碼。這份代碼開放了一個 HTTP 接口,當你觸發它之后,將每秒鐘生成 1MB 的數據。由于它和 GC Roots 的強關聯性,每次都不能被回收。 程序通過 JMX,將在每一秒創建數據之后,輸出一些內存區域的占用情況。然后通過訪問 http://localhost:8888 觸發后,它將一直運行,直到堆溢出。 ``` import?com.sun.net.httpserver.HttpContext; import?com.sun.net.httpserver.HttpExchange; import?com.sun.net.httpserver.HttpServer; import?java.io.OutputStream; import?java.lang.management.ManagementFactory; import?java.lang.management.MemoryPoolMXBean; import?java.net.InetSocketAddress; import?java.util.ArrayList; import?java.util.List; public?class?OOMTest?{ ???public?static?final?int?_1MB?=?1024?*?1024; ???static?List<byte[]>?byteList?=?new?ArrayList<>(); ???private?static?void?oom(HttpExchange?exchange)?{ ???????try?{ ???????????String?response?=?"oom?begin!"; ???????????exchange.sendResponseHeaders(200,?response.getBytes().length); ???????????OutputStream?os?=?exchange.getResponseBody(); ???????????os.write(response.getBytes()); ???????????os.close(); ???????}?catch?(Exception?ex)?{ ???????} ???????for?(int?i?=?0;?;?i++)?{ ???????????byte[]?bytes?=?new?byte[_1MB]; ???????????byteList.add(bytes); ???????????System.out.println(i?+?"MB"); ???????????memPrint(); ???????????try?{ ???????????????Thread.sleep(1000); ???????????}?catch?(Exception?e)?{ ???????????} ???????} ???} ???static?void?memPrint()?{ ???????for?(MemoryPoolMXBean?memoryPoolMXBean?:?ManagementFactory.getMemoryPoolMXBeans())?{ ???????????System.out.println(memoryPoolMXBean.getName()?+ ???????????????????"??committed:"?+?memoryPoolMXBean.getUsage().getCommitted()?+ ???????????????????"??used:"?+?memoryPoolMXBean.getUsage().getUsed()); ???????} ???} ???private?static?void?srv()?throws?Exception?{ ???????HttpServer?server?=?HttpServer.create(new?InetSocketAddress(8888),?0); ???????HttpContext?context?=?server.createContext("/"); ???????context.setHandler(OOMTest::oom); ???????server.start(); ???} ???public?static?void?main(String[]?args)?throws?Exception{ ???????srv(); ???} } ``` 我們使用 CMS 收集器進行垃圾回收,可以看到如下的信息。 命令: ``` java -Xmx20m? -Xmn4m? ?-XX:+UseConcMarkSweepGC? -verbose:gc -Xlog:gc, gc+ref=debug,gc+heap=debug, gc+age=trace:file=/tmp/logs/gc_%p.log:tags, uptime, time, level -Xlog:safepoint:file=/tmp/logs/safepoint_%p.log:tags, uptime, time, level -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/logs -XX:ErrorFile=/tmp/logs/hs_error_pid%p.log -XX:-OmitStackTraceInFastThrow? OOMTest ``` 輸出: ``` [0.025s][info][gc] Using Concurrent Mark Sweep 0MB CodeHeap 'non-nmethods' ?committed:2555904 ?used:1120512 Metaspace ?committed:4980736 ?used:854432 CodeHeap 'profiled nmethods' ?committed:2555904 ?used:265728 Compressed Class Space ?committed:524288 ?used:96184 Par Eden Space ?committed:3407872 ?used:2490984 Par Survivor Space ?committed:393216 ?used:0 CodeHeap 'non-profiled nmethods' ?committed:2555904 ?used:78592 CMS Old Gen ?committed:16777216 ?used:0 ...省略 [16.377s][info][gc] GC(9) Concurrent Mark 1.592ms [16.377s][info][gc] GC(9) Concurrent Preclean [16.378s][info][gc] GC(9) Concurrent Preclean 0.721ms [16.378s][info][gc] GC(9) Concurrent Abortable Preclean [16.378s][info][gc] GC(9) Concurrent Abortable Preclean 0.006ms [16.378s][info][gc] GC(9) Pause Remark 17M->17M(19M) 0.344ms [16.378s][info][gc] GC(9) Concurrent Sweep [16.378s][info][gc] GC(9) Concurrent Sweep 0.248ms [16.378s][info][gc] GC(9) Concurrent Reset [16.378s][info][gc] GC(9) Concurrent Reset 0.013ms 17MB CodeHeap 'non-nmethods' ?committed:2555904 ?used:1120512 Metaspace ?committed:4980736 ?used:883760 CodeHeap 'profiled nmethods' ?committed:2555904 ?used:422016 Compressed Class Space ?committed:524288 ?used:92432 Par Eden Space ?committed:3407872 ?used:3213392 Par Survivor Space ?committed:393216 ?used:0 CodeHeap 'non-profiled nmethods' ?committed:2555904 ?used:88064 CMS Old Gen ?committed:16777216 ?used:16452312 [18.380s][info][gc] GC(10) Pause Initial Mark 18M->18M(19M) 0.187ms [18.380s][info][gc] GC(10) Concurrent Mark [18.384s][info][gc] GC(11) Pause Young (Allocation Failure) 18M->18M(19M) 0.186ms [18.386s][info][gc] GC(10) Concurrent Mark 5.435ms [18.395s][info][gc] GC(12) Pause Full (Allocation Failure) 18M->18M(19M) 10.572ms [18.400s][info][gc] GC(13) Pause Full (Allocation Failure) 18M->18M(19M) 5.348ms Exception in thread "main" java.lang.OutOfMemoryError: Java heap space ? ?at OldOOM.main(OldOOM.java:20) ``` 最后 JVM 在一陣瘋狂的 GC 日志輸出后,進程停止了。在現實情況中,JVM 在停止工作之前,很多會垂死掙扎一段時間,這個時候,GC 線程會造成 CPU 飆升,但其實它已經不能工作了。 VisualVM 的截圖展示了這個溢出結果。可以看到 Eden 區剛開始還是運行平穩的,內存泄漏之后就開始瘋狂回收(其實是提升),老年代內存一直增長,直到 OOM。 ![](https://img.kancloud.cn/f1/9a/f19ac3b0e0d352e47567a579326aa0ec_999x655.jpg) 很多參數會影響對象的分配行為,但不是非常必要,我們一般不去調整它們。為了觀察這些參數的默認值,我們通常使用 -XX:+PrintFlagsFinal 參數,輸出一些設置信息。 命令: ``` # java -XX:+PrintFlagsFinal 2>&1 | grep SurvivorRatio ? ?uintx SurvivorRatio ? ? ? ? ? ? ? ? ? ? ? ? ? ?= 8 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? {product} {default} ``` Java13 輸出了幾百個參數和默認值,我們通過修改一些參數來觀測一些不同的行為。 **NewRatio** 默認值為 2,表示年輕代是老年代的 1/2。追加參數 “-XX:NewRatio=1”,可以把年輕代和老年代的空間大小調成一樣大。在實踐中,我們一般使用 -Xmn 來設置一個固定值。注意,這兩個參數不要用在 G1 垃圾回收器中。 **SurvivorRatio** 默認值為 8。表示伊甸區和幸存區的比例。在上面的例子中,Eden 的內存大小為:0.8*4MB。S 分區不到 1MB,根本存不下我們的 1MB 數據。 **MaxTenuringThreshold** ?這個值在 CMS 下默認為 6,G1 下默認為 15。這是因為 G1 存在動態閾值計算。這個值和我們前面提到的對象提升有關,如果你想要對象盡量長的時間存在于年輕代,則在 CMS 中,可以把它調整到 15。 ``` java -XX:+PrintFlagsFinal -XX:+UseConcMarkSweepGC 2>&1 | grep MaxTenuringThreshold java -XX:+PrintFlagsFinal -XX:+UseG1GC 2>&1 | grep MaxTenuringThreshold ``` **PretenureSizeThreshold**?這個參數默認值是 0,意味著所有的對象年輕代優先分配。我們把這個值調小一點,再觀測 JVM 的行為。追加參數 -XX:PretenureSizeThreshold=1024,可以看到 VisualVm 中老年代的區域增長。 **TargetSurvivorRatio** 默認值為 50。在動態計算對象提升閾值的時候使用。計算時,會從年齡最小的對象開始累加,如果累加的對象大小大于幸存區的一半,則將當前的對象 age 作為新的閾值,年齡大于此閾值的對象直接進入老年代。工作中不建議調整這個值,如果要調,請調成比 50 大的值。 你可以嘗試著更改其他參數,比如垃圾回收器的種類,動態看一下效果。尤其注意每一項內存區域的內容變動,你會對垃圾回收器有更好的理解。 **UseAdaptiveSizePolicy** ,因為它和 CMS 不兼容,所以 CMS 下默認為 false,但 G1 下默認為 true。這是一個非常智能的參數,它是用來自適應調整空間大小的參數。它會在每次 GC 之后,重新計算 Eden、From、To 的大小。很多人在 Java 8 的一些配置中會見到這個參數,但其實在 CMS 和 G1 中是不需要顯式設置的。 值的注意的是,Java 8 默認垃圾回收器是 Parallel Scavenge,它的這個參數是默認開啟的,有可能會發生把幸存區自動調小的可能,造成一些問題,顯式的設置 SurvivorRatio 可以解決這個問題。 下面這張截圖,是切換到 G1 之后的效果。 ``` java -Xmx20m ? -XX:+UseG1GC ?-verbose:gc -Xlog:gc,gc+ref=debug,gc+heap=debug,gc+age=trace:file=/tmp/logs/gc_%p.log:tags,uptime,time,level -Xlog:safepoint:file=/tmp/logs/safepoint_%p.log:tags,uptime,time,level -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/logs -XX:ErrorFile=/tmp/logs/hs_error_pid%p.log -XX:-OmitStackTraceInFastThrow ?OOMTest ``` ![](https://img.kancloud.cn/ca/98/ca9893f8815a4ece086f9097daa22a52_998x600.jpg) 可以通過下面這個命令調整小堆區的大小,來看一下這個過程。 ``` -XX:G1HeapRegionSize=<N>M ``` #### 元空間溢出 堆一般都是指定大小的,但元空間不是。所以如果元空間發生內存溢出會更加嚴重,會造成操作系統的內存溢出。我們在使用的時候,也會給它設置一個上限 for safe。 元空間溢出主要是由于加載的類太多,或者動態生成的類太多。下面是一段模擬代碼。通過訪問 http://localhost:8888 觸發后,它將會發生元空間溢出。 ``` import?com.sun.net.httpserver.HttpContext; import?com.sun.net.httpserver.HttpExchange; import?com.sun.net.httpserver.HttpServer; import?java.io.OutputStream; import?java.lang.reflect.InvocationHandler; import?java.lang.reflect.Method; import?java.lang.reflect.Proxy; import?java.net.InetSocketAddress; import?java.net.URL; import?java.net.URLClassLoader; import?java.util.HashMap; import?java.util.Map; public?class?MetaspaceOOMTest?{ ???public?interface?Facade?{ ???????void?m(String?input); ???} ???public?static?class?FacadeImpl?implements?Facade?{ ???????@Override ???????public?void?m(String?name)?{ ???????} ???} ???public?static?class?MetaspaceFacadeInvocationHandler?implements?InvocationHandler?{ ???????private?Object?impl; ???????public?MetaspaceFacadeInvocationHandler(Object?impl)?{ ???????????this.impl?=?impl; ???????} ???????@Override ???????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{ ???????????return?method.invoke(impl,?args); ???????} ???} ???private?static?Map<String,?Facade>?classLeakingMap?=?new?HashMap<String,?Facade>(); ???private?static?void?oom(HttpExchange?exchange)?{ ???????try?{ ???????????String?response?=?"oom?begin!"; ???????????exchange.sendResponseHeaders(200,?response.getBytes().length); ???????????OutputStream?os?=?exchange.getResponseBody(); ???????????os.write(response.getBytes()); ???????????os.close(); ???????}?catch?(Exception?ex)?{ ???????} ???????try?{ ???????????for?(int?i?=?0;?;?i++)?{ ???????????????String?jar?=?"file:"?+?i?+?".jar"; ???????????????URL[]?urls?=?new?URL[]{new?URL(jar)}; ???????????????URLClassLoader?newClassLoader?=?new?URLClassLoader(urls); ???????????????Facade?t?=?(Facade)?Proxy.newProxyInstance(newClassLoader, ???????????????????????new?Class<?>[]{Facade.class}, ???????????????????????new?MetaspaceFacadeInvocationHandler(new?FacadeImpl())); ???????????????classLeakingMap.put(jar,?t); ???????????} ???????}?catch?(Exception?e)?{ ???????} ???} ???private?static?void?srv()?throws?Exception?{ ???????HttpServer?server?=?HttpServer.create(new?InetSocketAddress(8888),?0); ???????HttpContext?context?=?server.createContext("/"); ???????context.setHandler(MetaspaceOOMTest::oom); ???????server.start(); ???} ???public?static?void?main(String[]?args)?throws?Exception?{ ???????srv(); ???} } ``` 這段代碼將使用 Java 自帶的動態代理類,不斷的生成新的 class。 ``` java -Xmx20m? -Xmn4m? ?-XX:+UseG1GC? -verbose:gc -Xlog:gc,gc+ref=debug,gc+heap=debug,gc+age=trace:file=/tmp/logs/gc_%p.log:tags,uptime,time,level -Xlog:safepoint:file=/tmp/logs/safepoint_%p.log:tags,uptime,time,level -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/logs -XX:ErrorFile=/tmp/logs/hs_error_pid%p.log -XX:-OmitStackTraceInFastThrow -XX:MetaspaceSize=16M -XX:MaxMetaspaceSize=16M? MetaspaceOOMTest ``` 我們在啟動的時候,限制 Metaspace 空間大小為 16MB。可以看到運行一小會之后,Metaspace 會發生內存溢出。 ``` [6.509s][info][gc] GC(28) Pause Young (Concurrent Start) (Metadata GC Threshold) 9M->9M(20M) 1.186ms [6.509s][info][gc] GC(30) Concurrent Cycle [6.534s][info][gc] GC(29) Pause Full (Metadata GC Threshold) 9M->9M(20M) 25.165ms [6.556s][info][gc] GC(31) Pause Full (Metadata GC Clear Soft References) 9M->9M(20M) 21.136ms [6.556s][info][gc] GC(30) Concurrent Cycle 46.668ms java.lang.OutOfMemoryError: Metaspace Dumping heap to /tmp/logs/java_pid36723.hprof ... Heap dump file created [17362313 bytes in 0.134 secs] ``` ![](https://img.kancloud.cn/e8/ef/e8ef1b998a15c1dd8655d8823e462797_1003x594.jpg) 但假如你把堆 Metaspace 的限制給去掉,會更可怕。它占用的內存會一直增長。 #### 堆外內存溢出 嚴格來說,上面的 Metaspace 也是屬于堆外內存的。但是我們這里的堆外內存指的是 Java 應用程序通過直接方式從操作系統中申請的內存。所以嚴格來說,這里是指直接內存。 程序將通過 ByteBuffer 的 allocateDirect 方法每 1 秒鐘申請 1MB 的直接內存。不要忘了通過鏈接觸發這個過程。 但是,使用 VisualVM 看不到這個過程,使用 JMX 的 API 同樣也看不到。關于這部分內容,我們將在堆外內存排查課時進行詳細介紹。 ``` import?com.sun.net.httpserver.HttpContext; import?com.sun.net.httpserver.HttpExchange; import?com.sun.net.httpserver.HttpServer; import?java.io.OutputStream; import?java.lang.management.ManagementFactory; import?java.lang.management.MemoryPoolMXBean; import?java.net.InetSocketAddress; import?java.nio.ByteBuffer; import?java.util.ArrayList; import?java.util.List; public?class?OffHeapOOMTest?{ ???public?static?final?int?_1MB?=?1024?*?1024; ???static?List<ByteBuffer>?byteList?=?new?ArrayList<>(); ???private?static?void?oom(HttpExchange?exchange)?{ ???????try?{ ???????????String?response?=?"oom?begin!"; ???????????exchange.sendResponseHeaders(200,?response.getBytes().length); ???????????OutputStream?os?=?exchange.getResponseBody(); ???????????os.write(response.getBytes()); ???????????os.close(); ???????}?catch?(Exception?ex)?{ ???????} ???????for?(int?i?=?0;?;?i++)?{ ???????????ByteBuffer?buffer?=?ByteBuffer.allocateDirect(_1MB); ???????????byteList.add(buffer); ???????????System.out.println(i?+?"MB"); ???????????memPrint(); ???????????try?{ ???????????????Thread.sleep(1000); ???????????}?catch?(Exception?e)?{ ???????????} ???????} ???} ???private?static?void?srv()?throws?Exception?{ ???????HttpServer?server?=?HttpServer.create(new?InetSocketAddress(8888),?0); ???????HttpContext?context?=?server.createContext("/"); ???????context.setHandler(OffHeapOOMTest::oom); ???????server.start(); ???} ???public?static?void?main(String[]?args)?throws?Exception?{ ???????srv(); ???} ???static?void?memPrint()?{ ???????for?(MemoryPoolMXBean?memoryPoolMXBean?:?ManagementFactory.getMemoryPoolMXBeans())?{ ???????????System.out.println(memoryPoolMXBean.getName()?+ ???????????????????"??committed:"?+?memoryPoolMXBean.getUsage().getCommitted()?+ ???????????????????"??used:"?+?memoryPoolMXBean.getUsage().getUsed()); ???????} ???} } ``` 通過 top 或者操作系統的監控工具,能夠看到內存占用的明顯增長。為了限制這些危險的內存申請,如果你確定在自己的程序中用到了大量的 JNI 和 JNA 操作,要顯式的設置 MaxDirectMemorySize 參數。 以下是程序運行一段時間拋出的錯誤。 ``` Exception in thread "Thread-2" java.lang.OutOfMemoryError: Direct buffer memory ? ?at java.nio.Bits.reserveMemory(Bits.java:694) ? ?at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123) ? ?at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311) ? ?at OffHeapOOMTest.oom(OffHeapOOMTest.java:27) ? ?at com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:79) ? ?at sun.net.httpserver.AuthFilter.doFilter(AuthFilter.java:83) ? ?at com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:82) ? ?at sun.net.httpserver.ServerImpl$Exchange$LinkHandler.handle(ServerImpl.java:675) ? ?at com.sun.net.httpserver.Filter$Chain.doFilter(Filter.java:79) ? ?at sun.net.httpserver.ServerImpl$Exchange.run(ServerImpl.java:647) ? ?at sun.net.httpserver.ServerImpl$DefaultExecutor.execute(ServerImpl.java:158) ? ?at sun.net.httpserver.ServerImpl$Dispatcher.handle(ServerImpl.java:431) ? ?at sun.net.httpserver.ServerImpl$Dispatcher.run(ServerImpl.java:396) ? ?at java.lang.Thread.run(Thread.java:748) ``` 啟動命令。 ``` java -XX:MaxDirectMemorySize=10M -Xmx10M OffHeapOOMTest ``` #### 棧溢出 還記得我們的虛擬機棧么?棧溢出指的就是這里的數據太多造成的泄漏。通過 -Xss 參數可以設置它的大小。比如下面的命令就是設置棧大小為 128K。 ``` -Xss128K ``` 從這里我們也能了解到,由于每個線程都有一個虛擬機棧。線程的開銷也是要占用內存的。如果系統中的線程數量過多,那么占用內存的大小也是非常可觀的。 棧溢出不會造成 JVM 進程死亡,危害“相對較小”。下面是一個簡單的模擬棧溢出的代碼,只需要遞歸調用就可以了。 ``` public?class?StackOverflowTest?{ ???static?int?count?=?0; ???static?void?a()?{ ???????System.out.println(count); ???????count++; ???????b(); ???} ???static?void?b()?{ ???????System.out.println(count); ???????count++; ???????a(); ???} ???public?static?void?main(String[]?args)?throws?Exception?{ ???????a(); ???} } ``` 運行后,程序直接報錯。 ``` Exception in thread "main" java.lang.StackOverflowError ? ?at java.io.PrintStream.write(PrintStream.java:526) ? ?at java.io.PrintStream.print(PrintStream.java:597) ? ?at java.io.PrintStream.println(PrintStream.java:736) ? ?at StackOverflowTest.a(StackOverflowTest.java:5) ``` 如果你的應用經常發生這種情況,可以試著調大這個值。但一般都是因為程序錯誤引起的,最好檢查一下自己的代碼。 #### 進程異常退出 上面這幾種溢出場景,都有明確的原因和報錯,排查起來也是非常容易的。但是還有一類應用,死亡的時候,靜悄悄的,什么都沒留下。 以下問題已經不止一個同學問了:我的 Java 進程沒了,什么都沒留下,直接蒸發不見了 why?是因為對象太多了么? 這是趣味性和技巧性非常突出的一個問題。讓我們執行 dmesg 命令,大概率會看到你的進程崩潰信息躺在那里。 ![](https://img.kancloud.cn/84/ba/84ba8da9458b13d9965cb4d250bce4aa_803x305.jpg) 為了能看到發生的時間,我們習慣性加上參數 T(dmesg -T)。 這個現象,其實和 Linux 的內存管理有關。由于 Linux 系統采用的是虛擬內存分配方式,JVM 的代碼、庫、堆和棧的使用都會消耗內存,但是申請出來的內存,只要沒真正 access過,是不算的,因為沒有真正為之分配物理頁面。 隨著使用內存越用越多。第一層防護墻就是 SWAP;當 SWAP 也用的差不多了,會嘗試釋放 cache;當這兩者資源都耗盡,殺手就出現了。oom-killer 會在系統內存耗盡的情況下跳出來,選擇性的干掉一些進程以求釋放一點內存。 所以這時候我們的 Java 進程,是操作系統“主動”終結的,JVM 連發表遺言的機會都沒有。這個信息,只能在操作系統日志里查找。 要解決這種問題,首先不能太貪婪。比如一共 8GB 的機器,你把整整 7.5GB 都分配給了 JVM。當操作系統內存不足時,你的 JVM 就可能成為 oom-killer 的獵物。 相對于被動終結,還有一種主動求死的方式。有些同學,會在程序里面做一些判斷,直接調用 System.exit() 函數。 這個函數危險得很,它將強制終止我們的應用,而且什么都不會留下。你應該掃描你的代碼,確保這樣的邏輯不會存在。 再聊一種最初級最常見還經常發生的,會造成應用程序意外死亡的情況,那就是對 Java 程序錯誤的啟動方式。 很多同學對 Linux 不是很熟悉,使用 XShell 登陸之后,調用下面的命令進行啟動。 ``` java com.cn.AA & ``` 這樣調用還算有點意識,在最后使用了“&”號,以期望進程在后臺運行。但可惜的是,很多情況下,隨著 XShell Tab 頁的關閉,或者等待超時,后面的 Java 進程就隨著一塊停止了,很讓人困惑。 正確的啟動方式,就是使用 nohup 關鍵字,或者阻塞在其他更加長命的進程里(比如docker)。 ``` nohup java com.cn.AA & ``` 進程這種靜悄悄的死亡方式,通常會給我們的問題排查帶來更多的困難。 在發生問題時,要確保留下了足夠的證據,來支持接下來的分析。不能喊一句“出事啦”,然后就陷入無從下手的尷尬境地。 通常,我們在關閉服務的時候,會使用“kill -15”,而不是“kill -9”,以便讓服務在臨死之前喘口氣。信號9和15的區別,是面試經常問的一個問題,也是一種非常有效的手段。 #### 小結 本課時我們簡單模擬了堆、元空間、棧的溢出。并使用 VisualVM 觀察了這個過程。 接下來,我們了解到進程靜悄悄消失的三種情況。如果你的應用也這樣消失過,試著這樣找找它。這三種情況也是一個故障排查流程中要考慮的環節,屬于非常重要的邊緣檢查點。相信聰明的你,會將這些情況揉進自己的面試體系去,真正成為自己的實戰經驗。 #### 課后問答 * 1、老師您好,能否把第二個例子詳細說一下,為什么gc是那樣變化?這是我的配置參數-Xmx20m -Xmn4m -XX:+UseG1GC -XX:+PrintGCDetails -XX:G1HeapRegionSize=10M我沒弄清楚,我這樣設置,gc的變化原因 答案:第二個例子?限制元空間(堆外)的參數是MaxMetaspaceSize,沒看到你的設置。另外G1HeapRegionSize大小應該是2的冪,你這么設置它的實際大小是8M。你現在一共才給了堆空間20M,小堆區最多才2個,這些設置參數怎么看都是不合理的。 * 2、我本地執行堆溢出模擬代碼時,發現經過180秒后,程序都沒有拋出OutOfMenoryErr,不知道為什么 答案:你要指定最大堆的大小,否則會默認使用操作系統的2/3 * 3、context.setHandler(OOMTest::oom); 老師您好,這個: : 能稍微說下嗎,想了解一下這個寫法 答案:你指的是雙冒號?這是Java 8中的Lambda寫法之一,意思是方法引用。直接搜索“java 雙冒號”可深入了解。 * 4、進程突然死掉,沒有留下任何信息時如何進行排查 只能依靠dump文件嗎 答案:OOM才會產生DUMP。靜悄悄死亡,這種情況dump文件都不存在,只能依靠dmesg信息和死亡之前的一些監控信息和日志信息。 * 5、“如果幸存區中相同年齡對象大小的和,大于幸存區的一半,大于或等于 age 的對象將會直接進入老年代”應該為小于等于某一年齡的對象大小總和? 答案:感謝提醒,參考代碼share/gc/shared/ageTable.cpp中的compute_tenuring_threshold函數,重新表述如下:從年齡最小的對象開始累加,如果累加的對象大小,大于幸存區的一半,則講當前的對象age將作為新的閾值,年齡大于此閾值的對象直接進入老年代。 * 6、另外“幸存區的一半”最好提一下 TargetSurvivorRatio 這個參數。 答案: 值的注意的是。使用“grep -rn -i --color TargetSurvivorRatio .”搜索(jdk13),可以看到這個參數只影響serial和G1收集器,還稍微影響PLAB緩沖區的大小。
                  <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>

                              哎呀哎呀视频在线观看