重温Java - Java基础二

发布于:2025-04-10 ⋅ 阅读:(33) ⋅ 点赞:(0)

工作中常见的6中OOM 问题

堆内存OOM

堆内存OOM 是最常见的OOM了。出现堆内存OOM 问题的异常信息如下

java.lang.OutOfMemoryError: Java heap space

此OOM是由于Java中的heap的最大值,已经不能满足需求了。

举个例子

@Test
public void test01(){
    List<OOMTest> list = Lists.newArrayList();
    while(true){
        list.add(new OOMTests());
    }
}

这里创建了一个List 集合,在一个死循环中不停的往里添加对象。

执行结果:

在这里插入图片描述

出现了java.lang.OutOfMemoryError:Java heap space 的堆内存溢出。

很多时候,execl一次性到处大量的数据,获取在程序中一次性查询的数据太多,都可能出现这种OOM的问题。

栈内存OOM

有时候,我们的业务系统创建了太多的线程,可能会导致栈内存OOM。

出现栈内存OOM 问题的异常信息如下:

java.lang.OutOfMemory:unable to create new native thread	

给举个例子:

public class StartOOMTest{
    public static void main(String[] args){
        while(true){
            new Thread().start();
        }
    }
}

使用一个死循环不停的创建线程,导致系统产生了大量的线程。

如果实际工作中出现了这个问题,一般是由于创建的线程太多了,或者设置的单个线程占用的内存空间太大导致的。

建议在日常工作中,多用线程池,少自己创建线程,防止这种OOM

栈内存溢出

我们在业务代码中可能会经常写一些递归 调用,如果递归深度超过了JVM 允许的对打深度,可能会出现占内存溢出的问题。

出现占内存溢出的异常信息如下:

java.lang.StackOverflowError

举个示例:

@Test
public void test03(){
    recursiveMethod();
}
public static void recursiveMethod(){
        recursiveMethod();
}

执行结果如下:

在这里插入图片描述

出现了java.lang.StackOverflowError栈溢出的错误

我们在写递归代码时,一定要考虑递归深度。即使是使用parentId 一层层网上找的逻辑,也最好加一个参数控制递归的深度。防止因为数据问题导致无限递归的情况,比如:id和ParentId 的值像等。

接内存OOM

直接内存 不是虚拟机运行时数据分区的一部分,也不是《Java虚拟机规范》中定义的内存区域。

它源于NIO ,通过存在堆中的DirectByteBuffer操作Native内存,是属于堆外内存 ,也可以直接想系统申请的内存空间

直接出现内存OOM 问题的异常信息如下:

java.lang.OutOfMemoryError:Direct buffer memory

例如下面这样的:

private static final int BUFFER = 1024*1024*20;
@Test
public void test04(){
    ArrayList<ByteBuffer> list = new ArrayList();
    int count = 0;
    try{
        while(true){
            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
            list.add(byteBuffer);
            count++;
            try{
                Thread.sleep(300);
               
            }catch(InterruptException e){
                e.printStackTrace();
            }
        }
    }final{
        System.out.println(count);
    }
}

会看到报出来java.lang.OutOfMemoryError:Direct buffer memory 直接内存空间不足。

GC OOM

GC OOM 是由于JVM 在GC 时,对象过多,导致内存溢出,建议直接调整GC 的策略。

出现了GO OOM 问题时异常信息如下:

java.lang.OutOfMemoryError:GC overhead limit exceeded

为了方便测试,我们先将Idea中的最大和最小堆内存都设置成10M

例如下面的例子:

public class GCOverheadOOM{
    public static void main(){
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for(int i = 0;i<Integer.MAX_VALUE;i++){
            executor.execute(()->{
                try{
                    Thread.sleep(10000);
                }catch(InterruptedException e){
                    
                }
            });
        }
    }
}

出现这个问题是由于JVM 在GC 的时候 ,对象太多,就会报这个错误。

我们需要改变GC 的策略。

在老代80%时就开始GC,并且将-XX:SurvivorRatio (-XX:SurvivoRatio=8)和-XX:NewRatio (-XX:NewRatio=4)设置的更合理。

元空间 OOM

JDK8 之后使用MateSpace来替代永久代,Matespace是方法区HotSpot中的实现。

Metaspace不在虚拟机内存中,而是使用本地内存也就是在JDK8的ClassMetadata ,也被存储在叫做Metaspace的Native memory。

出现元空间OOM 问题时异常信息如下:

java.lang.OutOfMemoryError:Metaspace

为了方便测试,我们修改一下idea中的JVM 参数,增加下面的配置:

-XX;MetasoaceSize=10M -XX:MaxMetaspaceSize=41m

这里指定了元空间和最大元空间都是10M。

接下来,看看下面这行例子

public class MetaspaceOOMTest{
    static class OOM{
        
    }
    public static void main(String[] args){
        int i = 0;
        try{
            while(true){
                i++;
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperClass(OOM.class);
                enhancer.setUserCache(false);
                enhancer.setCallback(new MethodInterceptor(){
                    public Object interceptor(Object o,Method method,Object[] objects,MethodProxy methodProxy) throws Throwable{
                        return methodProxy.invokeSuper(o,args);
                    }
                });
                enhandcer.create();
            }
        }catch(Throwable e){
            e.printStackTrace();
        }
    }
}

程序最后汇报java.lang.OutOfMemoryError:Metaspace的元空间OOM。

这一类问题一般是由于加载到内存中的类太多,或者类的体积太大导致。

Java中的异常处理机制是怎样的

异常是在程序执行的过程中可能出现的错误或意外情况。他们通常表示了程序无法正常处理的情况,如除零错误,空指针引用、文件不存在等。

Java中异常的处理机制通过使用try-catch-finally语句块来捕获和处理异常。具体的流程如下:

  1. 使用try 块包裹可能会出现抛出异常的代码块。一旦try块中发生了异常,程序的控制流会立即跳转到与之对应的catch块
  2. 在catch块中,可以指定捕获特定类型的异常,并提供相应的处理逻辑。如果发生了指定类型的异常,程序会跳转到相应的catch块中进行处理。一个try块可以有多个catch块,分别处理不同类型的异常。
  3. 如果某个catch块成功处理了异常,程序将继续执行catch块之后的代码。
  4. 在catc块中,可以通过throw 语句重新抛出异常,将异常交给上一级的调用者处理。
  5. 可以使用finally 块来定义无论是否发生异常都需要执行的代码。finally块中的代码使用 无论异常是否被捕获。

通过合理使用异常处理机制,可以是程序更具健壮性和容错性。在处理异常时,应根据具体情况选择是否恢复正常运行,报告错误给用户,还是中止程序运行。同时应便面过度捕获异常和不处理异常导致的问题,以及使用异常代理正常的流程控制的做法。