懂得Java虛擬機JVM的根本構造及JVM的內存溢出方法。本站提示廣大學習愛好者:(懂得Java虛擬機JVM的根本構造及JVM的內存溢出方法)文章只能為提供參考,不一定能成為您想要的結果。以下是懂得Java虛擬機JVM的根本構造及JVM的內存溢出方法正文
JVM外部構造圖
Java虛擬機重要分為五個區域:辦法區、堆、Java棧、PC存放器、當地辦法棧。上面
來看一些關於JVM構造的主要成績。
1.哪些區域是同享的?哪些是公有的?
Java棧、當地辦法棧、法式計數器是隨用戶線程的啟動和停止而樹立和燒毀的,
每一個線程都有自力的這些區域。而辦法區、堆是被全部JVM過程中的一切線程同享的。
2.辦法區保留甚麼?會被收受接管嗎?
辦法區不是只保留的辦法信息和代碼,同時在一塊叫做運轉經常量池的子區域還
保留了Class文件中常量表中的各類符號援用,和翻譯出來的直接援用。經由過程堆中
的一個Class對象作為接口來拜訪這些信息。
固然辦法區中保留的是類型信息,然則也是會被收受接管的,只不外收受接管的前提比擬刻薄:
(1)該類的一切實例都曾經被收受接管
(2)加載該類的ClassLoader曾經被收受接管
(3)該類的Class對象沒有在任何處所被援用(包含Class.forName反射拜訪)
3.辦法區中常量池的內容不變嗎?
辦法區中的運轉經常量池保留了Class文件中靜態常量池中的數據。除寄存這些編譯時
生成的各類字面量和符號援用外,還包括了翻譯出來的直接援用。但這不代表運轉經常量池
就不會轉變。好比運轉時可以挪用String的intern辦法,將新的字符串常量放入池中。
package com.cdai.jvm; public class RuntimeConstantPool { public static void main(String[] args) { String s1 = new String("hello"); String s2 = new String("hello"); System.out.println("Before intern, s1 == s2: " + (s1 == s2)); s1 = s1.intern(); s2 = s2.intern(); System.out.println("After intern, s1 == s2: " + (s1 == s2)); } }
4.一切的對象實例都在堆上分派嗎?
跟著逃逸剖析技巧的逐步成熟,棧上分派、標量調換優化技巧使得“一切對象都分派
在堆上”也變得不那末相對。
所謂逃逸就是當一個對象的指針被多個辦法或線程援用時,我們稱這個指針產生逃逸。
普通來講,Java對象是在堆裡分派的,在棧中只保留了對象的指針。假定一個部分變量
在辦法履行時代未產生逃逸(裸露給辦法外),則直接在棧裡分派,以後持續在挪用棧
裡履行,辦法履行停止後棧空間被收受接管,部分變量就也被收受接管了。如許就削減了年夜量暫時
對象在堆平分配,進步了GC收受接管的效力。
別的,逃逸剖析也會對未產生逃逸的部分變量停止鎖省略,將該變量上具有的鎖省略失落。
啟用逃逸剖析的辦法時加上JVM啟動參數:-XX:+DoEscapeAnalysis?EscapeAnalysisTest。
5.拜訪堆上的對象有幾種方法?
(1)指針直接拜訪
棧上的援用保留的就是指向堆上對象的指針,一次便可以定位對象,拜訪速度比擬快。
然則當對象在堆中被挪動時(渣滓收受接管時會常常挪動各個對象),棧上的指針變量的值
也須要轉變。今朝JVM HotSpot采取的是這類方法。
(2)句柄直接拜訪
棧上的援用指向的是句柄池中的一個句柄,經由過程這個句柄中的值再拜訪對象。是以句柄
就像二級指針,須要兩次定位能力拜訪到對象,速度比直接指針定位要慢一些,然則當
對象在堆中的地位挪動時,不須要轉變棧上援用的值。
JVM內存溢出的方法
懂得了Java虛擬機五個內存區域的感化後,上面我們來持續進修下在甚麼情形下
這些區域會產生溢出。
1.虛擬機參數設置裝備擺設
-Xms:初始堆年夜小,默許為物理內存的1/64(<1GB);默許(MinHeapFreeRatio參數可以調劑)空余堆內存小於40%時,JVM就會增年夜堆直到-Xmx的最年夜限制。
-Xmx:最年夜堆年夜小,默許(MaxHeapFreeRatio參數可以調劑)空余堆內存年夜於70%時,JVM會削減堆直到 -Xms的最小限制。
-Xss:每一個線程的客棧年夜小。JDK5.0今後每一個線程客棧年夜小為1M,之前每一個線程客棧年夜小為256K。應依據運用的線程所需內存年夜小停止恰當調劑。在雷同物理內存下,減小這個值能生成更多的線程。然則操作體系對一個過程內的線程數照樣無限制的,不克不及無窮生成,經歷值在3000~5000閣下。普通小的運用, 假如棧不是很深, 應當是128k夠用的,年夜的運用建議應用256k。這個選項對機能影響比擬年夜,須要嚴厲的測試。
-XX:PermSize:設置永遠代(perm gen)初始值。默許值為物理內存的1/64。
-XX:MaxPermSize:設置耐久代最年夜值。物理內存的1/4。
2.辦法區溢出
由於辦法區是保留類的相干信息的,所以當我們加載過量的類時就會招致辦法區
溢出。在這裡我們經由過程JDK靜態署理和CGLIB署理兩種方法來試圖使辦法區溢出。
2.1 JDK靜態署理
package com.cdai.jvm.overflow; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class MethodAreaOverflow { static interface OOMInterface { } static class OOMObject implements OOMInterface { } static class OOMObject2 implements OOMInterface { } public static void main(String[] args) { final OOMObject object = new OOMObject(); while (true) { OOMInterface proxy = (OOMInterface) Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(), OOMObject.class.getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Interceptor1 is working"); return method.invoke(object, args); } } ); System.out.println(proxy.getClass()); System.out.println("Proxy1: " + proxy); OOMInterface proxy2 = (OOMInterface) Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(), OOMObject.class.getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("Interceptor2 is working"); return method.invoke(object, args); } } ); System.out.println(proxy2.getClass()); System.out.println("Proxy2: " + proxy2); } } }
固然我們赓續挪用Proxy.newInstance()辦法來創立署理類,然則JVM並沒有內存溢出。
每次挪用都生成了分歧的署理類實例,然則署理類的Class對象沒有轉變。是否是Proxy
類對署理類的Class對象有緩存?詳細緣由會在以後的《JDK靜態署理與CGLIB》中停止
具體剖析。
2.2 CGLIB署理
CGLIB異樣會緩存署理類的Class對象,然則我們可以經由過程設置裝備擺設讓它不緩存Class對象,
如許便可以經由過程重復創立署理類到達使辦法區溢出的目標。
package com.cdai.jvm.overflow; import java.lang.reflect.Method; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class MethodAreaOverflow2 { static class OOMObject { } public static void main(String[] args) { while (true) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(OOMObject.class); enhancer.setUseCache(false); enhancer.setCallback(new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { return method.invoke(obj, args); } }); OOMObject proxy = (OOMObject) enhancer.create(); System.out.println(proxy.getClass()); } } }
3.堆溢出
堆溢出比擬簡略,只需經由過程創立一個年夜數組對象來請求一塊比擬年夜的內存,便可以使
堆產生溢出。
package com.cdai.jvm.overflow; public class HeapOverflow { private static final int MB = 1024 * 1024; @SuppressWarnings("unused") public static void main(String[] args) { byte[] bigMemory = new byte[1024 * MB]; } }
4.棧溢出
棧溢出也比擬罕見,有時我們編寫的遞歸挪用沒有准確的終止前提時,就會使辦法赓續
遞歸,棧的深度赓續增年夜,終究產生棧溢出。
package com.cdai.jvm.overflow; public class StackOverflow { private static int stackDepth = 1; public static void stackOverflow() { stackDepth++; stackOverflow(); } public static void main(String[] args) { try { stackOverflow(); } catch (Exception e) { System.err.println("Stack depth: " + stackDepth); e.printStackTrace(); } } }