首页 > 编程语言 >java中OOM错误浅析(没有深入太多,一些粗浅的知识,可以温习用)

java中OOM错误浅析(没有深入太多,一些粗浅的知识,可以温习用)

时间:2022-12-12 15:38:41浏览次数:33  
标签:Java OOM 虚拟机 内存 OutOfMemoryError java main 浅析


嗯,生活加油鸭。。。。 实习中遇到OOM错误

GC overhead limit exceeded   问题,所以整理一下OOM异常问题:不对的地方请小伙伴留言^_^

“阿里的开发手册”对OOM的描述:

OOM,全称“Out Of Memory”,翻译成中文就是“内存用完了”意思就是说,当JVM因为没有足够的内存来为对象分配空间并且垃圾回收器也已经没有空间可回收时,就会抛出这个error(注:非exception,因为这个问题已经严重到不足以被应用处理)。

  • 内存泄露:申请使用完的内存没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,因为申请者不用了,而又不能被虚拟机分配给别人用。
  • 内存溢出:申请的内存超出了JVM能提供的内存大小,此时称之为溢出。

《深入理解java虚拟机》中描述的OOM发生区域:

1)Java虚拟机栈:

    描述Java方法执行的内存模型,与计数器一般,java虚拟机栈也是线程私有的,它的生命周期与线程相同,每个方法在执行的同时会创建一个栈帧,用于存储局部变量表,操作数栈,动态链表,方法出口等信息。每个方法从调用到执行完成过程,就对应着一个栈帧在虚拟机中入栈到出栈的过程。

  •  局部变量表:存储了编译期可知的各种基本数据类型,对象的引用(reference类型,即指向对象起始地址的引用指针)和returnAddress类型(指向一条字节码指令的地址)。long与double类型数据会占用2个局部变量空间,其余的数据类型会占用一个,局部变量表所需要的内存空间在编译期完成分配,当进入一个方法时,方法在帧中分配的局部变量空间是完全确定的。在方法运行期不会改变局部变量表的大小。
  • ........

如果虚拟机栈可以动态扩展,但扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常。

2)本地方法栈:

  • 本地方法栈则是为虚拟机使用到的Native方法服务。也会抛出StackOverflowError异常,OutOfMemoryError异常

3)Java堆:

  •  java 堆是虚拟机所管理的内存中最大的一块,java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建,唯一作用是用来存放对象实例,几乎所有的对象实例以及数组都要在堆上分配地址。
  • java堆是垃圾收集器管理的主要区域。也被称为GC堆。Java堆可以处于物理上不连续的内存空间,只用逻辑上连续即可,可以通过-Xmx和Xms控制。如果在堆中没有内存完成实例分配,并且堆中也无法扩展,会抛出OutOfMenory异常。

4)运行时常量池:

方法区的一部分,Class文件中除了有类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

运行时常量池对于Class文件常量池的一个特征是具备动态性,Java语言不要求常量一定是在编译期才产生,即并非预置入Class文件中的常量池的内容可以才能进入方法区运行时常量池,运行期也可以将新的常量放入池中,比如String的intern()方法。当常量池无法 在申请内存时会抛出OutOfMethodError异常。java8之后改变了永久代的位置,存放到了直接内存,所以不要报了..

具体的实例:

一、Java堆溢出:

package com.company;

import java.util.ArrayList;
import java.util.List;

/**
* @Author: Liruilong
* @Date: 2019/7/13 10:03
* VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*/
public class HeapOOM {

static class OOMObject{
}
public static void main(String[] args){
List<OOMObject> list = new ArrayList<>();

while (true){
list.add(new OOMObject());
}
}
}

java.lang.OutOfMemoryError: Java heap spaceDumping heap to java_pid13048.hprof ...
Heap dump file created [29202866 bytes in 0.128 secs]Exception in thread "main" java.lang.OutOfMemoryError: Java heap space    
at java.util.Arrays.copyOf(Arrays.java:3204)    
at java.util.Arrays.copyOf(Arrays.java:3175)    
at java.util.ArrayList.grow(ArrayList.java:246)    
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:220)    
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:212)    
at java.util.ArrayList.add(ArrayList.java:443)    
at com.company.HeapOOM.main(HeapOOM.java:19)

二、虚拟机栈和本地方法栈溢出:

package com.company;

/**
* @Author: Liruilong
* @Date: 2019/7/13 18:00
* @VM Args: -Xss128k
*/
public class JavaVMStackSOF {

private int stackLength = 1;

public void stackLeng(){
stackLength++;
stackLeng();
}

public static void main(String[] args)throws Throwable {
JavaVMStackSOF oom = new JavaVMStackSOF();
try{
oom.stackLeng();
}catch (Throwable throwable){
System.out.println("sstack length:"+oom.stackLength);
throw throwable;
}
}

}

sstack length:1001 Exception in thread "main" java.lang.StackOverflowError at com.company.JavaVMStackSOF.stackLeng(JavaVMStackSOF.java:13) at com.company.JavaVMStackSOF.stackLeng(JavaVMStackSOF.java:14) ………………

在单线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverflowError。

通过不断地建立线程的方式可以产生内存溢出异常,

但是这样产生的内存溢出异常与栈空间是否足够大并不存在任何联系,为每个线程的栈分配内存越大,越容易产生内存溢出的异常。

简述操作系统内存分配

由于操作系统分配给每个进程的内存是有限的, 虚拟机提供了参数来控制Java堆和方法区域(Xmx(最大堆容量),MaxPermSize(最大方法区容量))。 其余内存主要由虚拟机栈和本地方法瓜分。

 每个线程分配到的栈容量越大,可以建立的线程数量自然越少,建立线程越容易耗尽剩下内存。

【创建线程导致内存溢出异常】

/**
* VM Args:-Xss2M (这时候不妨设大些)
*/
public class JavaVMStackOOM {

private void dontStop() {
while (true) {
}
}

public void stackLeakByThread() {
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}

public static void main(String[] args) throws Throwable {
JavaVMStackOOM oom = new JavaVMStackOOM();
oom.stackLeakByThread();
}

Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread

 

 

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

 String.intern()是一个Native方法,当字符串对象已经包含一个String的字符串引用时,则返回字符串的引用,反之,将字符串添加到常量池中,并发挥Sting对象的引用。

在JDK1.6之前的版本,由于常量池分配永久代中,所以可以通过限制方法区的大小来限制常量池容量。

【运行时常量池导致的内存溢出异常】

/**
* VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
*/
public class RuntimeConstantPoolOOM {

public static void main(String[] args) {
// 使用List保持着常量池引用,避免Full GC回收常量池行为
List<String> list = new ArrayList<String>();
// 10MB的PermSize在integer范围内足够产生OOM了
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}

xception in thread "main" java.lang.OutOfMemoryError: PermGen space at java.lang.String.intern(Native Method) at baby.oom.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:18)

 

 

 四,本机直接内存溢出

DirectMemory容量可通过 ​​-XX: MaxDirectMemorySize​​指定,若不指定则默认与Java堆最大值(-Xmx指定)相同。

以下代码越过了DerictByteBuffer类,直接通过反射获取Unsafe 实例进行内存分配。

虽然使用DerictByteBuffer分配内存也会抛出内存溢出异常,但它抛出异常时并没有真正向操作系统申请分配,

而是通过计算得知内存无法分配,于是手动抛出异常,真正申请分配内存的方法是​​unsafe.allocateMemory()​

使用unsafe 分配本机内存】

/**
* VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
*/
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) at baby.oom.DirectMemoryOOM.main(DirectMemoryOOM.java:20)

由DirectMemory导致的内存溢出,明显的特征就是在 Heap Dump文件中不会看见明显的异常,

如果开发人员发现OOM异常后的Dump文件很小,而程序中又直接或间接使用了NIO,可以考虑是否是这方面的原因。

最后看遇到的问题:

java中OOM错误浅析(没有深入太多,一些粗浅的知识,可以温习用)_java

查了一下:

oracle官方给出了这个错误产生的原因和解决方法:

Exception in thread thread_name: java.lang.OutOfMemoryError: GC Overhead limit exceeded
Cause: The detail message "GC overhead limit exceeded" indicates that the garbage collector is running all the time and Java program is making very slow progress. After a garbage collection, if the Java process is spending more than approximately 98% of its time doing garbage collection and if it is recovering less than 2% of the heap and has been doing so far the last 5 (compile time constant) consecutive garbage collections, then a java.lang.OutOfMemoryError is thrown. This exception is typically thrown because the amount of live data barely fits into the Java heap having little free space for new allocations.
Action: Increase the heap size. The java.lang.OutOfMemoryError exception for GC Overhead limit exceeded can be turned off with the command line flag -XX:-UseGCOverheadLimit.

原因:

大概意思就是说,JVM花费了98%的时间进行垃圾回收,而只得到2%可用的内存,频繁的进行内存回收(最起码已经进行了5次连续的垃圾回收),JVM就会曝出ava.lang.OutOfMemoryError: GC overhead limit exceeded错误。

即 超出了GC开销限制。是JDK6新添的错误类型。是发生在GC占用大量时间为释放很小空间的时候发生的,是一种保护机制。一般是因为堆太小,导致异常的原因:没有足够的内存

java中OOM错误浅析(没有深入太多,一些粗浅的知识,可以温习用)_内存泄漏_02

 

经过分析,我使用多线程的解析文件,解析的文件信息存放占用很大的内存,而使用时,我使用其中一小部分,但是整个内存被占用着,GC无法进行回收,所以会报错。我解决的问题是把需要的信息提取出来放到内存里。减少了对内存的使用。

解决方法:

  • 1、增加heap堆内存。
  •   增大堆内存 set JAVA_OPTS=-server -Xms512m -Xmx1024m -XX:MaxNewSize=1024m -XX:MaxPermSize=1024m 
  • 2、增加对内存后错误依旧,获取heap内存快照,使用Eclipse MAT工具,找出内存泄露发生的原因并进行修复。
  • 3、优化代码以使用更少的内存或重用对象,而不是创建新的对象,从而减少垃圾收集器运行的次数。如果代码中创建了许多临时对象(例如在循环中),应该尝试重用它们。
  • 4、除了使用命令-xms1g -xmx2g设置堆内存之外,尝试在启动脚本中加入配置:

 在启动脚本中添加-XX:-UseGCOverheadLimit命令。这个方法只会把“java.lang.OutOfMemoryError: GC overhead limit exceeded”变成更常见的java.lang.OutOfMemoryError: Java heap space错误。

 

标签:Java,OOM,虚拟机,内存,OutOfMemoryError,java,main,浅析
From: https://blog.51cto.com/u_13474506/5929824

相关文章

  • 《深入理解Java虚拟机》读书笔记
    第一部分,走进Java第二部分,自动内存管理机制:第二章:Java内存区域与内存溢出异常一,Java中,虚拟机自动管理内存机制,不在需要为每一个new操作去写配对的delete和free操作,不容易......
  • 《java8高级应用与开发》读书笔记(二)
    写在前面本笔记涉及内容:类加载、反射、枚举、注解、国际化、格式化类加载:是指将类的class文件读入内存,并为之创建一个Java.lang.class对象。即当线程使用任何一个类时,系统都......
  • Ubuntu20.04 Java相关环境(JDK、Mysql、Redis、nacos、influxdb)部署以及运行
     重装了系统,系统版本号为:Ubuntu20.041、云平台登录云平台,选择要重装的服务器,关机、一键重装即可 2、安装jdk下载jdk-8u341-linux-x64.tar.gz,并复制到服务器目录下,比......
  • 浅析35kV变电站综合自动化系统的结构和功能设计
    罗轩志安科瑞电气股份有限公司上海嘉定201801 摘要:变电站综合自动化系统是一个分层分布式自动控制系统,整个系统由主控层、通信层和现地单元层三层构成。本文以35kV电站......
  • 使用javassist修改jar包里class文件
    参考:使用javassist修改jar包里class文件_淹死的鱼0719的博客-CSDN博客一、javassist依赖<dependency><groupId>org.javassist</groupId><artifactId>javassist......
  • Java调优
    https://zhuanlan.zhihu.com/p/573662668性能分析在系统层面能够影响应用性能的一般包括三个因素:CPU、内存和IO,可以从这三方面进行程序的性能瓶颈分析。1.CPU分析当程......
  • java中的时间api
    packageDataTimeTest;/*java.util.data|---java.sql.Data类1.两个构造器的使用>构造器一:Date():创建一个对应当前时间的Date对象>构造器二:创建指定毫......
  • javascript Promise
    Promise对象构造时需要一个参数,这个参数必须是一个函数。letprom=newPromise(function(resolve,reject){ console.log("Run"); //进行费时间的异步操作 ... /......
  • 浅析智能消防应急照明和疏散指示系统在工业建筑项目上的应用
    罗轩志安科瑞电气股份有限公司上海嘉定201801  摘要:随着我国社会经济的迅猛发展与城市化建设进程的加快,大型城市综合体建筑越来越多,随之而来的消防安全管理问题不容忽视......
  • 说说真实Java项目的开发流程,以及面试前的项目准备说辞
      介绍项目是必不可少的Java面试环节,求职者需要借此证明自己真实Java项目的经验,如果再做的好的话,需要借此展开自己的亮点说辞。  不过之前如果只有学习项目经验,比......