【JVM】聊聊JVM生产环境常见的OOM问题

对于JVM来说,因为划分有固定的区域来执行字节码文件,无外乎,出问题的,也就是按照对应分分区会有常见的OOM问题。

对于栈来说,栈的主要作用就是用于方法的执行,方法调用入栈、方法调出出栈。但是如果我们写一个递归的程序,如果层级太深,那么就会导致出现栈深度超过系统限制。

StackOverflowError

栈深度一般默认是512K。所以在实际的生产环境中,使用递归要慎重。

java 复制代码
public static void main(String[] args) {
        f();
    }

    private static void f () {
        f();
    }
java 复制代码
Exception in thread "main" java.lang.StackOverflowError
	at oom.JavaStackOverFlowerErrorDemo.f(JavaStackOverFlowerErrorDemo.java:14)

堆空间主要存储的是对象,如果创建对象超过了所分配对象的空间。那么就会出现java heap space

Java. heap space

java 复制代码
// -Xms10m -Xmx10m
public class JavaHeapSpaceDemo {

    public static void main(String[] args) {
        // 创建对象 12MB对象
        byte [] bytes = new byte[12 * 1024 * 1024];
    }

}
java 复制代码
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at oom.JavaHeapSpaceDemo.main(JavaHeapSpaceDemo.java:13)

# GC overhead limit exceeded

Gc执行有一定的限制,如果超过了98%的时间用来做GC,并且回收了不到2%的堆内存 ,说白了就是一定的GC时间,回收内存过小,就会出现。

如果不抛出这个异常,那么堆空间越来越小,CPU使用率100%,但是GC没有效果。

java 复制代码
 /*
 *  JVM参数配置: -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
 *
 */
public class GcOverheadLimitDemo {

    public static void main(String[] args) {
        int i = 0;
        List<String> list = new ArrayList<>();
        while (true) {
            list.add(String.valueOf(++i).intern());
        }
    }
}
java 复制代码
[Full GC (Allocation Failure) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7164K->7164K(7168K)] 9212K->9212K(9728K), [Metaspace: 3263K->3263K(1056768K)], 0.0693555 secs] [Times: user=0.42 sys=0.01, real=0.07 secs] 
[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7165K->7164K(7168K)] 9213K->9212K(9728K), [Metaspace: 3264K->3264K(1056768K)], 0.0564705 secs] [Times: user=0.32 sys=0.00, real=0.05 secs] 
[Full GC (Ergonomics) [PSYoungGen: 2047K->0K(2560K)] [ParOldGen: 7167K->394K(7168K)] 9215K->394K(9728K), [Metaspace: 3268K->3268K(1056768K)], 0.0044834 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 2560K, used 66K [0x00000007bfd00000, 0x00000007c0000000, 0x00000007c0000000)
  eden space 2048K, 3% used [0x00000007bfd00000,0x00000007bfd10bb8,0x00000007bff00000)
  from space 512K, 0% used [0x00000007bff80000,0x00000007bff80000,0x00000007c0000000)
  to   space 512K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007bff80000)
 ParOldGen       total 7168K, used 394K [0x00000007bf600000, 0x00000007bfd00000, 0x00000007bfd00000)
  object space 7168K, 5% used [0x00000007bf600000,0x00000007bf662be0,0x00000007bfd00000)
 Metaspace       used 3295K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 363K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded

堆外内存

Direct buffer memory

写NIO程序的时候经常会使用ByteBuffer来读取或写入数据,这是一种基于通道(Channel) 与 缓冲区(Buffer)的I/O方式,它可以使用Native 函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

ByteBuffer.allocate(capability):第一种方式是分配JVM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对较慢

ByteBuffer.allocteDirect(capability):第二种方式是分配OS本地内存,不属于GC管辖范围,由于不需要内存的拷贝,所以速度相对较快

但如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象就不会被回收,这时候怼内存充足,但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError,那么程序就奔溃了。

一句话说:本地内存不足,但是堆内存充足的时候,就会出现这个问题

java 复制代码
    // -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
    public static void main(String[] args) {
        // 分配6M 堆外内存
        ByteBuffer.allocateDirect(6 * 1024 * 1024);
    }
java 复制代码
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
	at java.nio.Bits.reserveMemory(Bits.java:694)
	at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
	at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
	at oom.DirectMemorySizeDemo.main(DirectMemorySizeDemo.java:13)

这时候就会出现异常,堆外内存不够用。

本地方法栈

unable to create new native thread

这个异常其实就是,在应用进程内创建的线程超过进程限制格式,默认是1024个。

【复盘】记录一次JVM 异常问题 java.lang.OutOfMemoryError: unable to create new native thread

元空间

java.lang.OutOfMemoryError:Metaspace

元空间就是我们的方法区,存放的是类模板,类信息,常量池等.一般出现这个错误,要么就是框架动态生成的类太多,或者 引用的 jar 包加载了很多 class。

java 复制代码
/**
  -XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m
 * @author qxlx
 * @date 2024/2/25 22:00
 */
public class MetaspaceOutOfMemoryDemo {

    static class OOMTest {

    }

    public static void main(String[] args) {
        int i = 0;
        try {
            while (true) {
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOMTest.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        return methodProxy.invokeSuper(o,args);
                    }
                });
            }
        } catch (Exception e) {
            System.out.println("i="+i);
            e.printStackTrace();
        }
    }

}

java.lang.OutOfMemoryError:Metaspace
相关推荐
longlongqin14 分钟前
JVM 内存结构?
jvm
Joeysoda9 小时前
Java数据结构 时间复杂度和空间复杂度
java·开发语言·jvm·数据结构·学习·算法
18你磊哥9 小时前
java重点学习-JVM组成
java·开发语言·jvm
蜜桃小阿雯11 小时前
JAVA开源项目 校园美食分享平台 计算机毕业设计
java·jvm·spring boot·spring cloud·intellij-idea·美食
longlongqin12 小时前
JIT(即时编译)技术
jvm
ZachOn1y18 小时前
Java 入门指南:JVM(Java虚拟机)—— 双亲委派模型(Parent Delegation Model)
java·jvm·后端·java-ee·团队开发·个人开发
寻求出路的程序媛20 小时前
JVM —— 类加载器的分类,双亲委派机制
java·jvm·面试
深鱼~20 小时前
【JVM】垃圾回收机制|死亡对象的判断算法|垃圾回收算法
jvm
Lill_bin20 小时前
JVM内部结构解析
jvm·后端·spring cloud·微服务·云原生·ribbon
视觉小鸟20 小时前
【JVM安装MinIO】
前端·jvm·chrome