欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

了解Java虛擬機(jī)JVM的基本結(jié)構(gòu)及JVM的內(nèi)存溢出方式

 更新時(shí)間:2016年01月18日 08:54:04   作者:cdai  
這篇文章主要介紹了Java虛擬機(jī)JVM的基本結(jié)構(gòu)及JVM的內(nèi)存溢出方式,涉及到Java內(nèi)存分配相關(guān)方面的知識(shí),需要的朋友可以參考下

JVM內(nèi)部結(jié)構(gòu)圖

201611885003662.png (720×540)

Java虛擬機(jī)主要分為五個(gè)區(qū)域:方法區(qū)、堆、Java棧、PC寄存器、本地方法棧。下面
來看一些關(guān)于JVM結(jié)構(gòu)的重要問題。

1.哪些區(qū)域是共享的?哪些是私有的?

Java棧、本地方法棧、程序計(jì)數(shù)器是隨用戶線程的啟動(dòng)和結(jié)束而建立和銷毀的,
每個(gè)線程都有獨(dú)立的這些區(qū)域。而方法區(qū)、堆是被整個(gè)JVM進(jìn)程中的所有線程共享的。

201611885033514.png (720×540)

2.方法區(qū)保存什么?會(huì)被回收嗎?

方法區(qū)不是只保存的方法信息和代碼,同時(shí)在一塊叫做運(yùn)行時(shí)常量池的子區(qū)域還
保存了Class文件中常量表中的各種符號(hào)引用,以及翻譯出來的直接引用。通過堆中
的一個(gè)Class對(duì)象作為接口來訪問這些信息。

雖然方法區(qū)中保存的是類型信息,但是也是會(huì)被回收的,只不過回收的條件比較苛刻:

(1)該類的所有實(shí)例都已經(jīng)被回收

(2)加載該類的ClassLoader已經(jīng)被回收

(3)該類的Class對(duì)象沒有在任何地方被引用(包括Class.forName反射訪問)


3.方法區(qū)中常量池的內(nèi)容不變嗎?

方法區(qū)中的運(yùn)行時(shí)常量池保存了Class文件中靜態(tài)常量池中的數(shù)據(jù)。除了存放這些編譯時(shí)
生成的各種字面量和符號(hào)引用外,還包含了翻譯出來的直接引用。但這不代表運(yùn)行時(shí)常量池
就不會(huì)改變。比如運(yùn)行時(shí)可以調(diào)用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.所有的對(duì)象實(shí)例都在堆上分配嗎?

隨著逃逸分析技術(shù)的逐漸成熟,棧上分配、標(biāo)量替換優(yōu)化技術(shù)使得“所有對(duì)象都分配
在堆上”也變得不那么絕對(duì)。

所謂逃逸就是當(dāng)一個(gè)對(duì)象的指針被多個(gè)方法或線程引用時(shí),我們稱這個(gè)指針發(fā)生逃逸。
一般來說,Java對(duì)象是在堆里分配的,在棧中只保存了對(duì)象的指針。假設(shè)一個(gè)局部變量
在方法執(zhí)行期間未發(fā)生逃逸(暴露給方法外),則直接在棧里分配,之后繼續(xù)在調(diào)用棧
里執(zhí)行,方法執(zhí)行結(jié)束后棧空間被回收,局部變量就也被回收了。這樣就減少了大量臨時(shí)
對(duì)象在堆中分配,提高了GC回收的效率。

另外,逃逸分析也會(huì)對(duì)未發(fā)生逃逸的局部變量進(jìn)行鎖省略,將該變量上擁有的鎖省略掉。
啟用逃逸分析的方法時(shí)加上JVM啟動(dòng)參數(shù):-XX:+DoEscapeAnalysis?EscapeAnalysisTest。


5.訪問堆上的對(duì)象有幾種方式?

(1)指針直接訪問

棧上的引用保存的就是指向堆上對(duì)象的指針,一次就可以定位對(duì)象,訪問速度比較快。
但是當(dāng)對(duì)象在堆中被移動(dòng)時(shí)(垃圾回收時(shí)會(huì)經(jīng)常移動(dòng)各個(gè)對(duì)象),棧上的指針變量的值
也需要改變。目前JVM HotSpot采用的是這種方式。

201611885114325.png (750×339)

(2)句柄間接訪問

棧上的引用指向的是句柄池中的一個(gè)句柄,通過這個(gè)句柄中的值再訪問對(duì)象。因此句柄
就像二級(jí)指針,需要兩次定位才能訪問到對(duì)象,速度比直接指針定位要慢一些,但是當(dāng)
對(duì)象在堆中的位置移動(dòng)時(shí),不需要改變棧上引用的值。

201611885135188.png (750×345)


JVM內(nèi)存溢出的方式
了解了Java虛擬機(jī)五個(gè)內(nèi)存區(qū)域的作用后,下面我們來繼續(xù)學(xué)習(xí)下在什么情況下
這些區(qū)域會(huì)發(fā)生溢出。

1.虛擬機(jī)參數(shù)配置

-Xms:初始堆大小,默認(rèn)為物理內(nèi)存的1/64(<1GB);默認(rèn)(MinHeapFreeRatio參數(shù)可以調(diào)整)空余堆內(nèi)存小于40%時(shí),JVM就會(huì)增大堆直到-Xmx的最大限制。

-Xmx:最大堆大小,默認(rèn)(MaxHeapFreeRatio參數(shù)可以調(diào)整)空余堆內(nèi)存大于70%時(shí),JVM會(huì)減少堆直到 -Xms的最小限制。

-Xss:每個(gè)線程的堆棧大小。JDK5.0以后每個(gè)線程堆棧大小為1M,以前每個(gè)線程堆棧大小為256K。應(yīng)根據(jù)應(yīng)用的線程所需內(nèi)存大小進(jìn)行適當(dāng)調(diào)整。在相同物理內(nèi)存下,減小這個(gè)值能生成更多的線程。但是操作系統(tǒng)對(duì)一個(gè)進(jìn)程內(nèi)的線程數(shù)還是有限制的,不能無限生成,經(jīng)驗(yàn)值在3000~5000左右。一般小的應(yīng)用, 如果棧不是很深, 應(yīng)該是128k夠用的,大的應(yīng)用建議使用256k。這個(gè)選項(xiàng)對(duì)性能影響比較大,需要嚴(yán)格的測試。

-XX:PermSize:設(shè)置永久代(perm gen)初始值。默認(rèn)值為物理內(nèi)存的1/64。

-XX:MaxPermSize:設(shè)置持久代最大值。物理內(nèi)存的1/4。


2.方法區(qū)溢出

因?yàn)榉椒▍^(qū)是保存類的相關(guān)信息的,所以當(dāng)我們加載過多的類時(shí)就會(huì)導(dǎo)致方法區(qū)
溢出。在這里我們通過JDK動(dòng)態(tài)代理和CGLIB代理兩種方式來試圖使方法區(qū)溢出。

2.1 JDK動(dòng)態(tài)代理

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); 
    } 
  } 
 
} 

雖然我們不斷調(diào)用Proxy.newInstance()方法來創(chuàng)建代理類,但是JVM并沒有內(nèi)存溢出。
每次調(diào)用都生成了不同的代理類實(shí)例,但是代理類的Class對(duì)象沒有改變。是不是Proxy
類對(duì)代理類的Class對(duì)象有緩存?具體原因會(huì)在之后的《JDK動(dòng)態(tài)代理與CGLIB》中進(jìn)行
詳細(xì)分析。

2.2 CGLIB代理

CGLIB同樣會(huì)緩存代理類的Class對(duì)象,但是我們可以通過配置讓它不緩存Class對(duì)象,
這樣就可以通過反復(fù)創(chuàng)建代理類達(dá)到使方法區(qū)溢出的目的。

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.堆溢出

堆溢出比較簡單,只需通過創(chuàng)建一個(gè)大數(shù)組對(duì)象來申請一塊比較大的內(nèi)存,就可以使
堆發(fā)生溢出。

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.棧溢出

棧溢出也比較常見,有時(shí)我們編寫的遞歸調(diào)用沒有正確的終止條件時(shí),就會(huì)使方法不斷
遞歸,棧的深度不斷增大,最終發(fā)生棧溢出。

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(); 
    } 
  } 
   
} 

相關(guān)文章

  • eclipse啟動(dòng)出現(xiàn)“failed to load the jni shared library”問題解決

    eclipse啟動(dòng)出現(xiàn)“failed to load the jni shared library”問題解決

    這篇文章主要介紹了eclipse啟動(dòng)出現(xiàn)“failed to load the jni shared library”問題解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-11-11
  • 使用JSCH框架通過跳轉(zhuǎn)機(jī)訪問其他節(jié)點(diǎn)的方法

    使用JSCH框架通過跳轉(zhuǎn)機(jī)訪問其他節(jié)點(diǎn)的方法

    下面小編就為大家分享一篇使用JSCH框架通過跳轉(zhuǎn)機(jī)訪問其他節(jié)點(diǎn)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2017-12-12
  • springcloud feign傳輸List的坑及解決

    springcloud feign傳輸List的坑及解決

    這篇文章主要介紹了springcloud feign傳輸List的坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • SpringBoot+RabbitMQ?實(shí)現(xiàn)死信隊(duì)列的示例

    SpringBoot+RabbitMQ?實(shí)現(xiàn)死信隊(duì)列的示例

    本文主要介紹了SpringBoot+RabbitMQ?實(shí)現(xiàn)死信隊(duì)列的示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • 詳解SpringBoot+Mybatis實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換

    詳解SpringBoot+Mybatis實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換

    這篇文章主要介紹了詳解SpringBoot+Mybatis實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源切換,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-05-05
  • 談?wù)凧ava中try-catch-finally中的return語句

    談?wù)凧ava中try-catch-finally中的return語句

    我們知道return語句用在某一個(gè)方法中,一是用于返回函數(shù)的執(zhí)行結(jié)果,二是用于返回值為void類型的函數(shù)中,僅僅是一個(gè)return語句(return ;),此時(shí)用于結(jié)束方法的執(zhí)行,也即此return后的語句將不會(huì)被執(zhí)行,當(dāng)然,這種情況下return語句后不能再有其它的語句了
    2016-01-01
  • Java實(shí)現(xiàn)DES加密與解密,md5加密以及Java實(shí)現(xiàn)MD5加密解密類

    Java實(shí)現(xiàn)DES加密與解密,md5加密以及Java實(shí)現(xiàn)MD5加密解密類

    這篇文章主要介紹了Java實(shí)現(xiàn)DES加密與解密,md5加密以及Java實(shí)現(xiàn)MD5加密解密類 ,需要的朋友可以參考下
    2015-11-11
  • Java?限制前端重復(fù)請求的實(shí)例代碼

    Java?限制前端重復(fù)請求的實(shí)例代碼

    這篇文章主要介紹了Java?限制前端重復(fù)請求,文中給大家提到了JAVA利用自定義本地鎖解決重復(fù)提交的問題,通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2022-08-08
  • MyBatis查詢無記錄時(shí)的返回值問題

    MyBatis查詢無記錄時(shí)的返回值問題

    這篇文章主要介紹了MyBatis查詢無記錄時(shí)的返回值問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Java MeteoInfo解析與繪圖代碼教程詳解

    Java MeteoInfo解析與繪圖代碼教程詳解

    這篇文章主要介紹了Java MeteoInfo解析與繪圖代碼教程,對(duì)于后端導(dǎo)出圖片的話,就需要添加色階了,這一文很簡單,就涉及色階,名稱,網(wǎng)格刻度線,感興趣的朋友一起看看吧
    2021-10-10

最新評(píng)論