Java内存溢出异常(下)
此篇是上一篇文章Java内存溢出异常(上)的续篇,没有看过的同学,可以先看一下上篇。本篇文章将介绍剩余的两个溢出异常:方法区和运行时常量池溢出。
方法区和运行时常量池溢出
这部分为什么会放在一起呢?在Java内存区域与内存溢出异常这篇文章中我们说过,运行时常量池实际上属于方法区的一部分,所以就放在一起讨论。
常量池溢出
在讨论常量池的溢出之前,先说明一下String.intern()方法,该方法会检查字符串常量池中是否已经包含了一个等于此String对象的字符串,如果已经包含了,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并返回此String对象的引用。看过fastjson源代码的同学应该知道,该方法在fastjson这个json解析库中大量的出现,以提高json的解析速度。
在JDK 1.6之前的版本中,常量池是分配在永久代的,可以通过-XX:PermSize和-XX:MaxPermSize参数来设置大小,从而间接限制其中常量池的容量。
通过以上条件,我们可以轻易的复现这个异常,代码如下:
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}
如果你的JDK环境是1.6版本之前的,你会得到如下运行结果:
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
at java.lang.String.intern(Native Method)
......
如果你是JDK 1.7+,那么这段代码会无限的运行下去。因为在JDK 1.7之后对String.intern()方法进行了修改。
继续看下面这段经典的代码:
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
String str1 = new StringBuilder("计算机").append("软件").toString();
System.out.println(str1.intern() == str1);
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str2);
}
}
这段代码在JDK 1.6中运行,会得到两个false,而在JDK 1.7中运行,会得到一个true和一个false。
出现差异的原因是因为,在JDK 1.6中,intern()方法会把首次遇到的字符串实例复制到常量池中,返回的也是常量池中这个字符串实例的引用,而由StringBuilder创建的字符串实例在Java堆上,所以必然不是同一个引用,将返回false。而JDK 1.7中的intern()实训不会再复制实例,只是在常量池中记录首次出现的实例引用,因此intern()返回的引用和由StringBuilder创建的那个字符串实例是同一个。对str2比较返回false是因为“java”这个字符串在执行StringBuilder.toString()之前已经出现过了,字符串中常量池中已经有它的引用了,不符合“首次”出现的原则。
这就可以解释,为什么在JDK 1.7+版本之后不能用String.intern()方法使常量池溢出的原因了,intern()不会像JDK 1.6之前的版本一样无限复制实例到常量池中了。
方法区溢出
方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。这部分的测试可以通过生成大量的类去填满方法区,直到溢出,可以借助CGLib直接操作字节码运行时生成大量的动态类,代码如下:
public class JavaMethodAreaOOM {
public static void main(final String[] args) {
while (true) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
return methodProxy.invokeSuper(o, objects);
}
});
enhancer.create();
}
}
static class OOMObject {
}
}
运行结果如下:
Caused by: java.lang.OutOfMemoryError: PermGen space
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
...
在经常动态生成大量Class的应用中,比如说使用CGLib这类字节码技术的时候,容易出现方法区溢出。所以说在使用这类技术编写框架时,要留意这方面导致的内存溢出。
本机直接内存溢出
本机直接内存的溢出主要与大量的直接操作内存的API有关,比如说NIO相关的API,也可以通过rt.jar中的类使用Unsafe的功能来复现这个异常。DirectMemory容量可通过-XX:MaxDirectMemorySize指定,主要代码如下:
public class DirectMemoryOOM {
private static final int _1MB = 1024 * 1024;
public static void main(String[] args) throws Exception {
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while (true) {
unsafe.allocateMemory(_1MB);
}
}
}
运行结果如下:
Exception in thread "main" java.lang.OutOfMemoryError
at sun.misc.Unsafe.allocateMemory(Native Method)
...
由DirectMemory导致的内存溢出,不会在Heap Dump文件中不会看见明显的异常,如果发现OOM之后Dump文件很小,而程序中又直接或间接使用了NIO,那就可以考虑检查一下是不是这方面的原因。
Java内存溢出异常(下)的更多相关文章
- Java内存溢出异常(上)
上一篇文章我们讲了JVM运行时数据区域与内存溢出异常,其中对于内存溢出异常这部分将的不够详细,这篇文章将着重讲解Java内存溢出异常的相关知识.如果有没看过上一篇文章的小伙伴们,请点击Java内存区域 ...
- JVM(2) Java内存溢出异常
在Java虚拟机运行时数据区中,除了程序计数器之外,虚拟机栈.本地方法栈.方法区和Java堆都有发生OutOfMemoryError(简称OOM)异常的可能. 一.Java堆溢出 Java堆用于存储对 ...
- java内存溢出异常
名称 特征 作用 配置参数 异常 程序 计数器 占用内存小,线程私有, 生命周期与线程相同 大致为字节码行号指示器 无 无 虚拟机栈 线程私有,生命周期与线程 相同,使用连续的内存空间 Java 方法 ...
- 模拟Java内存溢出
本文通过修改虚拟机启动参数,来剖析常见的java内存溢出异常(基于jdk1.8). 修改虚拟机启动参数Java堆溢出虚拟机栈溢出方法区溢出本机直接内存溢出 修改虚拟机启动参数 这里我们使用的是ID ...
- 如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码
程序小白在写代码的过程中,经常会不经意间写出发生内存溢出异常的代码.很多时候这类异常如何产生的都傻傻弄不清楚,如果能故意写出让jvm发生内存溢出的代码,有时候看来也并非一件容易的事.最近通过学习< ...
- 深入理解java虚拟机系列(一):java内存区域与内存溢出异常
文章主要是阅读<深入理解java虚拟机:JVM高级特性与最佳实践>第二章:Java内存区域与内存溢出异常 的一些笔记以及概括. 好了開始.假设有什么错误或者遗漏,欢迎指出. 一.概述 先上 ...
- JVM自动内存管理-Java内存区域与内存溢出异常
摘要: JVM内存的划分,导致内存溢出异常的可能区域. 1. JVM运行时内存区域 JVM在执行Java程序的过程中会把它所管理的内存划分为以下几个区域: 1.1 程序计数器 程序计数器是一块较小的内 ...
- 《深入理解Java虚拟机》-----第2章 Java内存区域与内存溢出异常
2.1 概述 对于从事C.C++程序开发的开发人员来说,在内存管理领域,他们即是拥有最高权力的皇帝又是执行最基础工作的劳动人民——拥有每一个对象的“所有权”,又担负着每一个对象生命开始到终结的维护责任 ...
- 《深入理解java虚拟机》读书笔记——java内存区域和内存溢出异常
几种内存溢出异常: 堆溢出 原因:创建过多对象,并且GC Roots到对象之间有可达路径. 分两种情况: Memory Leak:无用的对象没有消除引用,导致无用对象堆积.例如<Effictiv ...
随机推荐
- https及证书
本文试图以通俗易通的方式介绍Https的工作原理,不纠结具体的术语,不考证严格的流程.我相信弄懂了原理之后,到了具体操作和实现的时候,方向就不会错,然后条条大路通罗马.阅读文本需要提前大致了解对称加密 ...
- 微信小程序开发之搞懂flex布局3——Flex Item
Flex Item flex容器的子元素就是这个容器的flex item. The direct children of a Flex Container (elements with display ...
- 【翻译】Flume 1.8.0 User Guide(用户指南) source
翻译自官网flume1.8用户指南,原文地址:Flume 1.8.0 User Guide 篇幅限制,分为以下5篇: [翻译]Flume 1.8.0 User Guide(用户指南) [翻译]Flum ...
- Promise实战AJAX封装
一.利用Promise的知识,对最开始的ajax的例子进行一个简单的封装: var url = 'xxx'; // 封装一个get请求的方法 function request(url){ return ...
- Maven之pom.xml配置文件详解
此文非原创,摘自:https://www.baidu.com/link?url=GlGgW21nijIiULDZj0RfPH8ofqGMqEnAzXiym7O3hfrZM5nFH2enukemBNTX ...
- 通过TensorFlow训练神经网络模型
神经网络模型的训练过程其实质上就是神经网络参数的设置过程 在神经网络优化算法中最常用的方法是反向传播算法,下图是反向传播算法流程图: 从上图可知,反向传播算法实现了一个迭代的过程,在每次迭代的开始,先 ...
- git使用之后悔药
1.工作区的代码想撤销 背景:有时候编写了一大段代码之后,想要撤销更改(执行add操作之前), 命令:git checkout -- <file路径> 使用git checkout -- ...
- SpringCloud服务注册与发现
1.介绍对于微服务的治理而言,其核心就是服务的注册和发现.在SpringCloud 中提供了多种服务注册与发现组件:Eureka,Consul,Zookeeper.官方推荐使用Eureka. 说明:E ...
- 感知机、logistic回归 损失函数对比探讨
感知机.logistic回归 损失函数对比探讨 感知机 假如数据集是线性可分的,感知机学习的目标是求得一个能够将正负样本完全分开的分隔超平面 \(wx+b=0\) .其学习策略为,定义(经验)损失函数 ...
- noip第22课资料