📫作者简介:小明java问道之路,2022年度博客之星全国TOP3,专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化,文章内容兼具广度、深度、大厂技术方案,对待技术喜欢推理加验证,就职于知名金融公司后端高级工程师。
📫 热衷分享,喜欢原创~ 关注我会给你带来一些不一样的认知和成长。
🏆 2022博客之星TOP3 | CSDN博客专家 | 后端领域优质创作者 | CSDN内容合伙人
🏆 InfoQ(极客邦)签约作者、阿里云专家 | 签约博主、51CTO专家 | TOP红人、华为云享专家
🔥如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主~
🍅 文末获取联系 🍅 👇🏻 精彩专栏推荐订阅收藏 👇🏻
专栏系列(点击解锁)
学习路线(点击解锁)
知识定位
🔥Redis从入门到精通与实战🔥
Redis从入门到精通与实战
围绕原理源码讲解Redis面试知识点与实战
🔥MySQL从入门到精通🔥
MySQL从入门到精通
全面讲解MySQL知识与企业级MySQL实战 🔥计算机底层原理🔥
深入理解计算机系统CSAPP
以深入理解计算机系统为基石,构件计算机体系和计算机思维
Linux内核源码解析
围绕Linux内核讲解计算机底层原理与并发
🔥数据结构与企业题库精讲🔥
数据结构与企业题库精讲
结合工作经验深入浅出,适合各层次,笔试面试算法题精讲
🔥互联网架构分析与实战🔥
企业系统架构分析实践与落地
行业最前沿视角,专注于技术架构升级路线、架构实践
互联网企业防资损实践
互联网金融公司的防资损方法论、代码与实践
🔥Java全栈白宝书🔥
精通Java8与函数式编程
本专栏以实战为基础,逐步深入Java8以及未来的编程模式
深入理解JVM
详细介绍内存区域、字节码、方法底层,类加载和GC等知识
深入理解高并发编程
深入Liunx内核、汇编、C++全方位理解并发编程
Spring源码分析
Spring核心七IOC/AOP等源码分析
MyBatis源码分析
MyBatis核心源码分析
Java核心技术
只讲Java核心技术
本文目录
本文导读
一、栈内存溢出
1、死递归
2、线程太多
二、堆内存溢出
1、初始对象太大
2、对象没有回收
3、常量池溢出
三、方法区内存溢出
四、直接内存溢出
总结
本文导读
Java内存溢出的原因为程序在申请内存(堆/栈/方法区/直接内存等等)时,没有足够的内存空间。
内存溢出有几种类型,栈溢出(程序所要求的栈深度过大)、堆溢出(创建对象时如果没有可以分配的堆内存、对象没有被回收)、方法区溢出(Class对象未被释放,Class对象占用信息过多,有过多的Class对象)、直接内存溢出(分配的本地内存大小大于JVM的限制)。
一、栈内存溢出
1、死递归
线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError,可以写一个死递归程序触发。
Java的栈空间默认是1M大小,可以通过-Xss 调整
public class StackOverFlow {
public void test(){
test();//死递归
}
public static void main(String[] args)throws Throwable {
StackOverFlow javaStack = new StackOverFlow();
javaStack.test();
}
}
2、线程太多
线程太多会导致栈溢出,抛出 OutOfMemoryError,例如1个方法运行中的对象占用1M内存。同时5000+个线程运行这个方法,如果机器的内存小于5G的话,那么也会发生内存溢出,这种情况会抛出OOM异常。
public class StackOutOfMemoryError {
public static void main(String[] args) {
StackOutOfMemoryError test = new StackOutOfMemoryError ();
test.oomMethod();
}
public void oomMethod(){
while(true){
new Thread(new Runnable() {
@Override
public void run() {
loopMethod();
}
}).start();;
}
}
private void loopMethod(){
while(true){
}
}
}
二、堆内存溢出
1、初始对象太大
创建对象时如果没有可以分配的堆内存,JVM就会抛出 OutOfMemoryError:java heap space 异常。
堆内存可以通过设置JVM初始堆空间和最大堆空间,堆溢出生成快照:
-Xms20M -Xmx20M -XX:+HeapDumpOnOutOfMemoryError
/**
* VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*/
public static void main(String[] args) {
List<byte[]> list = new ArrayList<>();
int i=0;
while(true){
list.add(new byte[5*1024*1024]);
System.out.println("分配次数:"+(++i));
}
}
2、对象没有回收
在方法执行中,回收效率不足2%,抛出 OutOfMemoryError: GC overhead limit exceeded。
/**
*
* VM Args:-Xms10m -Xmx10m -Xmn5m -XX:+PrintGCDetails 堆的大小10M
* 堆内存溢出
*/
public class HeapOom {
public static void main(String[] args) {
List<Object> list = new LinkedList<>(); //在方法执行的过程中,它是GCRoots
int i =0;
while(true){
i++;
if(i%10000==0)
System.out.println("i="+i);
list.add(new Object());
}
}
}
3、常量池溢出
JDK 1.8中 PermSize 和 MaxPermGen 已经无效,JDK 1.7 和 1.8 将字符串常量由永久代转移到堆中,并且 JDK 1.8 中已经不存在永久代的结论。
对象实际存储在堆上面,最终会产生堆内存溢出(java.lang.OutOfMemoryError: Java heap space),将堆内存设置为:-Xms5m -Xmx5m。
public class ConstantPoolOOMTest {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
int i=1;
try {
while(true){
list.add(UUID.randomUUID().toString().intern());
i++;
}
} finally {
System.out.println("运行次数:"+i);
}
}
}
三、方法区内存溢出
方法区溢出为,java.lang.OutOfMemoryError: Metaspace,一般发生在动态语言,因为动态语言编译后会放在方法区。
在经常动态生产大量Class的应用中,CGLIb字节码增强,动态语言,大量JSP(JSP第一次运行需要编译成Java类),基于OSGi的应用(同一个类,被不同的加载器加载也会设为不同的类)。如果方法区内存不够大的话也会发生java.lang.OutOfMemoryError: Metaspace溢出。
public class MethodAreaOOMTest {
public static void main(String[] args) {
int i=0;
try {
while(true){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMObject.class);
enhancer.setUseCache(false);
// 借助CGLib直接操作字节码,生成大量的动态类
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
return proxy.invokeSuper(obj, args);
}
});
enhancer.create();
i++;
}
} finally{
System.out.println("运行次数:"+i);
}
}
static class OOMObject{
}
}
四、直接内存溢出
直接内存溢出( java.lang.OutOfMemoryError: Direct buffer memory)分配的本地内存大小大于JVM的限制。NIO会使用到直接内存,可以通过NIO来模拟。
设置参数: -XX:MaxDirectMemorySize=100m
/**
* VM Args:-XX:MaxDirectMemorySize=100m
* 限制最大直接内存大小100m
* 直接内存溢出
*/
public class DirectOom {
public static void main(String[] args) {
// 直接分配128M的直接内存(100M)
ByteBuffer bb = ByteBuffer.allocateDirect(128*1024*1204);
}
}
总结
内存溢出有几种类型,栈溢出(程序所要求的栈深度过大)、堆溢出(创建对象时如果没有可以分配的堆内存、对象没有被回收)、方法区溢出(Class对象未被释放,Class对象占用信息过多,有过多的Class对象)、直接内存溢出(分配的本地内存大小大于JVM的限制)。