记录一次线上OOM
发现服务宕机两次,于是查看日志,发现错误如下:
原因:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | datab 15 : 46 : 59.302 [SimpleAsyncTaskExecutor- 7 ] ERROR o.s.a.i.SimpleAsyncUncaughtExceptionHandler - Unexpected error occurred invoking async method 'public void com.chinadatab.job.ScheduledTasks.lucene_enterprise()' . java.lang.StackOverflowError: null at sun.nio.fs.WindowsNativeDispatcher.CreateFile0(Native Method) at sun.nio.fs.WindowsNativeDispatcher.CreateFile(Unknown Source) at sun.nio.fs.WindowsChannelFactory.open(Unknown Source) at sun.nio.fs.WindowsChannelFactory.newFileChannel(Unknown Source) at sun.nio.fs.WindowsFileSystemProvider.newByteChannel(Unknown Source) at java.nio.file.Files.newByteChannel(Unknown Source) at java.nio.file.Files.createFile(Unknown Source) at org.apache.lucene.store.NativeFSLockFactory.obtainFSLock(NativeFSLockFactory.java: 98 ) at org.apache.lucene.store.FSLockFactory.obtainLock(FSLockFactory.java: 41 ) at org.apache.lucene.store.BaseDirectory.obtainLock(BaseDirectory.java: 45 ) at org.apache.lucene.index.IndexWriter.<init>(IndexWriter.java: 727 ) at com.chinadatab.lucene.LuceneManage._IndexWriter(LuceneManage.java: 934 ) at com.chinadatab.lucene.LuceneManage._IndexWriter(LuceneManage.java: 940 ) |
从日志中可以看出是那个方法导致的OOM,这里是栈溢出,于是找到对应的语句查看,有一个定时器,每隔30分钟执行一次,每次查询取了10000条数据,处理完10000完数据后批量通过indexWrite生成索引导致的,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | @Async @Scheduled (initialDelay= 1000 * 60 * 30 , fixedDelay= 1000 * 60 * 30 ) /** * 完善基础企业库 */ public void lucene_enterprise(){ String tableName= "company" ; LuceneManage luceneManage= LuceneManage.getInstance(); Page page = luceneManage.get( 0 , 1 , true , tableName); List<Map<String, Object>> lists = page.getList(); long id= 0 ; for (Map<String, Object> map : lists) { id=Long.parseLong(map.get( "id" ).toString()); } if (id> 0 ) { TngouDBHelp TngouDBHelp = new TngouDBHelp(); List<Enterprise> list; try { //这里取了10000条数据,导致了OOM list = mapper.serach(id); if (list== null ||list.isEmpty()) { return ; } List<Fields> ls= new ArrayList<>();<br> //创建了大量对象 for (Enterprise e : list) { Fields fields = new Fields(); fields.add( new Field( "id" , e.getId()+ "" , Type.Key)); fields.add( new Field( "person" , e.getOper_name(), Type.Text)); fields.add( new Field( "name" , e.getName(), Type.Text)); fields.add( new Field( "address" , e.getAddress(), Type.Text)); ls.add(fields); }; TngouDBHelp.insert(tableName,ls); } catch (Exception e1) { e1.printStackTrace(); } } } |
分析:
栈内存为线程私有的空间,每个线程都会创建私有的栈内存。栈空间内存设置过大,创建线程数量较多时会出现栈内存溢出StackOverflowError。
同时,栈内存也决定方法调用的深度,栈内存过小则会导致方法调用的深度较小,如递归调用的次数较少。
-Xss:如-Xss128k
解决:
调整JVM参数增加栈的大小,统计减少每次处理的数据量,修改为每次5000条数据
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通