记录一次OOM
背景
写了一个接口程序(Springboot),一边是http server , 另一边开放两个udp接口接收udp消息;需要自己管理一个连接池,一边用连接来查数据,一边用来发数据。
然后压测环节发现诸多问题。
udp单线程变多线程
UDP bean 配置:
public class IntegrationConfig {
@Bean
public IntegrationFlow processUdp() {
return IntegrationFlows
.from(new UnicastReceivingChannelAdapter(9988))
.handle("udpService", "cdrHandle")
.get();
}
}
UDP 处理程序:
@Slf4j
@Component("udpService")
public class UdpService {
@Async("myThread")
public void cdrHandle(Message message) {
String data = new String((byte[]) message.getPayload());
log.info("handleData = {}", data);
}
}
修改方式: 处理方法上增加@Async
线程池配置
@Configuration
@EnableAsync
public class ThreadPoolConfig {
// 核心线程池大小
private int corePoolSize = 100;
// 最大可创建的线程数
private int maxPoolSize = 1000;
// 队列最大长度
private int queueCapacity = 200;
// 线程池维护线程所允许的空闲时间
private int keepAliveSeconds = 300;
@Bean(name = "accessSyncThread")
public ThreadPoolTaskExecutor threadPoolTaskExecutor()
{
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setMaxPoolSize(maxPoolSize);
executor.setCorePoolSize(corePoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveSeconds);
// 线程池对拒绝任务(无线程可用)的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
executor.initialize();
return executor;
}
}
参考了ThreadPoolTaskExecutor和ThreadPoolExecutor 的区别
httpclient 连接池配置
@Bean
public CloseableHttpClient httpClient(){
Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
.register("http", PlainConnectionSocketFactory.getSocketFactory())
.register("https", SSLConnectionSocketFactory.getSocketFactory())
.build();
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(registry);
//设置连接池最大是1200个连接
connectionManager.setMaxTotal(1200);
//MaxPerRoute是对maxtotal的细分,每个主机的并发最大是300
connectionManager.setDefaultMaxPerRoute(300);
RequestConfig requestConfig = RequestConfig.custom()
//返回数据的超时时间
.setSocketTimeout(2000)
//连接上服务器的超时时间
.setConnectTimeout(2000)
//从连接池中获取连接的超时时间
.setConnectionRequestTimeout(500)
.build();
return HttpClientBuilder.create().setDefaultRequestConfig(requestConfig)
.setConnectionManager(connectionManager)
.setConnectionManagerShared(true)
.build();
}
参考了httpclient连接池
还有 CloseableHttpResponse用完需要手动关闭吗
以及遇到一个报错时找到了解决方法Connection pool shut down http请求异常关闭
增加local cache 以减少http查数据的次数
对业务日志分析后发现,线程池和连接池太多,IO太多导致OOM,后使用cache存储比较常用的业务数据。
@Slf4j
public class SessionCache {
private static final String PREFIX = "KEY-";
private static final Cache<String, Object> CACHE =
CacheBuilder.newBuilder().expireAfterWrite(8, TimeUnit.HOURS).build();
public synchronized static Object getInfoCache(String key){
key = ACCESS_PREFIX + key;
return CACHE.getIfPresent(key);
}
public synchronized static void putInfoCache(String key, Object obj){
key = PREFIX + key;
CACHE.put(key, obj);
}
public synchronized static void clearInfoCache(String key){
key = PREFIX + key;
Object w = getInfoCache(key);
if(w!=null){
CACHE.invalidate(key);
}
}
}
启动增加 -Xmx1024m
改到这个程度我觉得我能做的已经没有多少了,还是OOM,最后没办法提升了Java heap size
参考java.lang.OutOfMemoryError: Java heap space
压测后mem没有降下来 想知道哪里出问题
使用dstat和top查看内存使用最高的应用
dstat --top-mem
查看进程中的线程
假如我程序的进程PID是1314520
ps p 1314520 -L -o pcpu,pmem,pid,tid,time,tname,cmd
这个敲完就发现我有很多线程 所以我适当地改了下线程池的配置
查看jstask日志
jstack -l 1314520 > jstack.log
打开日志看看线程状态
查看堆
参考了Java工程服务MEM内存过高问题处理方法
提到软件开发人员主要关注java.lang.OutOfMemoryError: Java heap space异常,减少不必要的对象创建,同时避免内存泄漏
这个命令可以查看堆里面哪些类特别多
jmap -histo:live 1314520 |head -n 100
看了下,发现有个对象占了不少 emmm 我的确是给存cache了
以上 ,目前基本可以抗住压测了
参考总览 来自巨人&大佬:
https://blog.youkuaiyun.com/qq_44754515/article/details/125805766
https://blog.youkuaiyun.com/qq_34484062/article/details/109470135
https://blog.youkuaiyun.com/daxiong0816/article/details/125283031
https://blog.youkuaiyun.com/yanwendonge/article/details/103641278
https://qqe2.com/java/post/4746.html
https://blog.youkuaiyun.com/yanwendonge/article/details/103641278