Java中内存溢出常见于如下的几种情形:
栈内存溢出(StackOverflowError)
堆内存溢出(OutOfMemoryError:java heap space)
永久代溢出(OutOfMemoryError:PermGen sapce)
一、堆内存溢出 OutOfMemoryError
从jvm的角度看发生的情况是:
1、动态扩展的栈内存无法满足内存分配。
2、建立新的线程没有足够内存创建栈。
从编码角度看发生的情况是:
1、内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
2、集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
3、代码中存在死循环或循环产生过多重复的对象实体;
4、使用的第三方软件中的BUG;
5、启动参数内存值设定的过小;
解决方案:
1、检查代码中是否存在一次性取出大量数据
2、检查循环体、递归调用中是否有大量导致gc无法回收的对象
3、-Xms -Xmx 配置最大最小堆内存大小,默认 -Xms256m -Xmx512m
二、栈内存溢出 StackOverflowError
从jvm的角度看发生的情况是:方法执行时申请不到新的空间存储(局部变量表, 操作数栈 , 动态链接 , 方法出口信息)。
从编码角度看发生的情况是: 一般出现在递归和循环依赖调用的代码块中
解决方案:
1、检查递归和循环依赖调用的代码块,尽可能严谨。
2、-Xss 通过这个参数配置默认的jvm栈大小,这个标识即可以通过项目的配置也可以通过命令行来指定,默认 -Xss1m 或者 -Xss0.5m。
java堆用于存储对象实例,我们只要不断的创建对象,
并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,
就会在对象数量达到最大堆容量限制后产生内存溢出异常。
原因
1、代码中可能存在大对象分配
2、可能存在内存泄露,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象。
解决方法
1、检查是否存在大对象的分配,最有可能的是大数组分配
2、通过jmap命令,把堆内存dump下来,使用mat工具分析一下,检查是否存在内存泄露的问题
3、如果没有找到明显的内存泄露,使用 -Xmx 加大堆内存
4、还有一点容易被忽略,检查是否有大量的自定义的 Finalizable 对象,也有可能是框架内部提供的,考虑其存在的必要性
永久代 -> 方法区溢出
方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。
原因
永久代是 HotSot 虚拟机对方法区的具体实现,存放了被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等。
JDK8后,元空间替换了永久代,元空间使用的是本地内存,还有其它细节变化:
字符串常量由永久代转移到堆中
和永久代相关的JVM参数已移除
可能原因有如下几种:
1、在Java7之前,频繁的错误使用String.intern()方法
2、运行期间生成了大量的代理类,导致方法区被撑爆,无法卸载
3、应用长时间运行,没有重启
解决方法
1、检查项目中是否有大量的死循环或有使用大内存的代码,优化代码。
2、添加参数 -XX:-UseGCOverheadLimit 禁用这个检查,其实这个参数解决不了内存问题,
只是把错误的信息延后,最终出现 java.lang.OutOfMemoryError: Java heap space。
3、dump内存,检查是否存在内存泄露,如果没有,加大内存。
原因
出现这种异常,基本上都是创建的了大量的线程导致的,以前碰到过一次,通过jstack出来一共8000多个线程。
解决方法
1、通过 -Xss 降低的每个线程栈大小的容量
2、线程总数也受到系统空闲内存和操作系统的限制,检查是否该系统下有此限制:
不常见
这种情况一般是由于不合理的数组分配请求导致的,在为数组分配内存之前,JVM 会执行一项检查。
要分配的数组在该平台是否可以寻址(addressable),如果不能寻址(addressable)就会抛出这个错误。
解决方法就是检查你的代码中是否有创建超大数组的地方。
1、swap 分区大小分配不足;
2、其他进程消耗了所有的内存。
解决方案:
1、其它服务进程可以选择性的拆分出去 2、加大swap分区大小,或者加大机器内存大小
这个异常出现的概率极低,只能通过操作系统本地工具进行诊断,难度有点大,还是放弃为妙。