首页 > IT职业 > IT生活 > JVM学习笔记(3)---OutOfMemory详解
之前说过,堆中主要存储的是对象实例。
所以如果不断创建对象,并保证GC Roots(之后会说明)到对象间有可达路径来避免垃圾回收机制清除这些对象,就会在对象数量达到堆的容量限制后产生内存溢出。
/**
* 堆溢出
* VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
* -Xms设置堆的最小值 -Xmx设置堆的最大值
*
*/public class HeapOOM { static class OOMObject {
} public static void main(String[] args) {
List<OOMObject> list = new ArrayList<OOMObject>(); while (true) {
list.add(new OOMObject());
}
}
}
通过-Xms和-Xmx设置Vm最小、最大堆的值为20m(将最大最小值设为一样可以避免堆的自动扩展)
HotSpot虚拟机不区分虚拟机栈和本地方法栈,所以这里统称为栈溢出。
之前已经讨论过,虚拟机栈会抛出两种异常,StackOverflowError 和 OutOfMemoryError 异常。(见JVM学习笔记(2)---Java内存区域
Java虚拟机规范中,对该区域规定了两种异常:
StackOverFlowError:线程请求的栈深度大于虚拟机允许的栈深度时
OverOfMemoryError:动态扩展的线程无法申请到足够的内存时
简单理解来看:
对于前者而言,是由于增加过大栈帧深度或限制虚拟机栈内存产生。
在单线程中,我们不断增大栈帧中本地变量表的长度(如定义大量的本地变量),或者限制栈内存容量(通过Vm启动参数),都输出StackOverFlowError。
对于后者而言,是由于不断建立线程产生。
这样产生的 OutOfMemoryError 与栈空间大小不存在关系。在内存总量一定时,每个线程的栈分配的内存越大,则越容易产生内存溢出。
/**
* 栈溢出 OutOfMemoryError
* VM Args:-Xss2m
* -Xss设置栈大小 来减小栈容量
* 运行本代码前请先保存当前电脑环境,可能假死
*/public class StackOOM { public int threadCount = 0; public void addThread() { while (true) {
Thread thread = new Thread(new Runnable() { public void run() { while (true) {
}
}
});
thread.start();
}
} public static void main(String[] args){
StackOOM stackOOM = new StackOOM();
stackOOM.addThread();
}
}
在JDK1.4之后,新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以直接使用Native函数库分配堆外内存,通过一个存储在Java堆中的 DirectByteBuffer对象 作为这块内存的引用进行操作。
这样能在一些场景中显著提高性能,避免了在Java堆和Native堆中来回复制数据。
通过建立Unsafe实例,可以申请分配直接内存。通过MaxDirectMemorySize参数,可以指定直接内存容量,当申请的直接内存过大时,会出现直接内存溢出。
/**
* Vm Args: -Xmx20M -xx:MaxDirectMemorySize=10M
*
*/public class DirectMemoryOOM { private static final int _1MB = 1024*1024; public static void main(String[] args)throws Exception{ //通过反射获取Unsafe实例,来申请内存分配
Field unsafeFiled = Unsafe.class.getDeclaredFields()[0];
unsafeFiled.setAccessible(true);
Unsafe unsafe = (Unsafe)unsafeFiled.get(null); while (true){
unsafe.allocateMemory(_1MB);
}
}
}
我们已知,方法区中主要存放的是一些描述性信息,即元数据。元空间是方法区的一种实现方式。
(注意,方法区是一种规范,元空间 和 永久代 都是一种实现。
在JDK1.8之前,使用 永久代(PermGen)来实现方法区,但有以下问题:
永久代内存经常不够用或发生内存泄露,爆出异常 OutOfMemoryError: PermGen
动态类加载的情况越来越多,这块内存我们变得不太可控。
类及方法的信息等比较难确定其大小,因此对于永久代的大小指定比较困难,太小容易出现永久代溢出,太大则容易导致老年代溢出。
永久代会为 GC 带来不必要的复杂度,并且回收效率偏低。
在jdk8之后,用元空间(MetaSpace)替代。 元空间是和本地内存相关的。默认上限大小是本地内存。
元空间与永久代之间 最大区别 在于:
元空间并不在虚拟机中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制。
import net.sf.cglib.beans.BeanGenerator;import net.sf.cglib.beans.BeanMap;import java.util.*;/**
* VM args: -XX:MaxMetaspaceSize=1M -XX:+PrintGCDetails
*/public class MetaSpaceOOM { public static void main(String[] args) throws Exception { for (int i = 0; i < 80000; i++) { //动态创建类
Map<Object, Object> propertyMap = new HashMap<Object, Object>();
propertyMap.put("id", Class.forName("java.lang.Integer")); //建立一个动态生成bean
CglibBean bean = new CglibBean(propertyMap); //给 Bean 设置值
bean.setValue("id", new Random().nextInt(100)); //打印 Bean的属性id
System.out.println("num=" + i + " id=" + bean.getValue("id"));
}
} static class CglibBean { /**
* 实体Object
*/
public Object object = null; /**
* 属性map
*/
public BeanMap beanMap = null; public CglibBean() { super();
} @SuppressWarnings("unchecked") public CglibBean(Map propertyMap) { this.object = generateBean(propertyMap); this.beanMap = BeanMap.create(this.object);
} /**
* 给bean属性赋值
* @param property 属性名
* @param value 值
*/
public void setValue(String property, Object value) {
beanMap.put(property, value);
} /**
* 通过属性名得到属性值
* @param property 属性名
* @return 值
*/
public Object getValue(String property) { return beanMap.get(property);
} /**
* 得到该实体bean对象
* @return
*/
public Object getObject() { return this.object;
} @SuppressWarnings("unchecked") private Object generateBean(Map propertyMap) {
BeanGenerator generator = new BeanGenerator();
Set keySet = propertyMap.keySet(); for (Iterator i = keySet.iterator(); i.hasNext();) {
String key = (String) i.next();
generator.addProperty(key, (Class) propertyMap.get(key));
} return generator.create();
}
}
}
作者:weberweber
链接:https://www.jianshu.com/p/d50621e44e82
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/3209/viewspace-2818118/ ,如需转载,请注明出处,否则将追究法律责任。