相信不少JAVA中高級的同窗在面試的時候會常常碰到一個面試題
你是如何在工做中對JVM調優和排查定位問題的?java
事實上,若是用戶量不大的狀況下,在你的代碼還算正常的狀況下,在工做中除非真正碰到與JVM相關的問題是少之又少,就算碰到了也是由公司的一些大牛去排查解決,那麼咱們又如何積累這方面的經驗呢?下面由衝鍋帶你們一塊兒來實踐JVM的調優吧面試
注意咱們日常所說的JVM調優通常指Java堆,Java虛擬機棧參數調優
先來一段代碼示例,注意筆者用的是IDEA工具,須要配置一下VM options 爲-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError,若是不清楚的百度一下如何配置idea的JVM運行參數
package com.example.demo.jvm; import java.util.ArrayList; import java.util.List; /** * @Author: Wang Chong * @Date: 2019/9/22 9:37 * @Version: V1.0 */ public class HeapOutMemoryTest { static class ChongGuo { } public static void main(String[] args) { List<ChongGuo> chongGuos = new ArrayList<>(); while (true) { chongGuos.add(new ChongGuo()); } } }
運行結果以下:spring
java.lang.OutOfMemoryError: Java heap space Dumping heap to java_pid9352.hprof ... Heap dump file created [28701160 bytes in 0.122 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3210) at java.util.Arrays.copyOf(Arrays.java:3181) at java.util.ArrayList.grow(ArrayList.java:261) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) at java.util.ArrayList.add(ArrayList.java:458) at com.example.demo.jvm.HeapOutMemoryTest.main(HeapOutMemoryTest.java:18) Disconnected from the target VM, address: '127.0.0.1:54599', transport: 'socket'
能夠看到控制檯出現java.lang.OutOfMemoryError: Java heap space的錯誤,這是爲何呢,首先先解釋一下上面的運行參數segmentfault
-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
-Xms20m:設置JVM最小內存爲20m。此值能夠設置與-Xmx相同,以免每次垃圾回收完成後JVM從新分配內存
-Xmx20m:設置JVM最大可用內存20M
-XX:+HeapDumpOnOutOfMemoryError 表示當JVM發生OOM時,自動生成DUMP文件多線程
下面咱們分析一下出錯的緣由,用JProfiler分析一下,打開剛纔生成的名爲java_pid9352.hprof的dump文件。能夠看到根據(InstanceXcount和Size)基本能夠肯定哪一個類的對象出現問題,在上面示例中,能夠是ChongGuo這個實例生在數量的大小已經超過12M,但沒有超過20M,那麼新問題又來了? <font color="#0382AD" >沒到20M爲啥會報堆內存溢出呢?</font>
答案就是JDK8中堆內存中還包括Metaspace,即元內存空間,在元空間出現前JDK1.7以前在JDK7以及其前期的JDK版本號中。堆內存一般被分爲三塊區域Nursery內存(young generation)、長時內存(old generation)、永久內存(Permanent Generation for VM Matedata),以下圖
當中最上一層是年輕代,一個對象被建立之後首先被放到年輕代中的Eden內存中,假設存活期超兩個Survivor以後就會被轉移到長時內存中(Old Generation)中永久內存中存放着對象的方法、變量等元數據信息。經過假設永久內存不夠。咱們就會獲得例如如下錯誤:java.lang.OutOfMemoryError: PermGen
而在JDK8中狀況發生了明顯的變化,就是普通狀況下你都不會獲得這個錯誤,緣由
在於JDK8中把存放元數據中的永久內存從堆內存中移到了本地內存(native memory)
中,JDK8中JVM堆內存結構就變成了例如如下:
若是我啓動VM參數加上:-XX:MaxMetaspaceSize=1m,從新運行一下上面的程序,
Connected to the target VM, address: '127.0.0.1:56433', transport: 'socket' java.lang.OutOfMemoryError: Metaspace Dumping heap to java_pid9232.hprof ... Heap dump file created [1604635 bytes in 0.024 secs] FATAL ERROR in native method: processing of -javaagent failed Exception in thread "main" Disconnected from the target VM, address: '127.0.0.1:56433', transport: 'socket' Process finished with exit code 1
能夠發現報錯信息變成了java.lang.OutOfMemoryError: Metaspace,說明元空間不夠,我改爲到大概4m左右才能知足啓動條件。
在Java虛擬機規範中描述了兩種異常:
StackOverflowError比較好測試,測試代碼以下:
package com.example.demo.jvm; /** * @Author: Wang Chong * @Date: 2019/9/22 19:09 * @Version: V1.0 */ public class StackOverflowTest { /** * 棧大小 */ private int stackLength = 1; /** * 遞歸壓棧 */ public void stackLeak() { stackLength++; stackLeak(); } public static void main(String[] args) { StackOverflowTest stackOverflowTest = new StackOverflowTest(); try { stackOverflowTest.stackLeak(); } catch (Throwable e) { System.out.println("stack length is :" + stackOverflowTest.stackLength); throw e; } } }
運行結果以下:jvm
Exception in thread "main" stack length is :20739 java.lang.StackOverflowError at com.example.demo.jvm.StackOverflowTest.stackLeak(StackOverflowTest.java:20) at com.example.demo.jvm.StackOverflowTest.stackLeak(StackOverflowTest.java:20)
在VM參數-Xss參數未設置的狀況下,該線程的內存支持的棧深度爲20739,該測試結果與機器的內存大小有關,不過上面的第二點如何測試呢?正常來講若是是單線程,則難以測試內存泄露的狀況,那麼多線程呢?咱們看一下如下測試代碼:socket
package com.example.demo.jvm; /** * @Author: Wang Chong * @Date: 2019/9/22 19:09 * @Version: V1.0 */ public class StackOOMTest implements Runnable{ /** * 棧大小 */ private int stackLength = 1; /** * 遞歸壓棧 */ public void stackLeak() { stackLength++; stackLeak(); } public static void main(String[] args) { while (true){ StackOOMTest stackOverflowTest = new StackOOMTest(); new Thread(stackOverflowTest).start(); } } @Override public void run() { stackLeak(); } }
若是系統不假死的狀況下,會出現<font color="red">Exception in thread "main" java.lang.OutOfMemoryError:unable to create new native thread</font>ide
package com.example.demo.jvm; import java.util.ArrayList; import java.util.List; /** * @Author: Wang Chong * @Date: 2019/9/22 19:44 * @Version: V1.0 */ public class RuntimePoolOOMTest { public static void main(String[] args) { List<String> list = new ArrayList<>(); int i = 0; while (true) { list.add(String.valueOf(i).intern()); } } }
結果以下:工具
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3210) at java.util.Arrays.copyOf(Arrays.java:3181) at java.util.ArrayList.grow(ArrayList.java:261) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) at java.util.ArrayList.add(ArrayList.java:458) at com.example.demo.jvm.RuntimePoolOOMTest.main(RuntimePoolOOMTest.java:17) Disconnected from the target VM, address: '127.0.0.1:50253', transport: 'socket'
證實字符常量池已經在Java8中是在堆中分配的。測試
在Java7以前,方法區位於永久代(PermGen),永久代和堆相互隔離,永久代的大小在啓動JVM時能夠設置一個固定值,不可變;Java8仍然保留方法區的概念,只不過實現方式不一樣。取消永久代,方法存放於元空間(Metaspace),元空間仍然與堆不相連,但與堆共享物理內存,邏輯上可認爲在堆中
測試代碼以下,爲快速看出結果,請加入VM參數-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:MaxMetaspaceSize=10m:
package com.example.demo.jvm; import org.springframework.cglib.proxy.Enhancer; import org.springframework.cglib.proxy.MethodInterceptor; /** * @Author: Wang Chong * @Date: 2019/9/22 19:56 * @Version: V1.0 */ public class MethodAreaOOMTest { public static void main(String[] args) { while (true) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> methodProxy.invokeSuper(o, objects)); enhancer.create(); } } static class OOMObject { } }
運行結果以下:
java.lang.OutOfMemoryError: Metaspace Dumping heap to java_pid8816.hprof ... Heap dump file created [6445908 bytes in 0.039 secs] Exception in thread "main" org.springframework.cglib.core.CodeGenerationException: java.lang.reflect.InvocationTargetException-->null at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345) at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:492) at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:114) at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291) at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:480) at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:305) at com.example.demo.jvm.MethodAreaOOMTest.main(MethodAreaOOMTest.java:19) Caused by: java.lang.reflect.InvocationTargetException at sun.reflect.GeneratedMethodAccessor1.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:459) at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:336) ... 6 more Caused by: java.lang.OutOfMemoryError: Metaspace at java.lang.ClassLoader.defineClass1(Native Method) at java.lang.ClassLoader.defineClass(ClassLoader.java:763) ... 11 more Process finished with exit code 1
元空間內存報錯,證實方法區的溢出與元空間相關。
總結以下:
有更多的文章,請關注查看,更有面試寶典相送