1. 简述OOM错误种类:
1. java.lang.StackOverflowError //这个虽然不是OOM,但是也是内存溢出错误 2. java.lang.OutOfMemoryError: Java heap space 3. java.lang.OutOfMemoryError: GC overhead limit exceeded 4. java.lang.OutOfMemoryError: Direct buffer memory 5. java.lang.OutOfMemoryError: unable to create new native thread 6. java.lang.OutOfMemoryError: Metaspace1. java.lang.StackOverflowError
栈空间溢出 ,递归调用,栈被撑爆了,正常大小的栈是542K~1024K代码演示:
// 错误产生原因:深度调用方法,导致出不来,栈爆了 public static void main(String[] args) { stackOverflowError(); } private static void stackOverflowError() { stackOverflowError(); }运行结果: 2. java.lang.OutOfMemoryError: Java heap space
堆内存溢出 private static void Demo02() { //配置 VM options: -Xms1m -Xmx1m //一句代码也可以 byte[] bytes = new byte[5 * 1024 * 1024]; } private static void Demo01() { String str = "JmStart"; while (true) { //配置 VM options: -Xms1m -Xmx1m str += new Random().nextInt(10000000); } } public static void main(String[] args) { //两种方式都可以报出异常 //Demo01(); //Demo02(); }Demo01运行结果: Demo02运行结果: 3. java.lang.OutOfMemoryError: GC overhead limit exceeded
GC回收时间过长 过长的定义是超过98%的时间用来做GC,并且回收了不倒2%的堆内存,连续多次GC,都回收了不到2%的极端情况下才会抛出。 如果不抛出,那就是GC清理的一点内存很快会被再次填满,迫使GC再次执行,这样就恶性循环。 CPU使用率一直是100%,而GC却没有任何成果。代码演示:
public static void main(String[] args) { int i = 0; List<String> list = new ArrayList<>(); try { //配置 VM options : -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m while (true) { //保证添加到集合中的对象不重复 list.add(String.valueOf(++i).intern()); } } catch (Throwable e) { System.out.println("===========i: " + i); e.printStackTrace(); throw e; } }运行结果: 4. java.lang.OutOfMemoryError: Direct buffer memory
直接内存挂了,写NIO程序经常使用ByteBuffer来读取或写入数据,这是一种基于通道(Channel)与缓存区(Buffer)的I/O方式, 它可以使用Native函数库直接分配堆外内存,然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作, 这样能在一些场景中显著提高性能,因为避免了在java堆和native堆中来回复制数据 ByteBuffer.allocate(capability) 第一种方式是分配JVM堆内存,属于GC管辖,由于需要拷贝所以速度较慢 ByteBuffer.alloctedDirect(capability)分配os本地内存,不属于GC管辖,不需要拷贝,速度较快 但如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象们就不会被回收,这时候堆内存充足, 但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError,那程序直接崩溃了。 public static void main(String[] args) { // 配置 VM options: -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m // 没有配置 VM 之前,MaxDirectMemory大小为1796.0M,约是我的电脑内存的四分之一 System.out.println("配置的MaxDirectMemory: " + (sun.misc.VM.maxDirectMemory() / (double)1024 / 1024) + "M"); ByteBuffer bb = ByteBuffer.allocateDirect(6 * 1024 * 1024); }运行结果: 5. java.lang.OutOfMemoryError: unable to create new native thread
应用创建了太多线程,一个应用进程创建了多个线程,超过系统承载极限。 你的服务器并不允许你的应用程序创建这么多线程,linux系统默认允许单个进程可以创建的线程数是1024,超过这个数量,就会报错。 解决办法: 降低应用程序创建线程的数量,分析应用给是否针对需要这么多线程,如果不是,减到最低修改linux服务器配置。 在虚拟机Linux系统中演示观看更直观。代码演示:
public static void main(String[] args) { for (int i = 1; ; i++) { new Thread(() -> { try { TimeUnit.SECONDS.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } }, ""+i).start(); } }运行结果: 6. java.lang.OutOfMemoryError: Metaspace
元空间主要存放了虚拟机加载的类的信息、常量池、静态变量、即时编译后的代码。代码演示:
static class OOMTest{ } public static void main(String[] args) { int i = 0; // 记录多次报出异常 try { while (true) { i++; // 累加 // 配置 VM options : -XX:MetaspaceSize=5m -XX:MaxMetaspaceSize=5m // 使用下面的 Enhancer,需要导 cglib.jar 和 asm.jar包 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); } }); enhancer.create(); } } catch (Throwable e) { System.out.println("===============第多少次报出异常: " + i); e.printStackTrace(); } }运行结果:
