Throwable类关系
java.lang.StackOverFlowError
通过迭代,反复调用方法,使栈区撑爆,默认大小是512k
/**
* Created by gysui on 2020/12/7
*/
public class StackOverFlowError {
public static void main(String[] args) {
stackOverFlowError();
}
private static void stackOverFlowError() {
stackOverFlowError();
}
}
Exception in thread “main” java.lang.StackOverflowError
java.lang.OutOfMemoryError:Java heap space
说明
堆内存溢出,通过创建大对象,或者创建创建足够多的对象模拟这个错误
/**
* Created by gysui on 2020/12/7
*/
public class OutOfMemoryError {
public static void main(String[] args) {
byte[] bytes = new byte[50*1024*1024];
}
}
错误
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.itvip666.oom.OutOfMemoryError.main(OutOfMemoryError.java:8)
java.lang.OutOfMemoryError:GC overhead limit exceeded
GC回收时间长时会抛出OutOfMemoryError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存,连续多次GC都只回收了不到2%的极端情况下才会抛出。
假设不抛出GC overhead limit错误会发生什么情况呢?
那就是GC清理的这么点内存很快会再次填满,迫使GC再次执行,这样就形成恶性循环,CPU使用率一直是100%,而GC却没有任何成果。
配置jvm运行参数模拟该错误
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
/**
* Created by gysui on 2020/12/7
*
*/
public class GCOverheadLimit {
public static void main(String[] args) {
int i = 0;
List<String> list = new ArrayList<>();
try {
while (true) {
list.add(String.valueOf(++i));
}
} catch (Exception e) {
System.out.println("---------------- i = " + i);
e.printStackTrace();
}
}
}
- 可以看到GC一直在进行垃圾回收,但是成果可见,回收前和回收后根本没有区别,因此GC觉得,我没有必要做无用功,因此会抛出 Exception in thread “main” java.lang.OutOfMemoryError: GC overhead limit exceeded
java.lang.OutOfMemoryError:Direct buffer memory(直接内存溢出)
导致原因:
写NIO程序经常使用ByteBuffer来读取或者写入数据,这是一种基于通道(Channel)与缓冲区(Buffer)的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在java堆和Native堆中来回复制数据。
ByteBuffer.allocate(capability)第一种方式是分配JVM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对较慢。
ByteBuffer.allocateDirect(capability)第二种方式是分配OS本地内存,不属于GC管辖范围,由于不需要内存拷贝,所以速度相对较快。
但如果不断分配内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象们就不会被回收,这时候堆内存充足,但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError,那程序就直接崩溃了
模拟直接内存溢出案例
模拟直接内存溢出,需要配置如下参数
-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
- XX:MaxDirectMemorySize 是直接内存大小
/**
* Created by gysui on 2020/12/7
* -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
* 只给内存分配5M的空间,但是使用的时候,需要6M,运行会报内存溢出
*/
public class DirectBufferMemory {
public static void main(String[] args) {
System.out.println("直接内存大小 MaxDirectMemorySize = " + (sun.misc.VM.maxDirectMemory()) / (double) 1024 / 1024 + "MB");
// ByteBuffer.allocateDirect(capability)方式是分配OS本地内存,不属于GC管辖范围
// ,由于不需要内存拷贝,所以速度相对较快。
// Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(6*1024*1024);
}
}
java.langOutOfMemoryError:Metaspace(元空间)
Java8及之后的版本使Metaspace来代永久代
Metaspace是方法区在 HotSpot的实现,与永久代最大的区别在于: Metaspace不在虚拟机内存中而是使本地内存也即在 java8中, classe metadata( the virtual machines internal presentation of Java class),被存储在叫做Metaspace的native memory
永久代 (java8后被元空间Metaspace取代了)存放如下信息:
- 虚拟机加载类的信息
- 常量池
- 静态变量
- 即时编译后的代码
模拟Metaspace空间溢出,我们不断生成类往元空间灌,类占据的空间总是会超过Metaspace指定的空间大小的
JVM参数
使用java -XX:+PrintFlagsInitial命令查看本机的初始化参数,-XX:Metaspacesize为218103768(大约为20.8M)
-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=10m
代码案例
package com.itvip666.oom;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* Created by gysui on 2020/12/7
*
*/
public class MetaspaceDemo {
static class Test {}
public static void main(String[] args) {
int i = 0;
try {
while (true) {
i++;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Test.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 (Exception e) {
System.out.println("================= i =" + i);
e.printStackTrace();
}
}
}
在 for 循环中, 动态生成很多class, 最终将这些class加载到 Metaspace 中。
执行这段代码, 随着生成的class越来越多, 最后将会占满 Metaspace 空间, 抛出 java.lang.OutOfMemoryError: Metaspace. 在Mac OS X上, Java 1.8.0_05 环境下, 如果设置了启动参数 -XX:MaxMetaspaceSize=64m, 大约加载 70000 个class后JVM就会挂掉。
运行结果
需要引入cglib依赖
链接:https://pan.baidu.com/s/1WYKxAyqsMb-aBS62Qko_8Q
提取码:ybnk
java.lang.OutOfMemoryError:unable to create new native thread
错误说明
不能创建更多的线程
高并发请求服务器时,经常出现如下异常:java.lang.OutOfMemoryError:unbale to create new native thread
准确的将该native thread异常与对应的平台有关。
导致原因:
应用创建了太对线程,一个应用进程创建多个线程,超过系统承载极限。
服务器并不允许应用程序创建那么多线程,linux系统默认允许单个进程可以创建的线程数是1024个,如果应用创建超过这个数量,就会报java.lang.OutOfMemoryError:unable to create new native thread
解决办法:
想办法降低应用程序创建线程的数量,分析应用是否真的需要创建那么多线程,如果不是,改代码将线程数降到最低。
对于有的应用,确实需要创建多个线程,远超过linux系统默认的1024个线程的限制,可以通过修改linux服务器配置,扩大linux默认限制。
测试代码
/**
* Created by gysui on 2020/12/7
* 高并发请求服务器时,经常出现如下异常:java.lang.OutOfMemoryError:unbale to create new native thread
*
* 导致原因:
* 应用创建了太对线程,一个应用进程创建多个线程,超过系统承载极限
*/
public class UnbaleToCreateNewNativeThreadDemo {
public static void main(String[] args) {
for (int i = 0; ; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName());
TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
},""+i).start();
}
}
}
以上代码是在linux系统中测试的
linux服务器测试
非root用户登录linux系统测试
查看最大线程数量命令:ulimit -u
centos8: 一个进程可以创建的最大进程是7718个
服务器级别调参调优
扩大服务器线程数:
# 这个文件可能没有需要自己创建
vim /etc/security/limits.d/90-nproc.conf
# 内容
* soft nproc 65535
* hard nproc 65535
[root@localhost limits.d]$ ulimit -u
7718
[root@localhost limits.d]# vim 90-nproc.conf
[root@localhost limits.d]# exit
exit
[zmnxzshen@localhost limits.d]$ su root
密码:
[root@localhost limits.d]# ulimit -u
8000