《深刻理解Java虛擬機》學習小記一之自動內存管理機制(一)

Java內存區域與內存溢出異常

1、概要

咱們能夠帶着如下幾個問題去學習自動內存管理機制,羅列以下: java

  1. 什麼操做可能致使內存溢出?
  2. 有哪些種類的內存溢出?
  3. 都是在內存的哪些區域溢出?
  4. 垃圾收集有哪些原則?
  5. 有哪些垃圾收集算法及其實現?
  6. 新生代和老年代的回收策略如何?
  7. 各類內存相關的JVM參數是什麼意思?

本文章主要總結問題一、問題2和問題3 算法


2、運行時數據區域

Java虛擬機在執行Java程序的過程當中會把它所管理的內存劃分爲若干個不一樣的數據區域,以下圖所示 數組


其中虛擬機棧、本地方法棧和程序技術器是線程私有的,方法區和堆是線程共享的. ide

2.1程序計數器

做用:當前線程所執行的字節碼的行號指示器 學習

  • 字節碼解釋器工做時經過改變它的值來選取下一條須要執行的字節碼指令
  • 分支、循環、跳轉、異常處理和線程恢復都依賴於它

2.2虛擬機棧

棧的做用:棧用於存儲局部變量表、操做數棧、動態連接和方法出口等信息. spa

其中局部變量表用於存放8種基本數據類型(boolean,byte,char,short,int,float,long,double)和reference類型. .net

reference類型: 線程

  • 指向對象起始地址的引用指針 指針

  • 指向一個表明對象的句柄 調試

  • 指向一條字節碼指令的地址

可拋出兩種異常情況

  • 線程請求的棧深度大於虛擬機所容許的棧深度,拋出StackOverflowError異常

  • 當擴展時沒法申請到足夠的內存時會拋出OutOfMemoryError異常

2.3本地方法棧

與虛擬機棧的做用很是類似.其區別是虛擬機棧執行Java方法服務,而本地方法棧則爲虛擬機使用到的Native方法服務

同時也會拋出StackOverflowError和OutOfMemoryError異常

2.4堆

堆的做用:分配全部的對象實例和數組。能夠拋出OutOfMemoryError異常。

2.5方法區

方法區的做用:用於存儲已被虛擬機加載的類信息(Class)、常量(final修飾)、靜態變量(static)和即時編譯器編譯後的代碼(code)

能夠拋出OutOfMemoryError異常

2.6運行時常量池

屬於方法區的一部分,用於存放編譯期生成的各類字面量和符號引用(在之後介紹Class結構會講到),在類加載後存放到方法區的運行時常量池中。可拋出OutOfMemoryError異常

3、對象訪問

主流的兩種訪問方式:使用句柄和直接指針。(HotSpot虛擬機就是使用直接指針的訪問方式)

使用句柄訪問


使用直接指針訪問


優缺點比較


4、OutOfMemoryError異常

在Java虛擬機規範的描述中,除了程序計數器外,虛擬機內存的其餘幾個運行時區域都有發生OutOfMemoryError異常的可能.

下面經過若干實例來驗證異常發生的場景.如下代碼的開頭都註釋了執行時所須要設置的虛擬機啓動參數,這些參數對實驗結果有直接影響,請調試代碼的時候不要忽略掉.

4.1Java堆溢出

堆裏放的是new出來的對象,因此這部分很簡單不斷的new對象就能夠了,可是爲了防止對象new出來以後被GC,因此把對象new出來的對象放到一個List中去便可。爲了有更好的效果,能夠在運行前,調整堆的參數。

import java.util.ArrayList;
import java.util.List;
/**
 * VM Args: -Xms20m -Xms20m - XX:+HeapDumpOnOutOfMemoryError
 * @author Administrator
 *
 */
public class HeapOOM {
       static class OOMObject{}
       public static void main(String[] args) {
            List<OOMObject> list = new ArrayList<HeapOOM.OOMObject>();
             while(true ){
                  list.add( new OOMObject());
            }
      }
}

運行結果

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid3688.hprof ...
Heap dump file created [364376345 bytes in 15.891 secs]

4.2虛擬機棧溢出

  • 在單線程的堆中咱們不斷的讓一個成員變量自增,容納這個變量的單元沒法承受這個變量了,就拋出StackOverflowError了。
  • 能夠開儘可能多的線程,並在每一個線程裏調用native的方法,就天然會拋出 OutOfMemoryError了
/**
 * VM Args: - Xss64k
 * @author Administrator
 *
 */
public class JavaVMStackSOF {
       private int stackLength = 1;
       public void stackLeak(){
             stackLength ++;
            stackLeak();
      }
       public static void main(String[] args) throws Throwable {
            JavaVMStackSOF oom = new JavaVMStackSOF();
             try{
                  oom.stackLeak();
            } catch(Throwable e){
                  System. out.println("Stack length:" + oom.stackLength);
                   throw e;
            }
      }
}

運行結果
Stack length:914
Exception in thread "main" java.lang.StackOverflowError
      at JavaVMStackSOF.stackLeak(   JavaVMStackSOF.java:9 )
      at JavaVMStackSOF.stackLeak(   JavaVMStackSOF.java:10 )
      at JavaVMStackSOF.stackLeak(   JavaVMStackSOF.java:10 )


4.3 方法區溢出

可採用加強Class加載的方式。基本思路是運行時產生大量的類去填滿方法區,直到溢出。
藉助第三方類庫CGLib 直接操做字節碼運行,生成大量的動態類。
import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodProxy;
/**
 * VM Args:- XX:PermSize=10m -XX:MaxPermSize=10m
 * @author Administrator
 *
 */
public class JavaMethodAreaOOM {
       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 proxy.invoke(obj, args);
                        }
                  });
                  enhancer.create();
            }
      }
       static class OOMObject{
            
      }
}

運行結果
java.lang.OutOfMemoryError: PermGen space
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
... 8 more

4.4 運行時常量池溢出

可採用String類的intern方法。
import java.util.ArrayList;
import java.util.List;
/**
 * VM Args:- XX:PermSize=10m -XX:MaxPermSize=10m
 * @author Administrator
 *
 */
public class RuntimeConstantPoolOOM {
       public static void main(String[] args) {
            List<String> list = new ArrayList<String>();
             int i = 0;
             while (true ){
                  list.add(String. valueOf(i++).intern());
            }
      }
}

運行結果
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
      at java.lang.String.intern(   Native Method  )
      at RuntimeConstantPoolOOM.main(   RuntimeConstantPoolOOM.java:14   )

5、小結

主要介紹虛擬機裏面的內存是如何劃分的,哪部分區域、什麼樣的代碼和操做可能致使內存溢出異常。

---------------------------------全文完------------------------------

摘自《深刻理解Java虛擬機》

相關文章
相關標籤/搜索