1. 库存查询 60 万条数据为什么直接 OOM 了线上一个库存对账服务凌晨跑批的时候突然告警日志里赫然一行java.lang.OutOfMemoryError: Java heap space。这个服务做的事情很简单从 MySQL 里把某个仓库的库存流水全量拉出来做一次汇总比对。平时数据量小几万条跑得好好的那天赶上大促后对账单表命中 60 万条记录直接崩了。java.lang.OutOfMemoryError这个报错本质上是 JVM 堆内存不够用了。但堆不够用只是表象真正要问的是为什么一次查询会把堆撑爆答案通常藏在三个地方——JDBC 驱动默认把结果集全量加载到客户端内存、代码里用了select *把宽表所有字段都拉回来、以及分页写法本身有问题。先说 JDBC 这一层。MySQL 的 JDBC 驱动默认行为是执行查询后把服务端返回的所有行一次性读进客户端内存封装成ResultSet。60 万行、每行假设 500 字节那就是 300MB 左右的原始数据再加上 Java 对象头、字符串对象、List 容器扩容的冗余实际占用轻松翻两三倍。如果堆只给了 512MB 或 1GBOOM 几乎是必然的。再说select *。库存表往往字段很多有备注、扩展属性 JSON、时间戳等。你只需要sku_id和quantity两个字段做汇总却把整行都拉回来内存浪费是数量级的。我见过一个案例把select *改成只查需要的三列内存峰值直接降了 70%。最后是分页。很多人第一反应是limit offset, size分批查。这个写法在 offset 很大时性能会急剧下降因为 MySQL 要扫描并丢弃前 offset 行。更关键的是如果代码里把每批结果都塞进同一个 List 累积那分页根本没起到省内存的作用只是把一次性加载拆成了多次加载最终 List 还是那么大。所以排查java.lang.OutOfMemoryError在大数据量查询场景下思路应该是先确认是不是结果集全量加载再确认查询字段是否必要最后确认分页方式是否真的流式处理。这篇就按这个顺序把可复制的 JVM 参数、JDBC 配置、分页写法都过一遍同时演示怎么用 TaoToken 把 AI 辅助工具接进排查流程让定位问题这件事本身也提效。2. TaoToken 统一 Key 接入 AI 排查助手的前置准备排查内存溢出这种问题光靠看日志和猜是不够的。我习惯把堆转储文件、GC 日志、慢查询记录丢给 AI 助手做初步分析让它帮我圈出可疑的对象引用链和 SQL。但这里有个现实问题不同 AI 工具的 API 格式、鉴权方式、模型 ID 都不一样每换一个工具就要改一遍配置很烦。TaoToken 解决的就是这个统一入口的问题。它提供一个兼容 OpenAI 格式的 API 通道你只需要一个 Key、一个 Base URL就能在多种 AI 辅助工具里切换使用不用为每个工具单独申请和配置。对于排查java.lang.OutOfMemoryError这种需要反复对话、贴日志、问思路的场景统一通道能省下不少折腾时间。前置准备其实就三件事。第一拿到 API Key。访问https://taotoken.net/api-keys登录后创建一个 Key复制保存好后面配置里要用。第二确认 Base URL。TaoToken 的 API 地址是https://taotoken.net/api注意这个地址不带任何查询参数直接填在工具的 Base URL 字段里。第三选一个模型 ID。TaoToken 支持多种模型你在控制台或文档里能看到可用的模型列表挑一个适合代码分析和长文本推理的即可。这里要强调一个容易踩的坑Base URL 和 API Key 是配套的Key 必须是在 TaoToken 创建的不能拿别家的 Key 填进来。另外如果你用的是 Claude Code 这类工具它的配置文件和普通 OpenAI 兼容工具不太一样需要单独设置环境变量或配置文件。下面一节我会给出具体的可复制配置片段包括 JSON、TOML 和 settings 三种形式你按自己用的工具对号入座。还有一点TaoToken 的定位是统一 API 通道不是让你拿它去替代数据库或 JVM 本身。排查内存溢出核心还是靠jmap、jstat、MAT这些工具AI 助手只是帮你更快地理解堆转储和日志。把工具接好是为了让看日志—问思路—验证假设这个循环转得更快。3. 可复制的 JVM 参数、JDBC 配置与 AI 工具接入片段这一节是全文最干的部分直接给可复制的内容。先解决内存溢出本身再解决 AI 工具接入。3.1 JVM 参数先把堆和 GC 日志配好排查 OOM第一步是让 JVM 在崩溃时留下足够的信息。下面这组参数可以直接加到启动脚本里java -Xms2g -Xmx2g \ -XX:HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath/data/dumps/ \ -XX:PrintGCDetails \ -XX:PrintGCDateStamps \ -Xloggc:/data/logs/gc.log \ -jar inventory-service.jar-Xms和-Xmx设成一样避免堆动态扩容带来的抖动。HeapDumpOnOutOfMemoryError是关键它保证 OOM 发生时自动 dump 堆快照你后面用 MAT 分析就靠这个文件。GC 日志用来判断是不是频繁 Full GC 导致的内存耗尽。如果你用的是容器环境注意-Xmx不要超过容器内存限制否则会被 OOM Killer 干掉连 dump 都留不下。一般建议堆占容器内存的 60% 到 70%。3.2 JDBC 配置开启游标分批拉取这是解决大数据量查询 OOM 最直接的一招。在 JDBC URL 后面加上useCursorFetchtrue和defaultFetchSizejdbc:mysql://192.168.1.252:3306/lims?useUnicodetruecharacterEncodingutf8useCursorFetchtruedefaultFetchSize500useCursorFetchtrue会让 MySQL 在服务端维护一个游标客户端每次只拉defaultFetchSize条记录而不是一次性全量返回。defaultFetchSize500表示每批 500 条你可以根据单行大小调整行宽就调小行窄可以调大。注意这个配置要配合Statement的 fetch size 才生效。如果你用的是 MyBatis可以在SqlSessionFactory里设置defaultFetchSize如果用原生 JDBC要显式调用statement.setFetchSize(500)。3.3 分页查询用游标分页替代 offset 分页limit offset, size在深分页时性能差而且如果代码累积结果内存照样爆。推荐用基于主键的游标分页SELECT id, sku_id, quantity FROM inventory_flow WHERE id #{lastId} ORDER BY id LIMIT 500每次查完记住最后一条的id下次从它之后继续查。这样每批只加载 500 条处理完就释放内存占用是恒定的。配合useCursorFetch效果更好。3.4 AI 工具接入三种配置格式下面给出 TaoToken 在三种常见工具里的配置片段。Base URL 统一是https://taotoken.net/apiKey 换成你自己创建的。JSON 格式适用于 Cline、Continue 等{ provider: openai, baseUrl: https://taotoken.net/api, apiKey: sk-your-taotoken-key, model: your-model-id }TOML 格式适用于 Codex 类工具[model] provider openai base_url https://taotoken.net/api api_key sk-your-taotoken-key model_id your-model-idsettings 格式适用于 Claude Code 类工具{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-your-taotoken-key, ANTHROPIC_MODEL: your-model-id } }三件套就是 Base URL、Key、Model ID缺一不可。填完之后工具就能通过 TaoToken 统一通道调用模型了。如果你用的是 Claude Code配置细节可以参考https://taotoken.net/doc里的说明。4. 验证请求从复现 OOM 到确认修复成功配置改完不能只看代码要实际跑一遍验证。这一节给出具体的复现和验证动作。4.1 先复现问题在修复之前先确认问题能稳定复现。写一个简单的测试方法故意用select *全量查询public ListInventoryFlow queryAll() { String sql SELECT * FROM inventory_flow WHERE warehouse_id ?; return jdbcTemplate.query(sql, new Object[]{1L}, new BeanPropertyRowMapper(InventoryFlow.class)); }把堆设小一点比如-Xmx256m然后调用这个方法。如果数据量够大你应该能看到java.lang.OutOfMemoryError: Java heap space同时/data/dumps/下生成java_pidxxxx.hprof文件。这一步的目的是确认你的排查方向没错。4.2 用 AI 助手分析堆转储拿到 hprof 文件后用 MAT 打开看 Dominator Tree通常能看到ArrayList或HashMap占了大头里面装的就是全量查询结果。如果你想更快地理解引用链可以把 MAT 的摘要信息贴给接入了 TaoToken 的 AI 助手问它这个对象占用 300MB可能的来源是什么。它会结合你贴的代码和 SQL 给出方向。4.3 改完再验证把 JDBC URL 加上useCursorFetchtruedefaultFetchSize500把select *改成只查需要的列把 offset 分页改成游标分页。然后重新跑同样的测试方法观察内存曲线。你可以用jstat -gc pid 1000实时看 GC 情况。修复前你会看到老年代快速上涨频繁 Full GC修复后老年代应该保持平稳Young GC 正常回收。如果堆内存峰值从 300MB 降到 50MB 以内说明修复生效了。4.4 验证 AI 通道是否通配置完 TaoToken 后发一个最简单的请求确认通道可用。以 curl 为例curl https://taotoken.net/api/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer sk-your-taotoken-key \ -d { model: your-model-id, messages: [{role: user, content: ping}] }如果返回正常的 JSON 响应说明 Key、Base URL、Model ID 三件套都对了。如果报 401检查 Key 是否复制完整如果报 model not found检查 Model ID 是否拼写正确。5. 本篇常见报错排查401、local proxy failed 与 reading choices配置和排查过程中有几个报错特别常见这里逐个对照解决。401 Unauthorized。这个最直接就是 Key 不对。可能的原因Key 复制时带了空格、Key 已经过期或被删除、用了别家平台的 Key。解决方法是重新到https://taotoken.net/api-keys创建一个新 Key完整复制后替换配置里的值。注意不要手动改动 Key 的任何字符。local proxy failed。这个报错通常出现在工具尝试通过本地代理转发请求时。如果你在工具里配置了代理地址但代理没启动或端口不对就会报这个。解决方法是检查工具的代理设置确认 Base URL 直接填https://taotoken.net/api不要额外套一层本地代理。如果你确实需要代理确保代理服务正常运行且端口匹配。reading choices 相关报错。比如error reading choices或choices field is empty这通常是响应格式不符合预期。可能的原因Model ID 填错了导致服务端返回了错误结构或者请求体里messages格式不对。解决方法是先用 curl 发一个最小请求验证确认返回的 JSON 里有choices数组。如果 curl 正常但工具报错检查工具是否对响应做了额外解析比如期望choices[0].message.content但实际字段名不同。OAuth 相关报错。如果你用的是 Claude Code 类工具可能会遇到 OAuth 认证失败。这类工具默认走 Anthropic 的 OAuth 流程但接 TaoToken 时应该用 API Key 模式。检查你的 settings 配置确保用的是ANTHROPIC_API_KEY而不是 OAuth token。如果工具强制走 OAuth看文档里有没有切换到 API Key 模式的选项。连接超时。如果请求一直卡住然后超时先确认网络能访问https://taotoken.net/api。可以用curl -v看握手过程。如果 DNS 解析失败检查本机 DNS 配置如果 TLS 握手失败检查系统时间是否正确。模型返回内容被截断。排查内存溢出时你可能会贴很长的堆转储摘要给 AI如果返回被截断检查请求里的max_tokens参数是否设得太小。适当调大但也要注意模型本身的上限。把上面这些报错对照一遍基本能覆盖 90% 的配置问题。剩下的就是具体业务代码里的坑比如连接池配置、事务边界、批量提交大小那些需要结合你的实际代码看。6. 把 AI 排查助手固定进你的工作流排查java.lang.OutOfMemoryError这件事最耗时间的往往不是修复本身而是定位。堆转储几百 MBGC 日志几千行慢查询记录一堆光靠人眼扫很累。把 AI 助手接进工作流让它帮你做第一轮筛选你只负责验证关键假设效率会高很多。如果你只是偶尔排查一次用模型对话就够了把日志贴进去问思路访问https://taotoken.net/api对应的对话入口即可。如果你需要长期做代码分析、Agent 自动化排查那 Coding Plan 更合适能省去反复配置的麻烦。接入文档在https://taotoken.net/doc里面有各工具的详细配置说明。最后留一个我自己的习惯每次 OOM 修复后把堆转储文件、修复前后的 GC 日志、以及最终的配置改动整理成一个案例存档。下次遇到类似问题直接拿存档问 AI 助手这个场景和上次有什么异同比从零开始快得多。内存溢出不可怕可怕的是同一个坑踩两次。