工作中常见的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语句块来捕获和处理异常。具体的流程如下:
- 使用try 块包裹可能会出现抛出异常的代码块。一旦try块中发生了异常,程序的控制流会立即跳转到与之对应的catch块
- 在catch块中,可以指定捕获特定类型的异常,并提供相应的处理逻辑。如果发生了指定类型的异常,程序会跳转到相应的catch块中进行处理。一个try块可以有多个catch块,分别处理不同类型的异常。
- 如果某个catch块成功处理了异常,程序将继续执行catch块之后的代码。
- 在catc块中,可以通过throw 语句重新抛出异常,将异常交给上一级的调用者处理。
- 可以使用finally 块来定义无论是否发生异常都需要执行的代码。finally块中的代码使用 无论异常是否被捕获。
通过合理使用异常处理机制,可以是程序更具健壮性和容错性。在处理异常时,应根据具体情况选择是否恢复正常运行,报告错误给用户,还是中止程序运行。同时应便面过度捕获异常和不处理异常导致的问题,以及使用异常代理正常的流程控制的做法。