后台因http请求的一个参数引起java内存溢出。

项目场景:

一个运行了几年的项目,由于服务接收服务中的一家数据接收端口停止,从而引起java内存溢出报错。


问题描述

一个运行了几年的项目,项目有部分线程是负责实时传输数据到省系统,由于今天省系统端口关闭,数据上传中卡住并且引发一系列连接请求,不断消耗内存,堆内存满了从而引起整个系统崩溃。如下图:

从上面图片中可以看到大量的请求报错(报错重复,只截一小部分),前部分大量的http连接请求失败,后面的报错还带有java heap OutOfMemoryError 堆内存溢出。


查看引起报错发送数据关键代码:

public class GDEVHttpClient {
	private static Logger log = LoggerFactory.getLogger(GDEVHttpClient.class);
	private static final int REQUEST_TIMEOUT = 60 * 1000;// 设置请求超时1分钟
	private static final int SO_TIMEOUT = 60 * 1000; // 设置等待数据超时时间1分钟
    public static final int HTTP_MAX_CONNECTIONS = 200;
	
	private static PoolingHttpClientConnectionManager connManager = null;
	private static RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(SO_TIMEOUT).setConnectTimeout(REQUEST_TIMEOUT).build();//设置请求和传输超时时间
	private static CloseableHttpClient client;
	public static IdleConnectionMonitorThread monitor;

	static {
		connManager = new PoolingHttpClientConnectionManager();
		connManager.setMaxTotal(HTTP_MAX_CONNECTIONS);//设置整个连接池最大连接数
		connManager.setDefaultMaxPerRoute(connManager.getMaxTotal());
		client = HttpClients.custom().setConnectionManager(connManager).build();

	}


	public static String doPostForGDEV(String url, String params,String token) throws IOException {


		HttpPost httpPost = new HttpPost(url);
		httpPost.setConfig(requestConfig);
		CloseableHttpResponse response = null;
		HttpEntity entity = null;
		try {
			if(token!=null)
				httpPost.addHeader("Authorization","Bearer " + token);//Bearer后有空格
			httpPost.setEntity(new StringEntity(params, HTTP.UTF_8));

			long startTime = System.currentTimeMillis();
			response = client.execute(httpPost);
			long endTime = System.currentTimeMillis();
			long t = endTime - startTime;
			if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
				entity = response.getEntity();
				String returnStr = EntityUtils.toString(entity, HTTP.UTF_8);
				return returnStr;
			}else{
				log.error("url="+url+",params="+params+",getStatusCode="+response.getStatusLine().getStatusCode());
			}
		}catch (Exception e) {
			log.error(e.getMessage(), e);
		}finally{

			EntityUtils.consume(entity);
			httpPost.abort();
			if(response!=null){
				response.close();
			}

		}

		return null;
	}

原因分析:

从代码上看到http请求的超时与等待参数具然设置60秒 ???这个接收方服务器正常是没问题,但是如果对方一旦出现问题,像慢或者关闭了,你这里每个请求都要等60秒,正常传送才0.2-0.3秒,所以对于大数据量的传送这是多么可怕,积1分钟的量占用内存就是正常时的大概是240倍,占用内存=240 * 每个连接占用的内存,这是多么大的一个数,内存不爆才怪。


解决方案:

本次内存java溢出原因是因为http参数设置不合理,重新设置代码中http请求的超时参数,改为3秒以内更合理,保证3秒内连接超时就关闭,结束本次连接,少占用内存。

  代码如下:

    private static final int REQUEST_TIMEOUT = 2000;
    private static final int SO_TIMEOUT = 2000; // 
    public static final int HTTP_MAX_CONNECTIONS = 200;

 修改重新发布后,项目虽有些http连接报错(有些商家的服务是正常的),但此时占用的内存并不大,并不再报java内存堆溢出错误,查看内存占用也正常,一切恢复正常。

另外别看到内存溢出就别急着设置xmx虚拟机内存参数,先找原因,很多时候往往是代码引起的。

下面也介绍一下java内存堆溢出的解决方案。

关于java.lang.OutOfMemoryError解决

java.lang.OutOfMemoryError: Java heap space 错误是由于 Java 虚拟机(JVM)在堆内存中无法分配足够的内存来满足应用程序的需求。这个错误通常是由于以下几种原因引起的:

常见原因

  1. 堆内存设置过小

    • JVM 分配给堆的最大内存太小,无法满足应用程序的需求。
  2. 内存泄漏

    • 应用程序中有对象没有被及时回收,导致内存无法释放,从而引发内存耗尽。
  3. 大数据集合或对象

    • 应用程序处理的数据量过大,特别是当你试图将大量数据加载到内存中时。
  4. 无限制的数据增长

    • 某些数据结构(如 ArrayListHashMap 等)随着时间推移不断增长,导致内存耗尽。

解决方案

1. 增加 JVM 堆内存大小

你可以通过增加 JVM 的堆内存设置来解决这个问题。可以通过在启动 Java 应用程序时使用 -Xmx 参数来设置堆内存的最大值。例如:

java -Xmx2g -Xms1g -jar your-application.jar
  • -Xmx2g: 设置 JVM 最大堆内存为 2GB。
  • -Xms1g: 设置 JVM 初始堆内存为 1GB。

具体的值应根据应用程序的需求和可用的物理内存进行调整。

2. 分析和优化内存使用

如果只是简单地增加堆内存无法解决问题,你需要分析应用程序的内存使用情况,找出并修复内存泄漏或优化内存使用。

2.1 使用内存分析工具
  • VisualVM:JDK 自带的内存分析工具,可以帮助你实时监控应用的内存使用,分析堆转储文件,找出导致内存耗尽的对象。
  • Eclipse MAT (Memory Analyzer Tool):一个强大的内存分析工具,可以帮助你分析 Java 堆转储文件,并找出内存泄漏的根源。
2.2 生成并分析堆转储

OutOfMemoryError 发生时,可以生成堆转储文件(heap dump),以便后续分析:

java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumpfile
  • -XX:+HeapDumpOnOutOfMemoryError: 当发生 OutOfMemoryError 时自动生成堆转储文件。
  • -XX:HeapDumpPath: 指定堆转储文件的保存路径。

使用上述工具分析堆转储文件,查找那些占用大量内存的对象,并分析它们的生命周期。

3. 优化代码
3.1 避免将大量数据加载到内存中

如果可能,避免将大数据集全部加载到内存中,可以采用以下策略:

  • 使用分页查询:当从数据库中加载数据时,使用分页技术,避免一次性加载过多的数据。
  • 流式处理:使用流处理(如 java.util.stream.StreamInputStream)来逐步处理数据,而不是一次性加载所有数据。
3.2 检查并清理不必要的引用
  • 弱引用和软引用:使用 WeakReferenceSoftReference 代替强引用,让垃圾回收器在内存不足时更容易回收不再使用的对象。
  • 手动释放资源:确保在使用完大量占用内存的资源后及时释放,如关闭流、清理缓存等。
3.3 优化数据结构
  • 选择合适的数据结构:确保使用合适的数据结构存储数据。例如,避免使用过大的 HashMap,可以考虑使用 ConcurrentHashMap 等结构,它们对内存的占用更合理。
  • 减少不必要的缓存:在使用缓存时,控制缓存的大小,避免无限制地增长。

4. 分析和优化垃圾回收(GC)

如果你的应用程序频繁触发 GC 而且每次 GC 的时间过长,可能需要优化 GC 设置:

  • 选择合适的垃圾回收器:不同的垃圾回收器在不同的场景下表现不同。可以根据应用程序的特性选择合适的垃圾回收器(如 G1、CMS、ZGC 等)。
  • 调整 GC 参数:通过调整 GC 的参数,如 -XX:MaxGCPauseMillis-XX:G1HeapRegionSize 等,来优化 GC 的性能。

总结

  • 增大 JVM 堆内存 是最直接的解决方案,但这通常只是缓解问题的第一步。
  • 分析并优化代码 才是长期解决方案。通过内存分析工具找到内存泄漏或优化内存占用,可以有效预防类似问题。
  • 优化垃圾回收 则是确保在高内存压力下应用仍能稳定运行的重要手段。

通过上述步骤,你应该能够找出并解决 OutOfMemoryError 错误的根本原因。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qyhua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值