《深入理解java虛擬機器》學習筆記2——Java記憶體溢位例項

yangxi_001發表於2013-12-04

通過簡單的小例子程式,演示java虛擬機器各部分記憶體溢位情況:

(1).java堆溢位:

Java堆用於儲存例項物件,只要不斷建立物件,並且保證GC Roots到物件之間有引用的可達,避免垃圾收集器回收例項物件,就會在物件數量達到堆最大容量時產生OutOfMemoryError異常。

想要方便快速地產生堆溢位,要使用如下java虛擬機器引數:-Xms10m(最小堆記憶體為10MB),-Xmx10m(最大堆記憶體為10MB,最小堆記憶體和最大堆記憶體相同是為了避免堆動態擴充套件),-XX:+HeapDumpOnOutOfMemoryError可以讓java虛擬機器在出現記憶體溢位時產生當前堆記憶體快照以便進行異常分析。

例子程式碼如下:

[java] view plaincopy
  1. public class HeapOOM{  
  2.     static class OOMObject{  
  3. }  
  4. public static void main(String[] args){  
  5.     List<OOMObject> list = new ArrayList<OOMObject>();  
  6.     while(true){  
  7.     list.add(new OOMObject());  
  8. }  
  9. }  
  10. }  

執行一段時間就會發現產生OutOfMemoryError異常,並且產生了堆記憶體異常dump檔案。

(2).java虛擬機器棧和本地方法棧溢位:

由於Sun的HotSpot虛擬機器不區分java虛擬機器棧和本地方法棧,因此對於HotSpot虛擬機器來說-Xoss引數(設定本地方法棧大小)雖然存在,但是實際上是無效的,棧容量只能由-Xss引數設定。

由於Java虛擬機器棧會出現StackOverflowError和OutOfMemoryError兩種異常,所以分別使用兩個例子演示這兩種情況:

a.java虛擬機器棧深度溢位:

單執行緒的環境下,無論是由於棧幀太大,還是虛擬機器棧容量太小,當記憶體無法再分配的時候,虛擬機器總丟擲StackOverflowError異常。使用-Xss128k將java虛擬機器棧大小設定為128kb,例子程式碼如下:

[java] view plaincopy
  1. public class JavaVMStackOF{  
  2.     private int stackLength = 1;  
  3.     public void stackLeak(){  
  4.         statckLength++;  
  5.         stackLeak();  
  6. }  
  7. public static void main(String[] args){  
  8.     JavaVMStackOF oom = new JavaVMStackOF();  
  9. oom.stackLeak();  
  10. }  
  11. }  

執行一段時間後,產生StackOverflowError異常。Java虛擬機器棧溢位一般會產生在方法遞迴呼叫過多而java虛擬機器棧記憶體不夠的情況下。

b.java虛擬機器棧記憶體溢位:

多執行緒環境下,能夠建立的執行緒最大記憶體=實體記憶體-最大堆記憶體-最大方法區記憶體,在java虛擬機器棧記憶體一定的情況下,單個執行緒佔用的記憶體越大,所能建立的執行緒數目越小,所以在多執行緒條件下很容易產生java虛擬機器棧記憶體溢位的異常。

使用-Xss2m引數設定java虛擬機器棧記憶體大小為2MB,例子程式碼如下:

[java] view plaincopy
  1. public class JavaVMStackOOM{  
  2.     private void dontStop(){  
  3.     while(true){  
  4. }  
  5. }  
  6. public void stackLeakByThread{  
  7.     while(true){  
  8.         Thread t = new Thread(new Runnable(){  
  9.     public void run(){  
  10.     dontStop();  
  11. }  
  12. });  
  13. t.start();  
  14. }  
  15. }   
  16. public static void main(String[] args){  
  17.     JavaVMStackOOM oom = new JavaVMStackOOM();  
  18.     oom. stackLeakByThread();.  
  19. }  
  20. }  

執行一段時間之後,java虛擬機器棧就會因為記憶體太小無法建立執行緒而產生OutOfMemoryError。

(3).執行時常量池溢位:

執行時常量池屬於方法區的一部分,可以使用-XX:PermSize=10m和-XX:MaxPermSize=10m將永久代最大記憶體和最小記憶體設定為10MB大小,並且由於永久代最大記憶體和最小記憶體大小相同,因此無法擴充套件。

String的intern()方法用於檢查常量池中如果有等於此String物件的字串存在,則直接返回常量池中的字串物件,否則,將此String物件所包含的字串新增到執行時常量池中,並返回此String物件的引用。因此String的intern()方法特別適合演示執行時常量池溢位,例子程式碼如下:

[java] view plaincopy
  1. public class RuntimeConstantPoolOOM{  
  2.     public static void main(String[] args){  
  3. List<String> list = new ArrayList<String>();  
  4.         int i = 0;  
  5.         while(true){  
  6.         list.add(String.valueOf(i++).intern());  
  7. }  
  8. }  
  9. }  

執行一段時間,永久代記憶體不夠,執行時常量池因無法再新增常量而產生OutOfMemoryError。

(4).方法區溢位:

執行時常量池是方法區的一部分,他們都屬於HotSpot虛擬機器中的永久代記憶體區域。方法區用於存放Class的相關資訊,Java的反射和動態代理可以動態產生Class,另外第三方的CGLIB可以直接操作位元組碼,也可以動態產生Class,實驗通過CGLIB來演示,同樣使用-XX:PermSize=10m和-XX:MaxPermSize=10m將永久代最大記憶體和最小記憶體設定為10MB大小,並且由於永久代最大記憶體和最小記憶體大小相同,因此無法擴充套件。例子程式碼如下:

[java] view plaincopy
  1. public class JavaMethodAreaOOM{  
  2.     public static void main(String[] args){  
  3.     while(true){  
  4.     Enhancer enhancer = new Enhancer();  
  5.     enhancer.setSuperClass(OOMObject.class);  
  6.     enhancer.setUseCache(false);  
  7.     enhancer.setCallback(new MethodInterceptor(){  
  8.     public Object intercept(Object obj, Method method, Object[] args,   
  9.                       MethodProxy proxy)throws Throwable{  
  10.     return proxy.invokeSuper(obj, args);  
  11. }  
  12. });  
  13. enhancer.create();  
  14. }  
  15. }  
  16. class OOMObject{  
  17. }   
  18. }  

執行一段時間之後,永久代記憶體不夠,方法區無法再存放CGLIB建立處理的Class資訊,產生方法區OutOfMemoryError。

(5).本機直接記憶體溢位:

Java虛擬機器可以通過引數-XX:MaxDirectMemorySize設定本機直接記憶體可用大小,如果不指定,則預設與java堆記憶體大小相同。JDK中可以通過反射獲取Unsafe類(Unsafe的getUnsafe()方法只有啟動類載入器Bootstrap才能返回例項)直接操作本機直接記憶體。通過使用-XX:MaxDirectMemorySize=10M,限制最大可使用的本機直接記憶體大小為10MB,例子程式碼如下:

[java] view plaincopy
  1. public class DirectMemoryOOM{  
  2.     private static final int _1MB = 10241024 * 1024;  
  3.     publc static void main(String[] args) throws Exception{  
  4.         Field unsafeField = Unsafe.class.getDeclaredFields()[0];  
  5.         unsafeField.setAccessible(true);  
  6.         Unsafe unsafe = (Unsafe) unsafeField.get(null);  
  7.         while(true){  
  8.             //unsafe直接想作業系統申請記憶體  
  9.     unsafe.allocateMemory(_1MB);  
  10. }  
  11. }  
  12. }  

當執行一段時間之後,10MB的本機直接記憶體被分配光,無法在進行直接記憶體分配時,產生OutOfMemoryError。

相關文章