十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

TaoToken 通道下 MyBatis Cursor OOM?Claude Code 这样调 JVM 参数

TaoToken 通道下 MyBatis Cursor OOM?Claude Code 这样调 JVM 参数 这篇处理一个具体排障MyBatis 在-Xms500m -Xmx500m下List 查询百万行直接抛出java.lang.OutOfMemoryError: GC overhead limit exceeded而 Cursor 查询还能跑完。Claude Code 负责分析 OOM 现场模型通道用 TaoToken官网 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content Base URL 填https://taotoken.net/api。TaoToken 只作为 Claude Code 的模型通道出现不参与 JVM 设置堆内存参数仍然由你在本地运行配置、启动脚本或容器参数里自己调。下面按“复现问题、接通道、贴配置、验证回答、排查错点、闭环入口”的顺序写重点放在怎么把 JVM 参数和 MyBatis 查询链路一起交给 Claude Code而不是只丢一句报错。一、原问题与场景List 触发 GC overheadCursor 还能跑完场景来自一个很典型的 MyBatis 大数据量对比测试。测试表test_table先插入约 10 万行再通过多次union all拼成约 100 万行的结果集。Mapper 里准备两个方法一个返回CursorPerson一个返回ListPerson。在不限制堆内存时两者都能跑完一旦把 JVM 参数设置为-Xms500m -Xmx500mList 路径就出现java.lang.OutOfMemoryError: GC overhead limit exceededCursor 路径仍能完成遍历。这个现象本身并不神秘但排障时容易只盯着“内存太小”一个点。实际要解释的是两条链路在 JVM 堆里的对象生命周期List 查询会在结果映射阶段把每一行都实例化成Person然后加进一个ArrayList。100 万行意味着 100 万个对象以及ArrayList扩容时产生的数组复制和旧数组等待回收。500MB 堆下老年代很快被占满Full GC 频繁触发但能回收的空间又不够于是 JVM 抛出 GC overhead limit exceeded。Cursor 查询返回的是迭代器语义MyBatis 不会一次性把全部结果映射并收集到 List。它依赖 JDBC ResultSet 的游标行为配合ResultSetType.FORWARD_ONLY和驱动层的分批读取逐行或小批地把数据交给消费端。堆里主要保留当前行、必要缓冲和会话对象所以同样的 100 万行不会以 100 万个常驻对象的形式同时存在。这里要特别区分TaoToken 不参与 JVM 调参。它只是 Claude Code 的模型通道。-Xms500m -Xmx500m是在本地运行配置里设置的Claude Code 能帮你分析“为什么 List 会 OOM、Cursor 为什么能跑完”但不能替你修改 JVM 参数也不应该把堆内存问题误判成模型通道问题。真正需要交给 Claude Code 的是一组完整现场JVM 启动参数例如-Xms500m -Xmx500m以及是否加了-XX:HeapDumpOnOutOfMemoryError。Mapper 接口包括 Cursor 方法和 List 方法的注解、Options、SQL 结构。调用点也就是DemoTest里怎么遍历 Cursor、怎么接收 List。mybatis-config.xml中与事务、环境、数据源相关的片段。完整异常栈至少保留java.lang.OutOfMemoryError: GC overhead limit exceeded和触发它的测试方法。把这些贴给 Claude Code它才能从“结果集映射方式”和“堆对象数量”两个角度解释差异而不是泛泛回答“内存不够就加 Xmx”。二、TaoToken 前置把 Claude Code 的模型通道接上先打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 创建 Key。创建完成后把 Claude Code 的 Base URL 填为https://taotoken.net/api注意不要写成首页地址也不要漏掉/api。Claude Code 读取的是ANTHROPIC_BASE_URL这类环境变量最终请求会发到https://taotoken.net/api下的兼容入口。Key 用YOUR_API_KEY占位实际使用时替换成你在控制台生成的值。这一步只解决“Claude Code 走哪条模型通道”的问题。JVM 参数、MyBatis 配置、数据库连接串仍然在你自己项目里。不要因为出现了 OOM就先去怀疑 TaoToken 通道通道问题通常表现为 401、404、连接超时或模型名不存在而GC overhead limit exceeded是本地 JVM 堆和 GC 行为问题两者的排查方向不同。如果你还没创建 Key可以走这个入口API Keyshttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi-keys接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc拿到 Key 后再进入下一节的settings.json配置。不要把 Key 写进前端代码也不要提交到 Git 仓库。Claude Code 的配置建议放在用户级或项目级配置文件中通过环境变量读取。三、可复制配置settings.json、JVM 参数与提问模板3.1 Claude Code 的 settings.jsonClaude Code 常用~/.claude/settings.json或项目级.claude/settings.json。核心是env里的ANTHROPIC_*变量{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: YOUR_API_KEY, ANTHROPIC_MODEL: MODEL_ID } }其中MODEL_ID填你在 TaoToken 控制台选择的模型 ID。ANTHROPIC_AUTH_TOKEN用你的 API Key。若你的 Claude Code 版本同时读取ANTHROPIC_API_KEY不要两个都填成不同值避免认证头冲突。改完后重启 Claude Code 或重新打开终端让环境变量生效。3.2 JVM 参数放在本地运行配置在 IDEA 的 Run/Debug Configurations 里把 VM options 设为-Xms500m -Xmx500m -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath./oom.hprof如果你用命令行运行可以写成java -Xms500m -Xmx500m \ -XX:HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath./oom.hprof \ -jar target/mybatis-cursor-demo.jar确认参数真的生效可以在运行后执行jcmd pid VM.flags输出里应能看到-Xms500m和-Xmx500m。如果没看到说明你改的不是实际启动配置后面的 OOM 对比就没有意义。3.3 给 Claude Code 的提问模板不要只发“MyBatis Cursor OOM 怎么办”。建议把下面这段作为提问模板再把你的代码片段补进去我在做一个 MyBatis 排障JVM 参数是 -Xms500m -Xmx500m。 数据表 test_table 约 10 万行Mapper 里通过多次 union all 组成约 100 万行结果。 selectAllAsList 返回 ListPerson运行中抛出 java.lang.OutOfMemoryError: GC overhead limit exceeded。 selectAllByCursor 返回 CursorPerson在同一堆参数下可以遍历完成。 下面是 UserMapper、DemoTest 调用点、mybatis-config.xml 关键片段和完整异常栈 【贴代码】 请只做分析不要替我改 JVM 参数。需要说明 1. List 路径在 ResultSet 到对象映射阶段的内存行为 2. Cursor 路径为什么堆占用低FORWARD_ONLY 和 fetchSize 起了什么作用 3. GC overhead limit exceeded 的触发条件 4. SqlSession 与事务边界应该怎么检查 5. 如果消费端仍把 Cursor 元素收集进 List会发生什么 6. 给出代码层面的修改建议但保留我的 JVM 参数实验条件。这段模板的关键是最后一句保留 JVM 参数实验条件。否则模型很容易直接建议“把 Xmx 调到 2g”虽然能绕过现象但不利于理解 Cursor 和 List 的差异。3.4 需要贴给 Claude Code 的代码片段Mapper 侧可以简化成类似结构注意不要贴数据库密码public interface UserMapper { Select(select * from test_table union all ... ) Options(resultSetType ResultSetType.FORWARD_ONLY, fetchSize Integer.MIN_VALUE) CursorPerson selectAllByCursor(); Select(select * from test_table union all ... ) ListPerson selectAllAsList(); }调用侧要体现“Cursor 是边取边消费List 是一次性接收”try (SqlSession sqlSession sqlSessionFactory.openSession()) { UserMapper mapper sqlSession.getMapper(UserMapper.class); CursorPerson cursor mapper.selectAllByCursor(); int count 0; for (Person person : cursor) { count; } System.out.println(cursor count count); }List 调用点try (SqlSession sqlSession sqlSessionFactory.openSession()) { UserMapper mapper sqlSession.getMapper(UserMapper.class); ListPerson people mapper.selectAllAsList(); System.out.println(list size people.size()); }mybatis-config.xml里至少保留事务管理器、数据源和 mapper 注册片段。如果用了 Spring 事务也要说明Transactional的范围。Cursor 的消费必须在同一个 SqlSession 生命周期内完成提前关闭 session 或跨线程消费都可能出问题。四、验证请求与成功结果Claude Code 应回读哪些堆与查询链路配置好 Claude Code 后先发一个很小的验证请求确认通道可用。例如问它请用一句话说明 MyBatis Cursor 和 List 在结果集加载上的区别。如果这一步返回正常说明settings.json中的ANTHROPIC_BASE_URL、ANTHROPIC_AUTH_TOKEN和ANTHROPIC_MODEL已经配通。然后发送上一节的完整 OOM 提问模板。一个合格的回答应该包含下面这些点List 路径的内存曲线ResultSet 的每一行被映射成Person然后被加入ArrayList。100 万行对应约 100 万个对象加上ArrayList扩容产生的数组复制老年代压力急剧上升。GC overhead limit exceeded通常意味着 GC 花费了大量时间但回收到的堆空间很少JVM 认为继续运行下去无法有效推进。Cursor 路径的懒加载MyBatis 的Cursor实现迭代器接口消费端调用next时才推进结果集。配合FORWARD_ONLYJDBC 驱动不会把全部行缓存到内存而是按需从数据库读取。fetchSize会影响每次从驱动侧取回的行数Integer.MIN_VALUE在 MySQL 驱动中常用于开启流式读取但具体行为仍受驱动版本和连接参数影响。对象生命周期差异List 路径中查询方法返回前所有Person都处于强引用可达状态Cursor 路径中迭代过程中只有当前行和少量缓冲是强引用可达已经遍历过的行可以被 GC 回收。事务与 SqlSessionCursor 需要在事务和 SqlSession 有效期内消费。如果openSession后立刻关闭或者把 Cursor 返回到事务边界之外可能报Cursor is closed或类似异常。大事务下使用 Cursor 还要注意数据库侧的 undo log、连接占用和锁范围。消费端是否真流式如果遍历 Cursor 时又把每个Person加进一个新的ArrayList那只是把 OOM 从查询阶段推迟到消费阶段堆里仍然会积累 100 万个对象。参数实验结论在-Xms500m -Xmx500m下List 路径 OOM、Cursor 路径完成说明差异主要来自结果集在 JVM 堆中的驻留方式而不是 SQL 本身“更省内存”。成功结果不要求 Claude Code 给出唯一答案而是要求它能把这些链路对应到你的代码和 JVM 参数上。如果它只回答“增加 Xmx”或“使用 Cursor”没有解释FORWARD_ONLY、fetchSize、SqlSession和消费端收集行为就说明提问信息不足需要把 Mapper、调用点、异常栈补得更完整。你还可以追问一次让它做回读校验请复述我的 JVM 参数、List 方法名、Cursor 方法名、事务边界并指出你判断 OOM 发生在大致哪个阶段。这一步能检查模型是否真的读了你贴的现场。若它把方法名、参数或阶段说错应该回到提问模板把代码片段贴得更结构化。五、本篇常见错排查settings.json、UserMapper 与 SqlSession1. settings.json 里 Base URL 写错常见错误是写成https://taotoken.net或https://taotoken.net/api/。前者会走到首页路径后者可能因为多余斜杠导致路径拼接异常。Claude Code 侧统一填https://taotoken.net/api如果出现 404先检查这一项再检查模型 ID 是否存在。2. Key 环境变量冲突Claude Code 使用ANTHROPIC_AUTH_TOKEN承载 Key。若你同时设置了ANTHROPIC_API_KEY且两者值不同可能出现认证失败。排查时先只保留一套变量或者确认两处值一致。改完settings.json后要重启进程。3. 把 TaoToken 当成 JVM 调参工具TaoToken 只提供模型通道。你在 Claude Code 里问“帮我把 Xmx 改成 2g”它最多给出建议不会修改你的本地 JVM。JVM 参数要在 IDEA 运行配置、java -jar命令、容器启动参数或 CI 脚本里改。排障时要把“模型通道问题”和“JVM 问题”分开看。4. UserMapper 的 Cursor 方法没有正确的 ResultSetTypeCursor 通常需要前向只读结果集。若 Mapper 方法缺少Options(resultSetType ResultSetType.FORWARD_ONLY)或者数据源/驱动配置不支持流式读取可能退化成一次性加载。MyBatis 的 Cursor 返回类型本身只是接口层表现底层仍取决于 JDBC 驱动行为。5. SqlSession 提前关闭或跨事务消费Cursor 绑定在 SqlSession 和 ResultSet 上。正确写法是在同一个try-with-resources中打开 session、获取 Cursor、完成遍历。把 Cursor 返回到 session 关闭之后或者丢到另一个线程里遍历都可能失败。6. 消费端把 Cursor 又收集成 List这是最隐蔽的假流式ListPerson all new ArrayList(); for (Person person : cursor) { all.add(person); }这样堆里仍然会积累全部对象。要真正降低内存消费端应边遍历边处理例如直接写文件、写 Excel 的流式 API、批量入库而不是先聚合再处理。7. 长事务与大量修改混用 CursorCursor 适合小事务下的大结果集读取。如果在一个大事务里既用 Cursor 遍历又做大量更新数据库侧 undo log、锁和连接占用都会上来。原文场景也强调 Cursor 尽量配合小事务使用。排障时要把事务边界和批处理大小一起说明。8. 只贴异常栈不贴 Mapper 和调用点只发java.lang.OutOfMemoryError: GC overhead limit exceededClaude Code 无法判断是 List 聚合导致、Cursor 假流式导致还是别的地方泄漏。至少贴出UserMapper、DemoTest调用点、mybatis-config.xml、JVM 参数和异常栈。9. 忘记确认堆参数实际生效在 IDEA 里改了 VM options但运行的是另一个启动类或另一个配置参数就没生效。用jcmd pid VM.flags或 JVM 启动日志确认。否则 List 可能仍然跑在默认堆上对比结论不可靠。10. 把 Cursor 当银弹Cursor 降低的是查询结果在 JVM 堆中的驻留量不是让所有环节都变成常量内存。如果消费端聚合、如果驱动不支持流式、如果一次 fetch 过大、如果事务范围过长仍然可能出问题。正确做法是把 Cursor、fetchSize、事务边界和消费端处理方式一起看。六、排障闭环与下一步如果你已经能把-Xms500m -Xmx500m下的 OOM 现场、UserMapper的 Cursor/List 方法、DemoTest调用点和mybatis-config.xml片段一起发给 Claude Code并且它正确解释了 List 的对象驻留、Cursor 的懒加载、FORWARD_ONLY与fetchSize的作用那么这次排障的主链路就通了。剩下的就是把结论落到代码该用 Cursor 的地方改成边遍历边处理该缩小事务的地方缩事务该关 SqlSession 的地方用 try-with-resources 关闭。如果你还没创建 Key先到 API Keys 页面生成并替换YOUR_API_KEYhttps://taotoken.net/console/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentapi-keys如果在settings.json里配不通或者不确定ANTHROPIC_BASE_URL、ANTHROPIC_AUTH_TOKEN、ANTHROPIC_MODEL应该怎么填查接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentdoc把 Claude Code 通道配好之后再回到 JVM 参数和 MyBatis 查询链路本身。TaoToken 负责让你的排障问答稳定走模型通道JVM 参数和 Cursor/List 的取舍仍然由你的本地实验和代码决定。
返回列表