项目场景:
一个运行了几年的项目,由于服务接收服务中的一家数据接收端口停止,从而引起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)在堆内存中无法分配足够的内存来满足应用程序的需求。这个错误通常是由于以下几种原因引起的:
常见原因
-
堆内存设置过小:
- JVM 分配给堆的最大内存太小,无法满足应用程序的需求。
-
内存泄漏:
- 应用程序中有对象没有被及时回收,导致内存无法释放,从而引发内存耗尽。
-
大数据集合或对象:
- 应用程序处理的数据量过大,特别是当你试图将大量数据加载到内存中时。
-
无限制的数据增长:
- 某些数据结构(如
ArrayList
、HashMap
等)随着时间推移不断增长,导致内存耗尽。
- 某些数据结构(如
解决方案
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.Stream
或InputStream
)来逐步处理数据,而不是一次性加载所有数据。
3.2 检查并清理不必要的引用
- 弱引用和软引用:使用
WeakReference
或SoftReference
代替强引用,让垃圾回收器在内存不足时更容易回收不再使用的对象。 - 手动释放资源:确保在使用完大量占用内存的资源后及时释放,如关闭流、清理缓存等。
3.3 优化数据结构
- 选择合适的数据结构:确保使用合适的数据结构存储数据。例如,避免使用过大的
HashMap
,可以考虑使用ConcurrentHashMap
等结构,它们对内存的占用更合理。 - 减少不必要的缓存:在使用缓存时,控制缓存的大小,避免无限制地增长。
4. 分析和优化垃圾回收(GC)
如果你的应用程序频繁触发 GC 而且每次 GC 的时间过长,可能需要优化 GC 设置:
- 选择合适的垃圾回收器:不同的垃圾回收器在不同的场景下表现不同。可以根据应用程序的特性选择合适的垃圾回收器(如 G1、CMS、ZGC 等)。
- 调整 GC 参数:通过调整 GC 的参数,如
-XX:MaxGCPauseMillis
、-XX:G1HeapRegionSize
等,来优化 GC 的性能。
总结
- 增大 JVM 堆内存 是最直接的解决方案,但这通常只是缓解问题的第一步。
- 分析并优化代码 才是长期解决方案。通过内存分析工具找到内存泄漏或优化内存占用,可以有效预防类似问题。
- 优化垃圾回收 则是确保在高内存压力下应用仍能稳定运行的重要手段。
通过上述步骤,你应该能够找出并解决 OutOfMemoryError
错误的根本原因。