Java OOM异常

本文详细介绍了Java中的两种错误:内存耗尽错误(OutofmemoryError)和栈溢出错误(StackOverflowError)。讨论了它们的常见原因,如无限递归、堆内存耗尽、GC开销过大、直接内存溢出和无法创建新本地线程,并提供了相应的代码示例。此外,还探讨了Java8中元空间的特点、调优以及如何替换永久代。
摘要由CSDN通过智能技术生成

一. 概述

java.lang.OutofmemoryError:内存耗尽错误,同java.lang.StackOverflowError:栈溢出错误一样,不是异常,而是错误,虽然发生这种情况的时候同样是和抛出异常一样的停止运行的形式,但这二者是属于Error类下面的
在这里插入图片描述
而异常是归属于Exception类下面的,因此要明确OutOfmemoryError和StackOverflow是错误,而不是异常;

二. 栈溢出错误:java.lang.StackOverflowError

2.1 典型出错情景:

方法不断递归调用,最终把方法栈撑爆了。

public class StackOverflowErrorDemo {
	public static void main(String[] args) {
		stackOverflowError();
	}
	public static void stackOverflowError() {
		stackOverflowError();
	}
}

三. 内存耗尽错误:java.lang.OutofmemoryError

3.1 发生OOM常见的的五大原因

发生OOM错误的情况包括但不限于以下5种:
在这里插入图片描述

3.2 java.lang.OutOfMemoryError:Java heap space

第一种,这是发生在堆内存不足的时候,通常来说,堆的初始大小一般是物理内存的1/64,最大内存是物理内存的1/4。拿16G的物理内存来说,堆的最大内存也只有4G;

场景1.如果JVM一直处于运行状态,且不断的在生成对象,而且这些对象一直都是存在引用,也就是存活的,那么就不会被垃圾回收,因此总有一个时候堆内存的所有空间都会被消耗殆尽,此时就会发生OOM错误。
场景2.可以通过-Xms:和-Xmx:参数设置堆的初始大小和最大大小,假设初始大小设置为5MB,最大大小设置为10MB,那么如果此时生成一个大于10MB的对象,那么堆内存也会被直接干爆,从而抛出OOM错误;

代码测试

先用-Xms:和-Xmx:设置堆内存初始大小设置为5MB,最大大小设置为10MB,这样可以早点把堆内存耗尽,看到错误现象。

public class JavaHeapSpace_OOMDemo {
	public static void main(String[] args) {
		String string = "atguigu";
		while(true) {
			/*直接生成一个比堆内存大的对象撑爆堆内存*/
			byte[] arr = new byte[1000*1024*1024];
			/*不断生成小对象撑爆堆内存*/
//			string+=string+new Random().nextInt(11111)+new Random().nextInt(22222);
//			string.intern();
		}
	}
}

3.3 java.lang.OutOfMemoryError:GC overhead limit exceeded

GC回收时间过长时会抛OutOfMemroyError. 过长的定义是,假设程序运行时,有超过98%的时间用来做GC,并且只回收了不到2%的堆内存;
连续多次GC都只回收了不到2%的极端情况下才会抛出。假如不抛出GC overhead limit 错误会发生什么情况呢?
那就是GC清理的这么点内存很快会再次填满,迫使GC再次执行.这样就形成恶性循环,
CPU使用率一直是100%,而GC 却没有任何成果,同时GC时产生的STW现象也会造成长时间的用户线程停顿,导致程序无法顺利运行。

在这里插入图片描述

代码测试

package InterviewTest;
import java.util.ArrayList;
import java.util.List;

/**Exception in thread "main" 
 * [Full GC (Ergonomics) java.lang.OutOfMemoryError: GC overhead limit exceeded
 * 
 * @author admin
 *   java.lang.OutOfMemoryError:
 *   GC overhead limit exceeded
 *  
 *  JVM参数配置演示
 *  -Xms5m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
 *  
 *  GC回收时间过长会抛出OutOfMemoryError。过长的定义是,超过98%的时间来做GC
 *  并且回收了不到2%的堆内存,连续多次GC都只回收了不到2%的极端情况下才会抛出。
 *  假如不抛出GCoverhead limit 错误会发生什么呢?
 *  那就是GC清理的这么点内存很快就会再次填满,迫使GC在执行,这样就形成了恶性循环
 *  CPU使用率一直都是100%,而GC却没有任何成果
 */
public class GCOverHeadDemo {
	public static void main(String[] args) {
		int i = 0;
		List<String> list = new ArrayList<String>();
		try {
			while(true) {
				list.add(String.valueOf(++i).intern());
			}
		} catch (Exception e) {
			System.out.println("******************i:"+i);
			e.printStackTrace();
			throw e;
		}
	}
}

3.4 java.lang.OutOfMemoryError:Direct buffer memory

主要由NIO引起的,因为元空间是在本地内存里面,所以不归JVM管,所以GC无法回收元空间里面的对象。而且元空间的最大内存是没有上限的,也就是说物理内存最大多少,那么元空间的最大内存就是多少;但是如果不断生成对象,导致本地内存用完了,程序就会直接崩溃,导致java.lang.OutOfMemoryError:Direct buffer memory(“直接内存溢出”)

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

  • ByteBuffer.allocate(capability)第-种方式是分配JVM堆内存,属于GC管辖范围,由于需要拷贝所以速度相对较慢
  • ByteBuffer.allocteDirect(capability)第-种方式是分配0S本地内存,不属于GC管辖范围,由于不需要内存拷贝所以速度相对较快。
    *但如果不断分配本地内存,堆内存很少使用,那么JVM就不需要执行GC,DirectByteBuffer对象们就不会被回收,
    这时候堆内存充足,但本地内存可能已经使用光了,再次尝试分配本地内存就会出现OutOfMemoryError,那程序就直接崩溃了。
    */

补充:

1.为什么用元空间替换持久代

1.持久代的大小是在启动时固定好的——很难进行调优。-XX:MaxPermSize,设置成多少好呢?
2.HotSpot的内部类型也是Java对象:它可能会在Full GC中被移动,同时它对应用不透明,且是非强类型的,难以跟踪调试,还需要存储元数据的元数据信息(meta-metadata)。
3.简化Full GC:每一个回收器有专门的元数据迭代器。
4.可以在GC不进行暂停的情况下并发地释放类数据。
5.使得原来受限于持久代的一些改进未来有可能实现

2. 元空间的特点

持久代的空间被彻底地删除了,它被一个叫元空间的区域所替代了。持久代删除了之后,很明显,JVM会忽略PermSize和MaxPermSize这两个参数,还有就是你再也看不到java.lang.OutOfMemoryError: PermGen error的异常了。
JDK 8的HotSpot JVM现在使用的是本地内存来表示类的元数据,这个区域就叫做元空间。

元空间的特点:
1.充分利用了Java语言规范中的好处:类及相关的元数据的生命周期与类加载器的一致。
2.每个加载器有专门的存储空间
3.只进行线性分配
4.不会单独回收某个类
5.省掉了GC扫描及压缩的时间
6.元空间里的对象的位置是固定的
7.如果GC发现某个类加载器不再存活了,会把相关的空间整个回收掉

元空间调优

使用-XX:MaxMetaspaceSize参数可以设置元空间的最大值,默认是没有上限的,也就是说你的系统内存上限是多少它就是多少。-XX:MetaspaceSize选项指定的是元空间的初始大小,如果没有指定的话,元空间会根据应用程序运行时的需要动态地调整大小。

MaxMetaspaceSize的调优

-XX:MaxMetaspaceSize={unlimited}
元空间的大小受限于你机器的内存
限制类的元数据使用的内存大小,以免出现虚拟内存切换以及本地内存分配失败。如果怀疑有类加载器出现泄露,应当使用这个参数;32位机器上,如果地址空间可能会被耗尽,也应当设置这个参数。
元空间的初始大小是21M——这是GC的初始的高水位线,超过这个大小会进行Full GC来进行类的回收。
如果启动后GC过于频繁,请将该值设置得大一些
可以设置成和持久代一样的大小,以便推迟GC的执行时间

**

CompressedClassSpaceSize的调优

**

只有当-XX:+UseCompressedClassPointers开启了才有效
-XX:CompressedClassSpaceSize=1G
由于这个大小在启动的时候就固定了的,因此最好设置得大点。
没有使用到的话不要进行设置
JVM后续可能会让这个区可以动态的增长。不需要是连续的区域,只要从基地址可达就行;可能会将更多的类元信息放回到元空间中;未来会基于PredictedLoadedClassCount的值来自动的设置该空间的大小

通过以下参数来指定元空间的大小:

XX:MetaspaceSize,
初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
XX:MaxMetaspaceSize,
最大空间,默认是没有限制的,也就是说物理内存有多大,元空间就有多大。
除了上面两个指定大小的选项以外,还有两个与 GC 相关的属性:

XX:MinMetaspaceFreeRatio,
当进行过Metaspace
GC之后,会计算当前Metaspace的空闲空间比,如果空闲比小于这个参数,那么虚拟机将增长Metaspace的大小。设置该参数可以控制Metaspace的增长的速度,太小的值会导致Metaspace增长的缓慢,Metaspace的使用逐渐趋于饱和,可能会影响之后类的加载。而太大的值会导致Metaspace增长的过快,浪费内存。
XX:MaxMetaspaceFreeRatio,当进行过Metaspace GC之后,
会计算当前Metaspace的空闲空间比,如果空闲比大于这个参数,那么虚拟机会释放Metaspace的部分空间。在本机该参数的默认值为70,也就是70%。
XX:MaxMetaspaceExpansion:Metaspace增长时的最大幅度。
XX:MinMetaspaceExpansion:Metaspace增长时的最小幅度。

3.5 java.lang.OutOfMemoryError:Unable to create new native thread

无法创建新的本地线程

*高并发请求服务器时,经常出现如下异常:java. Lang. OutOfNemoryError: unable to create new native thread
*准确的讲该native thread异 常与对应的平台有关

导致原因:
1.你的应用创建了太多线程了,一个应用进控创建多个线程超过系统承载极限
2.你的服务器并不允许你的应用程序创建这么多线程, linux系统默认允许单个进程可以创建的线程数是1024个,
你的应用创建超过这个数量,就会报java.lang. OutOfMemoryError: unable to create new native thread

解决办法:

  • 1.想办法降低你应用程序创建线程的数量,分析应用是否真的需要创建这么多线程,如果不是,改代码将线程数降到最低
  • 2.对于有的应用,确实需要创建很多线程远超过linux系统的默认1024个线程的限制,可以通过修改linux服务器配置,扩大linux默认限制

代码测试

在Linux虚拟机里面编译以下代码可以看到异常结果

Linux虚拟机中,普通用户的线程上限是1024
Root用户没有上限

package InterviewTest;

public class UnableCreateNewNativeThreadDemo {
	public static void main(String[] args) {
		for(int i=1;;i++) {
			System.out.println("*********i:"+i);
			new Thread(()->{
				try {
					Thread.sleep(Integer.MAX_VALUE);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			});
		}
	}
}

在这里插入图片描述

3.6 java.lang.OutOfMemoryError:Metaspace

Java 8及之后的版本使用Metaspace来替代永久代。
Metaspace是方法区HotSpot中的实现,它与持久代最大的区别在: Metaspace并不在虚拟机内存中而是使用本地内存也即在java8中classe metadata(the virtual machines internal presentation of Java class), 被存储在叫做
Metaspace的native memory
永久代(java8后被原空间Metaspace取代了) 存放了以下信息:
1.虚拟机加载的类信息
2.常量池
3.静态变量
4.即时编译后的代码
模拟Metaspace空间溢出,我们不断生成类往元空间灌,类占据的空间总是会超边Metaspace指定的空间大小的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值