JAVA内存溢出的常见原因及代码示例

1 篇文章 0 订阅

JAVA内存溢出的常见原因及代码示例

一、堆溢出

堆是java程序中最为重要的内存空间,绝大部分的内存溢出都属于这种情况。其原因是因为大量对象占据了堆空间,而这些对象都持有强引用,导致无法回收。当对象大小之和大于有Xmx参数指定的堆空间大小时,就会发生堆溢出。

代码示例:

public class OOM {

	public static void main(String[] args) {
		//堆溢出
		List<byte[]> list = new  ArrayList<byte[]>();
		for(int i =0;i<1024000;i++) {
			list.add(new byte[1024*1024]);
		}
	}
}

运行结果:

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at springboot.jvm.OOM.main(OOM.java:16)

二、直接内存溢出

在Java的NIO中,支持直接内存的使用,可以直接获得一块堆以外的内存空间,这块空间是直接向操作系统申请的。直接内存的申请速度一般比堆内存要慢。

代码示例:

public class OOM {

	public static void main(String[] args) {
		//直接内存溢出,32位的机器可以试试,64位不会报错,-XX:+PrintGCDetails 可以查看内存回收情况
		for(int i =0;i<1024;i++) {
			ByteBuffer.allocate(1024*1024*1024);
			System.out.println(i);
		}
	}
}

三、线程过多导致内存溢出

每一个线程的开启都需要占用系统内存。线程所需的栈空间也是在堆外分配的,如果想让系统支持更多的线程,可以减少堆空间。

代码示例:

public static void main(String[] args) {
		
		//线程过多引起oom
		for(int i =0;i<1024000;i++) {
			new Thread(new SleepThread(),"Thread"+i).start();
			System.out.println("Thread"+i+" created");
		}
	}
	
	public static class SleepThread implements Runnable{

		@Override
		public void run() {
			try {
				Thread.sleep(1000000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
	}

运行结果:

Thread138280 created
Thread138281 created
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(Unknown Source)
	at springboot.jvm.OOM.main(OOM.java:27)

四、永久区溢出

永久区是存放类元数据的区域。如果一个系统定义了太多的类,那么永久区是有可能溢出的。在jdk1.8以后取消了永久区,取而代之的是元数据区域。

代码示例

public class OOM {

	public static void main(String[] args) {
		
		//永久区溢出,设置-XX:-UseGCOverheadLimit 
		URL url = null;
        List<ClassLoader> classLoaderList = new ArrayList<ClassLoader>();
        try {
            url = new File("/tmp").toURI().toURL();
            URL[] urls = {url};
            while (true){
                ClassLoader loader = new URLClassLoader(urls);
                classLoaderList.add(loader);
                loader.loadClass("springboot.nio.NewioClient");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

运行结果:
因为JDK 1.8 中已经不存在永久代,所以显示的是Java heap space。换到JDK1.6中可以正常显示。

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.lang.ClassLoader.findLoadedClass0(Native Method)
	at java.lang.ClassLoader.findLoadedClass(Unknown Source)
	at java.lang.ClassLoader.loadClass(Unknown Source)

五、GC效率低下引起的OOM

虚拟机会评估GC的效率,一旦虚拟机认为GC的效率地下,就有可能抛出OOM异常。可以通过设置-XX:-UseGCOverheadLimit 来禁止这种OOM的产生

代码示例

public class OOM {

	public static void main(String[] args) {
		
		//GC效率低下
		URL url = null;
        List<ClassLoader> classLoaderList = new ArrayList<ClassLoader>();
        try {
            url = new File("/tmp").toURI().toURL();
            URL[] urls = {url};
            while (true){
                ClassLoader loader = new URLClassLoader(urls);
                classLoaderList.add(loader);
                loader.loadClass("springboot.nio.NewioClient");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

运行结果:

[Full GC (Ergonomics) [PSYoungGen: 38399K->0K(40960K)] [ParOldGen: 87410K->1918K(60928K)] 125810K->1918K(101888K), [Metaspace: 7724K->7724K(1056768K)], 0.0190998 secs] [Times: user=0.05 sys=0.00, real=0.02 secs] 
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
	at java.net.URLClassLoader.<init>(Unknown Source)
	at springboot.jvm.OOM.main(OOM.java:25)
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
导出大量数据的时候,如果一次性获取过多数据,很容易导致内存溢出。为了避免这种情况,可以考虑采用分页查询和分批写入Excel文件的方式。以下是一种常见的分页查询和分批写入Excel文件的方式: 1. 将数据按照分页大小进行分页,每次查询一页数据。 2. 使用POI等Java Excel操作库,将查询到的一页数据分批写入Excel文件中,每次写入一定数量的数据。 3. 循环执行步骤1和步骤2,直到所有数据都导出到Excel文件中。 下面是一个简单的代码示例: ``` // 设置分页大小和每次写入的数据量 int pageSize = 1000; int batchSize = 100; // 创建Excel文件和工作表 Workbook workbook = new XSSFWorkbook(); Sheet sheet = workbook.createSheet("Sheet1"); // 查询总记录数 int total = getDataTotal(); // 分页导出数据 for(int i = 0; i < total; i += pageSize) { // 查询一页数据 List<Data> dataList = getDataByPage(i, pageSize); // 将一页数据分批写入Excel文件中 for(int j = 0; j < dataList.size(); j += batchSize) { // 创建批次数据列表 List<Data> batchList = dataList.subList(j, Math.min(j + batchSize, dataList.size())); // 将批次数据写入Excel文件中 for(int k = 0; k < batchList.size(); k++) { Data data = batchList.get(k); Row row = sheet.createRow(i + j + k); row.createCell(0).setCellValue(data.getId()); row.createCell(1).setCellValue(data.getName()); row.createCell(2).setCellValue(data.getValue()); // ... } } } // 将Excel文件写入输出流 OutputStream outputStream = response.getOutputStream(); workbook.write(outputStream); outputStream.flush(); outputStream.close(); ``` 在上面的代码中,首先设置了分页大小为1000条数据和每次写入的数据量为100条数据,然后查询总记录数,接着循环执行分页查询数据并分批写入Excel文件中,最后将Excel文件写入输出流返回给前端。这样可以避免一次性获取数据过多导致内存溢出的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值