Java虚拟机内存管理(二)

时间:2022-01-15 10:02:17

1.JVM内存溢出几种情况

  • PCR 程序计数器:用于记录正在执行的虚拟机字节码指令的地址,也是虚拟机规范中唯一未定义内存溢出的【内存区域】
  • Java虚拟机栈:每一个方法的执行都对应着一个StackFrame栈桢的入栈和出栈过程,StackFrame用于存储局部变量、操作栈、动态链接、方法出口等信息。这块内存区域定义了2种内存溢出场景:当线程请求的栈深度超过虚拟机规定的最大栈深度,就会产生 *Error 即栈溢出的异常;当栈深度足够但线程申请不到足够内存是会产生 OutOfMemoryError 即内存溢出的情况
  • Java堆:虚拟机内存管理最重要的区域,用于存放对象实例。当堆内存不够并且扩展内存失败时会产生 OutOfMemoryError
  • 方法区:存储已经被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据,当方法区内存分配不足时也会产生 OutOfMemory
  • 运行时常量池:属于方法区一部分,存放编译期间生成的各种字面常量和符号引用,这部分内容等到类加载之后存放到方法区运行时常量池中。因为常量池具备动态性、运行期间也可以放入新的常量。因此在常量池无法申请到足够内存时,也会产生OutOfMemoryError
  • 直接内存:JDK加入NIO机制,允许基于Channel通道方式,使用Native方法直接分配堆外内存,能在一些大文件读取时显著提升性能。因为不需要在Java堆和本地堆中来回复制数据。但是本机内存会有物理限制,比如机器内存就只有8G,一旦直接内存不够时,也会发生OutOfMemoryError

 2.内存溢出情况模拟

 2.1 栈内存溢出

[java]  view plain  copy
  1. package out.of.memory.test;  
  2.   
  3. /** 
  4.  * Java虚拟机栈:内存溢出情况模拟 *Error + OutOfMemoryError 
  5.  * 虚拟机参数设置为:-Xss2M 即栈内存分配2M 
  6.  *  
  7.  * @author yli 
  8.  */  
  9. public class StackTest {  
  10.   
  11.     public static void main(String[] args) {  
  12.          callSelf(0);  
  13.           
  14.          // 调用很容易导致系统死机...  
  15.          // new StackTest().oom();  
  16.     }  
  17.   
  18.     /** 
  19.      * *Error: 
  20.      * 当线程请求栈深度超过虚拟机规定最大深度,就会产生这种异常 
  21.      * 我们知道每一次方法调用就是StackFrame栈桢入栈和出栈过程 
  22.      * 那么只要无限次调用方法久很容易产生栈深度不够的情况 
  23.      *  
  24.      * 要模拟这种异常非常简单,很自然联想到[递归调用]就是这么一种情况 
  25.      * 递归调用就是无限调用自己:如果没有合适的退出机制,就产生栈溢出! 
  26.      *  
  27.      * @param callCount 
  28.      */  
  29.     private static void callSelf(int callCount) {  
  30.         // 打印方法被调用多少次  
  31.         callCount++;  
  32.         System.out.println(String.format("被调用%s次!", callCount));  
  33.           
  34.         // 只需要无限调用自己,并且不退出调用即可模拟!  
  35.         callSelf(callCount);  
  36.     }  
  37.       
  38.     /** 
  39.      * OutOfMemoryError:申请栈扩展内存不足导致内存溢出 
  40.      * 不停的创建局部变量可以模拟 
  41.      */  
  42.     private void oom() {  
  43.         while(true) {  
  44.             new Thread(new Runnable() {  
  45.                 @Override  
  46.                 public void run() {  
  47.                     print();  
  48.                       
  49.                 }  
  50.             }).start();  
  51.         }  
  52.     }  
  53.       
  54.     private void print(){  
  55.         while(true) {  
  56.             System.out.println(Thread.currentThread());  
  57.         }  
  58.     }  
  59. }  


2.2 堆内存溢出

[java]  view plain  copy
  1. package out.of.memory.test;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. /** 
  7.  * 堆内存溢出测试 
  8.  * 虚拟机参数设置为:-Xms10M -Xmx20M 即初始堆内存10M,最大堆内存20M 
  9.  * @author yli 
  10.  *  
  11.  */  
  12. public class HeapTest {  
  13.   
  14.     static class User {  
  15.         long[] numns = { 1l, 2l, 3l, 4l };  
  16.     }  
  17.       
  18.     /** 
  19.      * 不停创建对象,由于对象实例存储在堆内存 
  20.      * 就能很容易模拟堆内存溢出! 
  21.      *  
  22.      * @param args 
  23.      */  
  24.     public static void main(String[] args) {  
  25.         List<User> list = new ArrayList<User>();  
  26.         while(true) {  
  27.             User u = new User();  
  28.             list.add(u);  
  29.         }  
  30.     }  
  31.   
  32. }  


2.3 常量池内存溢出

[java]  view plain  copy
  1. package out.of.memory.test;  
  2.   
  3. import java.util.ArrayList;  
  4. import java.util.List;  
  5.   
  6. /** 
  7.  * 虚拟机参数设置为:-XX:PermSize=2M -XX:MaxPersmSize=5M 
  8.  *  
  9.  * @author yli 
  10.  */  
  11. public class ConstantPoolTest {  
  12.   
  13.     /** 
  14.      * 常量池内存溢出:只要无限构造常量即可模拟 
  15.      * @param args 
  16.      */  
  17.     public static void main(String[] args) {  
  18.         List<String> list = new ArrayList<String>();  
  19.         String s = "常量池内存溢出:只要无限构造常量即可模拟";  
  20.         int i=0;  
  21.         while(true) {  
  22.             list.add((s+String.valueOf(i++)).intern());  
  23.             System.out.println(i);  
  24.         }  
  25.     }  
  26. }  


2.4方法区内存溢出

[java]  view plain  copy
  1. package out.of.memory.test;  
  2.   
  3. import java.lang.reflect.InvocationHandler;  
  4. import java.lang.reflect.Method;  
  5. import java.lang.reflect.Proxy;  
  6. import java.util.Date;  
  7.   
  8. import net.sf.cglib.proxy.Enhancer;  
  9. import net.sf.cglib.proxy.MethodInterceptor;  
  10. import net.sf.cglib.proxy.MethodProxy;  
  11.   
  12. /** 
  13.  * 方法区内存溢出测试 
  14.  * -XX:PermSize=2M -XX:MaxPermSize=5M 
  15.  * @author yli 
  16.  * 
  17.  */  
  18. public class MethodAreaTest {  
  19.   
  20.     /** 
  21.      * 方法区用于存储虚拟机加载的类信息(每一个类都有与之对应的class对象) 
  22.      * 也存储常量、静态变量、动态编译后的代码等数据 
  23.      * 常量存放在常量池,通过ConstantPoolTest测试:不停构造常量即可构造内存溢出 
  24.      * 方法区内存溢出也可以加载大量类导致内存溢出 
  25.      *  
  26.      *  
  27.      *  
  28.      * @param args 
  29.      */  
  30.     public static void main(String[] args) {  
  31.         while(true) {  
  32.             Enhancer en = new Enhancer();  
  33.             en.setSuperclass(UserImpl.class);  
  34.             en.setUseCache(false);  
  35.             en.setCallback(new MethodInterceptor() {  
  36.                   
  37.                 @Override  
  38.                 public Object intercept(Object obj, Method method, Object[] args,  
  39.                         MethodProxy proxy) throws Throwable {  
  40.                     return  proxy.invokeSuper(obj, args);  
  41.                 }  
  42.             });  
  43.               
  44.             en.create();  
  45.             System.out.println("created!");  
  46.         }  
  47.   
  48.     }  
  49. }  

 

2.5 直接内存溢出

[java]  view plain  copy
  1. package out.of.memory.test;  
  2.   
  3. import java.lang.reflect.Field;  
  4.   
  5. import sun.misc.Unsafe;  
  6.   
  7. /** 
  8.  * 直接内存溢出:通过设置最大直接内存,并且避免堆内存扩展 
  9.  * 模拟直接内存溢出,Unsafe.allocateMemory 类似于c语言的 malloc函数分配内存 
  10.  *  
  11.  * @author yli 
  12.  * 
  13.  */  
  14. public class DirectTest {  
  15.   
  16.     private static final int _1MB = 1024 * 1024;  
  17.       
  18.     public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {  
  19.         Field f = Unsafe.class.getDeclaredFields()[0];  
  20.         f.setAccessible(true);  
  21.         Unsafe us = (Unsafe)f.get(null);  
  22.         while(true) {  
  23.             us.allocateMemory(_1MB);  
  24.             System.out.println(true);  
  25.         }  
  26.     }  
  27.           
  28. }