对于JVM来说,因为划分有固定的区域来执行字节码文件,无外乎,出问题的,也就是按照对应分分区会有常见的OOM问题。
栈
对于栈来说,栈的主要作用就是用于方法的执行,方法调用入栈、方法调出出栈。但是如果我们写一个递归的程序,如果层级太深,那么就会导致出现栈深度超过系统限制。
StackOverflowError
栈深度一般默认是512K。所以在实际的生产环境中,使用递归要慎重。
public static void main(String[] args) {f();}private static void f () {f();}
Exception in thread "main" java.lang.StackOverflowErrorat oom.JavaStackOverFlowerErrorDemo.f(JavaStackOverFlowerErrorDemo.java:14)
堆
堆空间主要存储的是对象,如果创建对象超过了所分配对象的空间。那么就会出现java heap space
Java. heap space
// -Xms10m -Xmx10m
public class JavaHeapSpaceDemo {public static void main(String[] args) {// 创建对象 12MB对象byte [] bytes = new byte[12 * 1024 * 1024];}}
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat oom.JavaHeapSpaceDemo.main(JavaHeapSpaceDemo.java:13)
# GC overhead limit exceeded
Gc执行有一定的限制,如果超过了98%的时间用来做GC,并且回收了不到2%的堆内存,说白了就是一定的GC时间,回收内存过小,就会出现。
如果不抛出这个异常,那么堆空间越来越小,CPU使用率100%,但是GC没有效果。
/** 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());}}
}
[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]
HeapPSYoungGen 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 1056768Kclass 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,那么程序就奔溃了。
一句话说:本地内存不足,但是堆内存充足的时候,就会出现这个问题
// -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5mpublic static void main(String[] args) {// 分配6M 堆外内存ByteBuffer.allocateDirect(6 * 1024 * 1024);}
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memoryat 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。
/**-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() {@Overridepublic 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