算法分为“标记”和“清除”阶段:标记存活的对象, 统一回收所有未被标记的对象(一般选择这种);也可以反过来,标 记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象 。它是最基础的收集算法,比较简单,但是会带来 两个明显的问题:1. 效率问题 (如果需要标记的对象太多,效率不高)2. 空间问题(标记清除后会产生大量不连续的碎片)
线程3
是
并发标记
适用场景1. 50%以上的堆被存活对象占用2. 对象分配和晋升的速度变化非常大3. 垃圾回收时间特别长,超过1秒4. 8GB以上的堆内存(建议值)5. 停顿时间是500ms以内
我们团队采购了字节 Trae 服务,给到每位研发每月 5000K Token 额度,Trae 资源耗尽后自动切换火山 Coding Plan 作为兜底方案。项目早期,我们仅仅把 AI 编程当成单纯辅助工具,依靠零散 Prompt、简易自制 Skill 开展开发。落地不久就暴露出不少痛点:通用 AI 虽然生成代码速度快,但很难适配客服系统复杂工程现状。我们并非单一项目,而是多系统、多技术栈集群,包含宜口袋、苍穹、浩瀚桔子、得助四大业务体系,总计 50 多个系统。后端以 Java Spring Cloud、Dubbo 为主,不同模块分包规范、Mapper 路径、基类定义各不相同;前端同时维护 Vue2、React 两套技术栈。并且同类需求在不同业务体系实现方案完全不同。直接使用通用 AI 开发的体验很低效、智能化不足:需要多轮反复对话修正,产出代码质量参差不齐;同时对话轮次多,Token 消耗巨大,经常短短几天就耗尽月度配额。基于这些痛点,我们意识到不能零散使用 AI,需要推动 AI 编程工程化,适配团队乃至公司完整研发体系。为此我们组建 AI Coding 虚拟研发小组,系统性探索落地路径。我们核心落地思路:将团队隐性研发经验显性化、工程化,沉淀为专属 AI Skill 资产。和一次性零散 Prompt 有本质区别,我们构建的 Skill 是一套结构化、可版本管理、持续迭代的项目级研发工作说明书,内置项目上下文、技术栈规范、完整研发流程、业务场景范例与各类历史踩坑规则,推动 AI 从通用代码助手转变为懂业务、懂规范、遵循流程的专属研发协作者。目前沉淀了两套核心 Skill,最重要的是客服全流程研发 Skill(kefu-dev-workflow)。我们接入 Code-Graph 代码全局链路分析能力,赋予 AI 项目级代码拓扑感知,强制流程:先梳理全量调用关系→输出完整技术方案→人工评审通过之后,再执行代码变更。整套链路覆盖飞书需求解析、代码链路溯源、影响范围评估、分支创建、分层编码、合规校验、文档归档。落地中串联三大 AI 编程配套工具:Code-Graph、Superpowers、Harness,搭建「代码链路分析 + AI 工作流与编码规范校验 + CI/CD 发布预校验」闭环,保证 AI 产出代码不仅能写出来,同时改动准确、编码规范、具备上线条件。举个典型案例:接到「工单新增筛选条件」需求,AI 不会立刻编写代码。首先依托 Code-Graph 扫描工程拓扑,精准定位需求归属 ykd-work-order 后端、ykd-work-order-web 前端,自动梳理筛选逻辑入口方法、服务间调用、SQL 查询、前端渲染全链路,识别存量业务逻辑,避免误改动线上功能;随后输出完整方案,包含数据库变更、接口改造、前端筛选组件、导出逻辑适配、边界测试用例。代码编写完成后,调用 Superpowers 执行规范门禁校验,核查包结构、Service 命名、Mapper 定义、返回体封装、前端语法是否匹配团队标准,拦截不合规代码;接着联动 Harness 流水线配置预校验,检查当前分支依赖版本、编译参数、环境变量配置,提前规避本地正常、线上打包部署失败的问题。方案与代码经过人工评审、三轮自动化校验完成后,再做最终优化,整个过程可追溯、流程受约束。权责划分清晰:AI 承担代码链路分析、标准化编码、自动化自检工作;研发人员聚焦业务风险评估、架构设计与方案评审。针对高频需求场景,我们单独沉淀专项 Skill 规则。例如消保工单新增字段场景,梳理两套差异化标准:得助体系使用动态表单扩展、宜口袋采用数据库物理字段新增,固化进 Skill 规则,解决 AI 混淆多系统实现逻辑、写出错误代码的高频问题,让高频需求开发高度标准化。整套体系持续迭代优化:我们汇总所有 AI 误判、错改、漏改案例,复盘之后转化为团队强制标准,持续更新 Skill 规则。工具各司其职形成约束:Code-Graph 规避影响范围误判;Superpowers 统一 AI 开发流程与编码规范;Harness 对齐线上发布标准,减少同需求实现方案不统一、代码风格混乱、上线故障等问题,有效降低代码评审与线上返工成本。工程层面,代码链路分析规则、编码规范、发布环境约束全部沉淀为可迭代 Skill 资产,形成闭环流程:Code-Graph 链路分析 → AI 方案设计 → Superpowers 规范校验 → Harness 发布管控逐步降低团队对资深员工个人经验的依赖,实现研发经验资产化、可复制。为降低上手门槛,应对 50 多个后端仓库、35 个前端仓库分散管理的痛点,我们开发研发环境一键初始化脚本,一站式完成 Code-Graph、Superpowers、Harness 整套工具链环境配置。新人手动搭建环境耗时久,还容易出现代码索引缺失、规范校验不生效、流水线配置缺失、代码链路无法解析等各类环境问题。我们的自动化脚本兼容 Windows、macOS,执行一次即可批量拉取 / 更新所有代码仓库、安装对应 Skill,自动完成三项关键配置:第一,初始化 Code-Graph 本地代码索引,为所有前后端工程构建代码拓扑,保障 AI 跨模块调用链、服务依赖、SQL 映射精准解析;第二,同步 Superpowers 最新静态扫描规则,保证本地编码校验标准和线上门禁完全一致;第三,拉取 Harness 流水线脚本、环境变量、分支发布规则,对齐本地开发环境和线上部署环境。原本需要半天完成的环境搭建、工具配置、索引构建工作,现在 3 分钟一键完成,解决 AI 链路分析失真、本地线上环境割裂、规范标准不统一等常见问题。总结我们团队 AI 编程落地核心认知:重点不是单纯依靠 AI 加快写代码速度,而是依靠标准化 Skill 体系,让 AI 合规、可控、高质量参与端到端研发流程,最终实现研发流程标准化、经验资产化、提效规模化。
GC线程
s1
方法返回地址
堆(-Xms设置初始大小,-Xmx设置最大空间大小,一般设置成一样大小,方便垃圾回收后不用再计算空间分配)
线程1
并发重置
定义一个数字数组,存储方法参数和定义在方法内部的局部变量,包含基本数据类型、引用类型、以及returnaddress
动态链接
应用工作线程
s0(survivor0)1/10
否
CPU2,线程2
新生代(1/3)
s1(survivor1)1/10
标记线程
回收前
堆空间外的一些结构,比如虚拟机栈、本地方法栈、方法区、字符串常量池等地方对堆空间进行引用的,都可以作为 GC Roots 进行可达性分析。
保存计算过程的中间结果,同时作为计算过程中变量的临时存储空间
对象新生成是在eden区,当经过一次YGC/MinGc就会到s0,在经过一次回收到s1,在经过一次回收到s0,达到次数后如果还没被回收就会到老年代
常量池
放到老年代
清理线程
Parallel Scavenge:多线程收集器,采用多线程进行垃圾回收,也会停止应用的工作线程。常见的应用场景:一般使用4G内存一下的应用。-XX:+UseParallelGC -XX:+UseParallelOldGC。默认开启的并行收集线程数为cpu的核数:-XX:ParallelGCThreads
“aaa”
动态绑定(晚期绑定):需要在运行期才能确定调用的方法,在运行的过程中进行指向,例如多态、继承、重写等
CMS的相关核心参数1. -XX:+UseConcMarkSweepGC:启用cms2. -XX:ConcGCThreads:并发的GC线程数3. -XX:+UseCMSCompactAtFullCollection:FullGC之后做压缩整理(减少碎片)4. -XX:CMSFullGCsBeforeCompaction:多少次FullGC之后压缩一次,默认是0,代表每次FullGC后都会压缩一 次5. -XX:CMSInitiatingOccupancyFraction: 当老年代使用达到该比例时会触发FullGC(默认是92,这是百分比)6. -XX:+UseCMSInitiatingOccupancyOnly:只使用设定的回收阈值(-XX:CMSInitiatingOccupancyFraction设 定的值),如果不指定,JVM仅在第一次使用设定值,后续则会自动调整7. -XX:+CMSScavengeBeforeRemark:在CMS GC前启动一次minor gc,目的在于减少老年代对新生代的引 用,降低CMS GC的标记阶段时的开销,一般CMS的GC耗时 80%都在标记阶段8. -XX:+CMSParallellnitialMarkEnabled:表示在初始标记的时候多线程执行,缩短STW9. -XX:+CMSParallelRemarkEnabled:在重新标记的时候多线程执行,缩短STW;
1
s0
YGC/MinerGC
本地方法栈
对象回收过程
新对象申请
初始标记:暂停所有其他线程(STW),并记录下gc roots直接能引用的对象,速度很快。
不可回收
它可以将内存分为大小相同的两块,每次使用其中的一块。当这一块的 内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。这样就使每次的内存回收都是对 内存区间的一半进行回收。
。。。。
操作数栈
并发标记:从GC Roots的直接关联对象开始遍历整个对象图的过程。这个过程耗时比较久,不会发生stw。
fullGC停止系统程序,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批Region来供下一次MixedGC使用,这 个过程是非常耗时的。
老年代采取标记整理算法,stw。
重新标记
初始标记
可回收
回收后
GC线程2
筛选回收
parNew:多线程收集器,采用多线程进行垃圾回收,也会停止应用的工作线程。常见的应用场景:配合老年代cms回收器。-XX:+userParNewGC
未被使用
MixedGC不是fullgc,老年代的堆占用率达到了参数-XX:InitiatingHeapOccupancyPercent设定的值则触发,回收所有的 Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象区,正常情况G1的垃圾收集是先做 MixedGC,主要使用复制算法,需要把各个region中存活的对象拷贝到别的region里去,拷贝过程中如果发现没有足够 的空region能够承载拷贝对象就会触发一次Full GC
放在S0/S1区
并发清理
15
16
CMS收集器(Concurrent Mark Sweep)以获取最短回收停顿时间为目标的收集器。hotspot第一款真正意义上的并发收集器,实现了让垃圾收集线程和用户工作线程基本上同时工作。采用标记清楚算法实现。只能用于老年代-XX:+UseConcMarkSweepGC
标记线程3
方法区
标记线程1
元空间(永久代,jdk8以后由元空间替代永久代)
GC线程1
局部变量表的大小是在编译器确定下来,而且不会变动
eden是否放下?
非虚方法{静态方法、私有方法、final方法、实例构造器、父类方法}
局部变量
jvm无效对象判断
cpu3,线程3
老年代是否放得下?
1.默认新生代的起始占比是5%,可以通过-XX:G1NewSizePercent设置新生代的初始占比。2.在运行过程中jvm会不停的增加新生代的占比,但最多占比不会超过60%的region,可以通过-XX:G1MaxNewSizePercent调整。3.新生代中的eden和survivor对应的region的占比依然是8:1:1。4.fullgc的时候除了新生代和老年代,也会将humongous区一并回收。
最终标记:stw,为了修正并发标记期间因为用户线程继续运行导致的标记产生变动的那一部分对象,这个阶段的stw时间一般会比初始标记时间略长,主要用到三色标记里的增量更新算法做重新标记。
l老年代
栈帧1
栈帧n
静态绑定(早期绑定):编译期就能确定指向的是哪个方法,就会在编译期在栈帧中把引用与调用的对应方法绑定
标记整理清除算法
栈帧2
可达性分析
复制算法
操作栈
最终标记
局部变量表的内部是有slot组成,一个slot占4字节,而且这个slot是可有重复利用的,当之前的变量没有引用以后,可以在接着存放其他变量
栈帧
invokestatic
调用静态方法
invokespecial
调用<init>方法、私有父类方法
invokevirtual
调用虚方法(final修饰的方法也会用该条指令)
invokeinterface
调用接口方法
invokedynamic
动态解析需要调用的方法,然后执行
并行垃圾回收器(parNew、Parallel Scavenge)
指向运行时常量池的方法引用
线程2
程序计数器
2
如果已经被C已经被标记为黑色了,因为是并发标记,此时可能会有线程在C中引用D。此时由于C已经被标记为黑色,不会再扫描D。D会被认为需要回收,此问题会导致系统出问题。
eden是否放得下?
重置线程
运行时数据区
初始标记会stw,暂停所有其他工作线程,并记录下gc roots直接能引用的对象,速度非常快
三色标记法
FGC
Serial 串行收集器,单线程主要在新生代中使用,垃圾收集过程中会暂停应用线程。serial old多用于老年代,使用标记整理算法
标记线程2
并发清理:开启用户的工作线程,同事GC线程开始对未标记的区域进行清理。这个阶段如果有新增对象会被标记为黑色,不做任何处理。
1.新生代收集(minor gc/yong gc):当新生代空间不足触发minorgc,主要是指的eden区满了触发,survivor满了不会触发gc,每次minorgc都会清理整个新生代,会引发stw2.老年代垃圾收集(major gc/old gc)(只有cms单独收集老年代):老年代空间不足,会先尝试minorgc,如果之后还空间不足则触发majorgc,majorgc的速度要比minorgc慢10倍以上,如果majorgc后空间还是不足就会触发oom3.混合收集(mixed GC)收集整个新生代以及部分老年代。只有G1才会有这种行为4.整堆收集(full gc)收集整个堆和方法区的垃圾回收
线程私有,记录每个线程当前被执行的字节码指令的地址
垃圾回收算法
分配对象内存
筛选回收:stw,筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期 望的GC停顿时间(可以用JVM参数 -XX:MaxGCPauseMillis指定)来制定回收计划。
survivor是否放得下?
G1收集器(Garbage-First):1.一款面向服务器的垃圾收集器,主要针对配备多颗处理器以及大容量内存的机器,以极高的概率满足GC停顿时间要求的同时还具备高吞吐量性能特征。2.整个内存不是连续的空间分配给新生代、S0\\S1、老年代,而是吧整个堆内存分为一块一块的region,一般Region的大小等于堆大小除以2048,可以通过-XX:G1HeapRegionSize来设置每个Region的大小,推荐默认计算方式。3.保留了老年代和新生代的概念,但是不再是物理隔离的连续内存区域,而是Region的集合,空间不一定是连续的。4.humongous是用来存放大对象的,大对象默认定义为大于一个region的50%
eden8/10
黑色:代表该对象以及该对象下的属性全部被标记过了。(程序需要用到的对象,不应该被回收)灰色:对象被标记了,但是该对象下的属性未被完全标记。(需要在该对象中寻找垃圾)白色:对象未被标记(需要被清除的垃圾)
eden
标记整理算法:先标记需要清除的对象,然后回收,然后把剩下的存活对象放到一起
str
垃圾收集器Parallel Old 是Parallel Scavenge收集器的老年代版本。这个收集器在1.6中才开始提供。在JDK1.5以及之前的版本中,Parallel Scavenge+Serial Old(单线程),无法充分利用多CPU的处理能力。1.6之后,终于有了名副其实的“吞吐量优先“收集器组合:Parallel Scavenge + Parallel Old。目前jdk1.8默认的收集器为:parallel Scavenge +Serial old。
GC线程3
OOM
标记复制算法
标记清楚算法
老年代(2/3)
引用计数法
新生代采取复制算法暂停所有用户线程
将GC roots作为七点,从这些节点开始向下搜索引用对象,查到的对象都标记为废垃圾对象,其余未标记的对象都认为是垃圾对象。
cpu0,线程1
重新标记:stw,为了修正并发标记期间因为用户线程继续运行导致的标记产生变动的那一部分对象,这个阶段的stw时间一般会比初始标记时间略长,主要用到三色标记里的增量更新算法做重新标记。
YoungGCyounggc并不是现有的属于eden的所有region都放满了就会马上触发,G1会继续按下现有eden区的回收时间,如果时间远小于参数-XX:MaxGCPsudrMills设定的值,那么g1就会继续增加新生代的region,直到下一次eden区放满,G1计算的回收时间接近上述参数设定的值,就会触发younggc
对象被回收次数超过阈值?
对象被引用一次计数+1,当计数为0的时候则判定为无效对象进行回收,但是该方法在对象互相引用的时候就会存在问题。
YGC
虚拟机栈
并发重置:重置本次标记过程中的标记数据。
-XX:+UseG1GC:使用G1收集器-XX:ParallelGCThreads:指定GC工作的线程数量-XX:G1HeapRegionSize:指定分区大小(1MB~32MB,且必须是2的N次幂),默认将整堆划分为2048个分区-XX:MaxGCPauseMillis:目标暂停时间(默认200ms)-XX:G1NewSizePercent:新生代内存初始空间(默认整堆5%)-XX:G1MaxNewSizePercent:新生代内存最大空间-XX:TargetSurvivorRatio:Survivor区的填充容量(默认50%),Survivor区域里的一批对象(年龄1+年龄2+年龄n的多个 年龄对象)总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代-XX:MaxTenuringThreshold:最大年龄阈值(默认15)-XX:InitiatingHeapOccupancyPercent:老年代占用空间达到整堆内存阈值(默认45%),则执行新生代和老年代的混合 收集(MixedGC),比如我们之前说的堆默认有2048个region,如果有接近1000个region都是老年代的region,则可能 就要触发MixedGC了-XX:G1MixedGCLiveThresholdPercent(默认85%) region中的存活对象低于这个值时才会回收该region,如果超过这 个值,存活对象过多,回收的的意义不大。-XX:G1MixedGCCountTarget:在一次回收过程中指定做几次筛选回收(默认8次),在最后一个筛选回收阶段可以回收一 会,然后暂停回收,恢复系统运行,一会再开始回收,这样可以让系统不至于单次停顿时间过长。-XX:G1HeapWastePercent(默认5%): gc过程中空出来的region是否充足阈值,在混合回收的时候,对Region回收都 是基于复制算法进行的,都是把要回收的Region里的存活对象放入其他Region,然后这个Region中的垃圾对象全部清 理掉,这样的话在回收过程就会不断空出来新的Region,一旦空闲出来的Region数量达到了堆内存的5%,此时就会立 即停止混合回收,意味着本次混合回收就结束了。
根据老年代的特点特出的一种标记算法,标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象回 收,而是让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。
栈顶缓存技术基于栈式架构的虚拟机所使用的零地址指令更加紧凑,但完成一项操作的时候必然需要使用更多的入栈和出栈指令,这同时也就意味着将需要更多的指令分派(instruction dispatch)次数和和内存读 / 写次数由于操作数是存储在内存中的,因此频繁的执行内存读 / 写操作必然会影响执行速度。为了解决这个问题,HotSpot JVM 的设计者们提出了栈顶缓存(ToS, Top-of-Stack Cashing)技术,将栈顶元素全部缓存在物理 CPU 的寄存器中,以此降低对内存的读 / 写次数,提升执行引擎的执行效率
局部变量表的第0个位置存储的一般都是this