引言:更多相关请看 JVM+GC解析系列
在java虚拟机规范中,虚拟机栈和本地方法栈都会出现StackOverflowError和OutofMemoryError,程序计数器是java虚拟机中唯一一块不会产生error的内存区域。
注意:OOM是Error,不是Exception。
如StackOverflowError的继承体系:Object->Throwable->Error->VirtualMachineError->StackOverflowError。
如OutOfMemoryError的继承体系:Object->Throwable->Error->VirtualMachineError->OutOfMemoryError。
Java.lang.StackOverflowError
(栈溢出)代表的是,当栈深度超过虚拟机分配给线程的栈大小时就会出现此error。
引发 StackOverFlowError 的常见原因有以下几种:
无限递归循环调用(最常见:对象/方法自我调用、两个对象/方法相互调用);
执行了大量方法,导致线程栈空间耗尽;
方法内声明了海量的局部变量;
代码:
@Data
class User{
public User(){
new Stu(); // Exception in thread "main" java.lang.StackOverflowError
}
}
@Data
class Stu{
public Stu(){
new User();// Exception in thread "main" java.lang.StackOverflowError
}
}
public class OOMDemo {
public OOMDemo(){
new OOMDemo();// Exception in thread "main" java.lang.StackOverflowError
}
public static void main(String[] args) throws Exception {
// new OOMDemo();// 对象自我调用
// stackOverflowError01();// 方法自我调用
// stackOverflowError02();// 两个方法相互调用
new Stu();// 两个对象相互调用
}
private static void stackOverflowError01() {
stackOverflowError01();// Exception in thread "main" java.lang.StackOverflowError
}
private static void stackOverflowError02() {
stackOverflowError03();// Exception in thread "main" java.lang.StackOverflowError
}
private static void stackOverflowError03() {
stackOverflowError02();// Exception in thread "main" java.lang.StackOverflowError
}
}
Java.lang.OutOfMemoryError:Java heap space
内存溢出之堆内存不足。 JVM参数设置最大最小内存为10m:
-Xms10m -Xmx10m -XX:+PrintGCDetails
代码:
public class OOMDemo {
public static void main(String[] args) throws Exception {
/**
* JVM参数设置最大最小内存为10m:-Xms10m -Xmx10m -XX:+PrintGCDetails
* 设定一个50M的大对象
* */
// byte[] bytes = new byte[50*1024*1024];
// 无限产生对象也会爆Error
String str = "hello";
while (true){
str += str + new Random().nextInt(333333333) + new Random().nextInt(555555555);
str.intern();
}
}
}
执行效果:
[GC (Allocation Failure) [PSYoungGen: 2048K->504K(2560K)] 2048K->879K(9728K), 0.0011060 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 789K->488K(2560K)] 1165K->911K(9728K), 0.0016022 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 488K->488K(2560K)] 911K->935K(9728K), 0.0007646 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 488K->0K(2560K)] [ParOldGen: 447K->861K(7168K)] 935K->861K(9728K), [Metaspace: 3079K->3079K(1056768K)], 0.0052523 secs] [Times: user=0.11 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] 861K->861K(9728K), 0.0003575 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (Allocation Failure) [PSYoungGen: 0K->0K(2560K)] [ParOldGen: 861K->844K(7168K)] 861K->844K(9728K), [Metaspace: 3079K->3079K(1056768K)], 0.0073385 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 2560K, used 111K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 5% used [0x00000000ffd00000,0x00000000ffd1bc18,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 7168K, used 844K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 11% used [0x00000000ff600000,0x00000000ff6d3018,0x00000000ffd00000)
Metaspace used 3157K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 344K, capacity 388K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.example.demo.test01.OOMDemo.main(OOMDemo.java:10)
Java.lang.OutOfMemeoryError:GC overhead limit exceeded
内存溢出之垃圾回收超出限制。
程序在垃圾回收上花费了98%的时间,却收集不回2%的空间,通常这样的异常伴随着CPU的冲高。
GC回收时间过长时会抛出OutOfMemoryError,过长的定义是,超过98%的时间用来做CC并且回收了不到2%的堆内存,连续多次GC却只回收了不到2%的情况下才会抛出,假如不抛出GC overhead limit错误会出现:GC清理的这么点内存快会再次填满,追使GC再次执行,没有意义,这样就形成恶性循环。CPU使用率一直都是100%,而GC却没有任何成果。
JVM参数设置:(-XX:MaxDirectMemorySize 最大直接内存大小)
-Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m
代码:
public class OOMDemo {
public static void main(String[] args) throws Exception {
int i = 0;
List<String> list = new ArrayList<>();
try {
while (true){
list.add(String.valueOf(i++).intern());
}
}catch (Throwable e){
System.out.println("******************* i:"+ i);
e.printStackTrace();
throw e;
}
}
}
执行效果:
...[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7046K->7046K(7168K)] 9094K->9094K(9728K), [Metaspace: 3457K->3457K(1056768K)], 0.0450893 secs] [Times: user=0.23 sys=0.00, real=0.05 secs]
******************* i:138816
[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7066K->7046K(7168K)] 9114K->9094K(9728K), [Metaspace: 3479K->3479K(1056768K)], 0.0482812 secs] [Times: user=0.19 sys=0.00, real=0.05 secs]
[Full GC (Ergonomics) [PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7066K->7048K(7168K)] 9114K->9096K(9728K), [Metaspace: 3512K->3512K(1056768K)], 0.0427306 secs] [Times: user=0.25 sys=0.00, real=0.04 secs]
[Full GC (Ergonomics) java.lang.OutOfMemoryError: GC overhead limit exceeded
[PSYoungGen: 2047K->2047K(2560K)] [ParOldGen: 7061K->7050K(7168K)] 9108K->9098K(9728K), [Metaspace: 3525K->3525K(1056768K)], 0.0552589 secs] [Times: user=0.36 sys=0.00, real=0.06 secs]
[Full GC (Ergonomics) [PSYoungGen: 2047K->0K(2560K)] [ParOldGen: 7065K->964K(7168K)] 9113K->964K(9728K), [Metaspace: 3539K->3539K(1056768K)], 0.0081120 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 2560K, used 61K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 3% used [0x00000000ffd00000,0x00000000ffd0f610,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 7168K, used 964K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 13% used [0x00000000ff600000,0x00000000ff6f1048,0x00000000ffd00000)
Metaspace used 3564K, capacity 4504K, committed 4864K, reserved 1056768K
class space used 389K, capacity 392K, committed 512K, reserved 1048576K
at java.lang.Integer.toString(Integer.java:401)
at java.lang.String.valueOf(String.java:3099)
at com.example.demo.test01.OOMDemo.main(OOMDemo.java:13)
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
at java.lang.Integer.toString(Integer.java:401)
at java.lang.String.valueOf(String.java:3099)
at com.example.demo.test01.OOMDemo.main(OOMDemo.java:13)
...
从执行效果可以看见,最后的Full GC几乎不能有回收了。
Java.lang.OutOfMemeoryError:Direct buffer memory
内存溢出之直接缓存。
写NIO程序经常使用ByteBuffer来读或者写入数据、这是一种基于通道( Channel)与缓冲区( Buffer)的I/0方式。它可以使Native函数库直接分配堆外内存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这样能在一些场景中显著地提高性能,因为免了在Java堆和Native堆中来回复制数据。
1.ByteBuffer.allocate(capacity);直接分配JVM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对较慢。
2.ByteBuffer.allocateDirect(capacity);分配OS本地内存,不属于GC管辖,由于不需要内存拷贝所以速度相对较快。但如果不断分配本地内存,JVM堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象就不会被回收。这时候堆内存充足,但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError
,那程序直接就奔溃了。
案例
代码:
/**
* 配置参数:-Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails
* 故障现象:Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
* 写NIO程序经常使用ByteBuffer来读或者写入数据、这是一种基于通道( Channel)与缓冲区( Buffer)的I/0方式。它可以使Native函数库直接分配堆外内
* 存,然后通过一个存储在Java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。这
* 样能在一些场景中显著地提高性能,因为免了在Java堆和Native堆中来回复制数据。
* 1.ByteBuffer.allocate(capacity);直接分配JVM堆内存,属于GC管辖范围,由于需要拷贝
* 所以速度相对较慢。
* 2.ByteBuffer.allocateDirect(capacity);分配OS本地内存,不属于GC管辖,由于不需要内
* 存拷贝所以速度相对较快。但如果不断分配本地内存,JVM堆内存很少使用,那么JVM就不需要
* 执行GC,DirectByteBuffer对象就不会被回收。这时候堆内存充足,但本地内存可能已经使用
* 光了,再次尝试分配本地内存就会出现OutOfMemoryError
* ,那程序直接就奔溃了。
*/
public class OOMDemo {
public static void main(String[] args) throws Exception {
System.out.println("分配的内存:" + (sun.misc.VM.maxDirectMemory() / (double) 1024 / 1024) + "MB");
try {
Thread.sleep(200);
} catch (Throwable e) {
e.printStackTrace();
}
// 实际为5m 这里分配为6m,故意使坏。
ByteBuffer allocate = ByteBuffer.allocateDirect(6 * 1024 * 1024);
}
}
执行效果:
[GC (Allocation Failure) [PSYoungGen: 2048K->504K(2560K)] 2048K->877K(9728K), 0.0008484 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
分配的内存:5.0MB
[GC (System.gc()) [PSYoungGen: 990K->504K(2560K)] 1363K->1021K(9728K), 0.0012199 secs] [Times: user=0.06 sys=0.00, real=0.00 secs]
[Full GC (System.gc()) [PSYoungGen: 504K->0K(2560K)] [ParOldGen: 517K->937K(7168K)] 1021K->937K(9728K), [Metaspace: 3298K->3298K(1056768K)], 0.0070385 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]
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 com.example.demo.test01.OOMDemo.main(OOMDemo.java:29)
Heap
PSYoungGen total 2560K, used 61K [0x00000000ffd00000, 0x0000000100000000, 0x0000000100000000)
eden space 2048K, 2% used [0x00000000ffd00000,0x00000000ffd0f448,0x00000000fff00000)
from space 512K, 0% used [0x00000000fff80000,0x00000000fff80000,0x0000000100000000)
to space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
ParOldGen total 7168K, used 937K [0x00000000ff600000, 0x00000000ffd00000, 0x00000000ffd00000)
object space 7168K, 13% used [0x00000000ff600000,0x00000000ff6ea4c8,0x00000000ffd00000)
Metaspace used 3329K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 360K, capacity 388K, committed 512K, reserved 1048576K
Java.lang.OutOfMemeoryError:unable to create new native thread
内存溢出之不能创建一个新的原生线程。
高并发请求服务器时,经常出如下异常:Java.lang.OutOfMemeoryError:unable to create new native thread,精确的讲Native thread异常与对应的平台有关。
导致原因:
1.应用创建了太多线程,一个应用进程创建多个线程,超过系统就极限。
2服务器并不允许应用程序创建这么多线程,Linux系统允许单个进程可创建的线程数是1024个。应用创建线程超过这个数量,就会报Java.lang.OutOfMemeoryError:unable to create new native thread。
解决办法:
1.降低应用程序创建线程的数量,分折应用是否真的需便的建这么多线君,如果不是改代码将线程数降到最低。
2.对于有的应用确实需要创建很多线程,远超过inux系统的默认1024个线程的限制可以通过修改Linux服务器配置,扩大Linux默认限制。
注意:linux不一定是1024,具体要看linux服务器相关配置文件,参数:
vim /etc/security/limits.d/20-nproc.conf
我的是4096

案例
将上面代码上传到linux,并使用非root用户登录Linux系统测试行:
public class OOMDemo {
public static void main(String[] args) throws Exception {
for (int i = 1; ; i++) {
System.out.println("================i : " + i);
new Thread(() -> {
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
执行效果:(注意:我的默认限制是4096,至于为什么没到4096就报异常,是因为系统默认会占一些线程)
...
================i : 3769
================i : 3770
================i : 3771
================i : 3772
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
at java.lang.Thread.start0(Native Method)
at java.lang.Thread.start(Thread.java:717)
at OOMDemo.main(OOMDemo.java:15)
...
服务器级别参数调优
在linux服务器打开配置文件,参数:
vim /etc/security/limits.d/20-nproc.conf
内容如下:
# Default limit for number of user's processes to prevent
# accidental fork bombs.
# See rhbz #432903 for reasoning.
* soft nproc 4096
root soft nproc unlimited
可以看到root用户不做限制unlimited,其它用户4096,如果想设置自己用户的单个线程大小,可添加下面内容,sire用户的单个最大限制线程是3000:
# Default limit for number of user's processes to prevent
# accidental fork bombs.
# See rhbz #432903 for reasoning.
* soft nproc 4096
root soft nproc unlimited
sire sotf nproc 3000
Java.lang.OutOfMemeoryError:Metaspace
内存溢出之元空间。
VM参数设置:-XX:MetaspaceSize=8m -XX:MaxMetaspaceSize=8m
Jova8及之后的版本使用Metaspace来代替永久代。Metaspace是方法区在HotSpot中的实现,与持久代最大的区別在于: Metaspace并不在虚拟机内而是使用本地内存。也既在Java8中,class metadata(the virtual machines internal presentation of Java class),被存在叫做Metaspace的native memory里,永久代(jova8后被元空间Metaspace取代了)存放ア以下信息:虚拟机加载的类信息、常量池、静态交量、即时编译后的代码。
模拟Metaspace元空间溢出,不断生成类往元空间灌,类占据的空间总是会超过Metaspace指定的空间大小的。
使用Java -XX:+PrintFlagsInitial命令查看本机的初始化参数,-XX:MetaspaceSize为21810376B(约20M)。

案例
VM参数设置:
-XX:MetaspaceSize=18m -XX:MaxMetaspaceSize=18m
代码:
/**
*VM参数设置:-XX:MetaspaceSize=18m -XX:MaxMetaspaceSize=18m
* Jova8及之后的版本使用Metaspace来代替永久代。Metaspace是方法区在HotSpot中的实现,与持久代最大的区
* 別在于: Metaspace并不在虚拟机内而是使用本地内存。也既在Java8中,class metadata(t
* he virtual machines internal presentation of Java class),被存在叫做Metaspace
* 的native memory里,永久代(jova8后被元空间Metaspace取代了)存放ア以下信息:虚拟机
* 加载的类信息、常量池、静态交量、即时编译后的代码。
* 模拟Metaspace元空间溢出,不断生成类往元空间灌,类占据的空间总是会超过Metaspace指
* 定的空间大小的。
*/
public class OOMDemo {
static class OOMClass{}
public static void main(String[] args) throws Exception {
int i = 0;
try{
while (true){
i++;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(OOMClass.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();
}
}
}
执行效果:
===============多少次后发生异常 : 1519
java.lang.OutOfMemoryError: Metaspace
at org.springframework.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:530)
at org.springframework.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:363)
at org.springframework.cglib.proxy.Enhancer.generate(Enhancer.java:582)
at org.springframework.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:131)
at org.springframework.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:319)
at org.springframework.cglib.proxy.Enhancer.createHelper(Enhancer.java:569)
at org.springframework.cglib.proxy.Enhancer.create(Enhancer.java:384)
at com.example.demo.test01.OOMDemo.main(OOMDemo.java:37)