JVM学习笔记-内存泄漏异常二

笔者在上一篇文章讲了JVM学习笔记-内存泄漏异常一,针对的是java堆和虚拟机栈、本地方法栈内存溢出的情况。在这片文章中,将讲述方法区、运行时常量池溢出和直接内存溢出两种情况。

1、方法区和运行时常量池溢出

由于运行时常量池是方法区的一部分,因此这两个区域的溢出测试将放在一起进行。
提到运行时常量池,会经常想到String.intern()方法。该方法的作用是:若字符串常量池中已经包含了一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并返回此String对象的引用。在jdk1.6版本前,由于常量池分配在永久代中,只需要设置-XX:PermSize和
-XX:MaxPermSize的大小来限制方法区的大小,从而间接的限制其中常量池的大小。以下示例用来测试运行时常量池导致的内存溢出情况:

/**
 * VM 参数:-XX:PermSize=10M -XX:MaxPermSize=10M -Xms20m -Xmx20m
 * 
 * @author sj
 *
 */
public class ConstantPoolOOMTest {

	public static void main(String[] args) {
		// 使用list保持常量池的引用,避免垃圾回收
		List<String> list = new ArrayList<String>();
		int i = 0;
		while (true) {
			list.add(String.valueOf(i++).intern());
		}
	}
}

在jdk1.7版本以上时,报错如下:
在这里插入图片描述
而在1.6版本时,报错提示的区域不一样:
在这里插入图片描述
这也就验证了,1.7版本后,运行时常量池由永久代移到了java堆中。
方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。因此对于方法区溢出的测试,基本思路就是运行时产生大量的类去填满方法区,直到溢出。在许多主流框架比如Spring、Hibernate中,都会用到CGLib技术对类进行增强,增强的类越多,就需要越大的方法区来保证动态生成的Class可以载入内存。下面就借助CGLib直接操作字节码运行时生成大量的动态类:

/**
 * VM 参数:-XX:PermSize=10M -XX:MaxPermSize=10M
 * 
 * @author sj
 *
 */
public class MethodAreaOOMTest {

	public static void main(String[] args) {
		while (true) {
			Enhancer enhancer = new Enhancer();
			enhancer.setSuperclass(OOMObject.class);
			enhancer.setUseCache(false);
			enhancer.setCallback(new MethodInterceptor() {
				public Object intercept(Object obj, Method method,
						Object[] args, MethodProxy proxy) throws Throwable {
					return proxy.invokeSuper(obj, args);
				}
			});
			enhancer.create();
		}
	}

	static class OOMObject {

	}
}

运行结果:
在这里插入图片描述
方法区溢出也是一种常见的内存溢出异常,一个类要被垃圾回收,条件是比较苛刻的。在经常动态生成大量class的应用中,需要特别关注垃圾回收情况。这类场景除了是上面所说的使用CGLib字节码增强和动态语言外,常见的还有:大量JSP或者动态产生JSP文件的应用、基于OSGI的应用等。

2、直接内存溢出

设置直接内存的VM参数:
-XX : MaxDirectMemorySize
若不指定该参数,则直接内存的大小默认与Java堆的最大值一样,下面代码使用unsafe分配本机内存:

/**
 * VM 参数: -Xmx20M -XX:MaxDirectMemorySize=10M
 * 
 * @author sj
 *
 */
public class DirectMemoryOOMTest {

	// 每次分批内存大小为1MB
	private static final int OneMB = 1024 * 1024;

	public static void main(String[] args) throws IllegalArgumentException,
			IllegalAccessException {
		// 直接通过反射获取到Unsafe实例
		Field unsafeField = Unsafe.class.getDeclaredFields()[0];
		unsafeField.setAccessible(true);
		Unsafe unsafe = (Unsafe) unsafeField.get(null);
		while (true) {
			// 申请分配内存
			unsafe.allocateMemory(OneMB);
		}
	}

}

运行结果:
在这里插入图片描述
由直接内存导致的内存溢出,一个明显的特征就是在Head Dump文件中不会看见明显异常,若发现OOM之后Dump文件很小,并且程序中又直接或间接使用了NIO,有可能就是这方面的原因。

3、总结

JVM学习笔记-内存泄漏异常一和JVM学习笔记-内存泄漏异常二两篇文章主要讲了哪部分区域、什么样的代码和操作可能导致内存溢出。即使Java有垃圾回收机制,但我们在日常开发中还是会经常遇到内存溢出的情况。

参考:

深入理解Java虚拟机第2章-周志明

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值