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
- package out.of.memory.test;
- /**
- * Java虚拟机栈:内存溢出情况模拟 *Error + OutOfMemoryError
- * 虚拟机参数设置为:-Xss2M 即栈内存分配2M
- *
- * @author yli
- */
- public class StackTest {
- public static void main(String[] args) {
- callSelf(0);
- // 调用很容易导致系统死机...
- // new StackTest().oom();
- }
- /**
- * *Error:
- * 当线程请求栈深度超过虚拟机规定最大深度,就会产生这种异常
- * 我们知道每一次方法调用就是StackFrame栈桢入栈和出栈过程
- * 那么只要无限次调用方法久很容易产生栈深度不够的情况
- *
- * 要模拟这种异常非常简单,很自然联想到[递归调用]就是这么一种情况
- * 递归调用就是无限调用自己:如果没有合适的退出机制,就产生栈溢出!
- *
- * @param callCount
- */
- private static void callSelf(int callCount) {
- // 打印方法被调用多少次
- callCount++;
- System.out.println(String.format("被调用%s次!", callCount));
- // 只需要无限调用自己,并且不退出调用即可模拟!
- callSelf(callCount);
- }
- /**
- * OutOfMemoryError:申请栈扩展内存不足导致内存溢出
- * 不停的创建局部变量可以模拟
- */
- private void oom() {
- while(true) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- print();
- }
- }).start();
- }
- }
- private void print(){
- while(true) {
- System.out.println(Thread.currentThread());
- }
- }
- }
2.2 堆内存溢出
[java]
view plain
copy
- package out.of.memory.test;
- import java.util.ArrayList;
- import java.util.List;
- /**
- * 堆内存溢出测试
- * 虚拟机参数设置为:-Xms10M -Xmx20M 即初始堆内存10M,最大堆内存20M
- * @author yli
- *
- */
- public class HeapTest {
- static class User {
- long[] numns = { 1l, 2l, 3l, 4l };
- }
- /**
- * 不停创建对象,由于对象实例存储在堆内存
- * 就能很容易模拟堆内存溢出!
- *
- * @param args
- */
- public static void main(String[] args) {
- List<User> list = new ArrayList<User>();
- while(true) {
- User u = new User();
- list.add(u);
- }
- }
- }
2.3 常量池内存溢出
[java]
view plain
copy
- package out.of.memory.test;
- import java.util.ArrayList;
- import java.util.List;
- /**
- * 虚拟机参数设置为:-XX:PermSize=2M -XX:MaxPersmSize=5M
- *
- * @author yli
- */
- public class ConstantPoolTest {
- /**
- * 常量池内存溢出:只要无限构造常量即可模拟
- * @param args
- */
- public static void main(String[] args) {
- List<String> list = new ArrayList<String>();
- String s = "常量池内存溢出:只要无限构造常量即可模拟";
- int i=0;
- while(true) {
- list.add((s+String.valueOf(i++)).intern());
- System.out.println(i);
- }
- }
- }
2.4方法区内存溢出
[java]
view plain
copy
- package out.of.memory.test;
- import java.lang.reflect.InvocationHandler;
- import java.lang.reflect.Method;
- import java.lang.reflect.Proxy;
- import java.util.Date;
- import net.sf.cglib.proxy.Enhancer;
- import net.sf.cglib.proxy.MethodInterceptor;
- import net.sf.cglib.proxy.MethodProxy;
- /**
- * 方法区内存溢出测试
- * -XX:PermSize=2M -XX:MaxPermSize=5M
- * @author yli
- *
- */
- public class MethodAreaTest {
- /**
- * 方法区用于存储虚拟机加载的类信息(每一个类都有与之对应的class对象)
- * 也存储常量、静态变量、动态编译后的代码等数据
- * 常量存放在常量池,通过ConstantPoolTest测试:不停构造常量即可构造内存溢出
- * 方法区内存溢出也可以加载大量类导致内存溢出
- *
- *
- *
- * @param args
- */
- public static void main(String[] args) {
- while(true) {
- Enhancer en = new Enhancer();
- en.setSuperclass(UserImpl.class);
- en.setUseCache(false);
- en.setCallback(new MethodInterceptor() {
- @Override
- public Object intercept(Object obj, Method method, Object[] args,
- MethodProxy proxy) throws Throwable {
- return proxy.invokeSuper(obj, args);
- }
- });
- en.create();
- System.out.println("created!");
- }
- }
- }
2.5 直接内存溢出
[java]
view plain
copy
- package out.of.memory.test;
- import java.lang.reflect.Field;
- import sun.misc.Unsafe;
- /**
- * 直接内存溢出:通过设置最大直接内存,并且避免堆内存扩展
- * 模拟直接内存溢出,Unsafe.allocateMemory 类似于c语言的 malloc函数分配内存
- *
- * @author yli
- *
- */
- public class DirectTest {
- private static final int _1MB = 1024 * 1024;
- public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
- Field f = Unsafe.class.getDeclaredFields()[0];
- f.setAccessible(true);
- Unsafe us = (Unsafe)f.get(null);
- while(true) {
- us.allocateMemory(_1MB);
- System.out.println(true);
- }
- }
- }