开发了一个 patches 风格的数字矩形游戏,欢迎来挑战
玩法非常简单,拖动矩形涵盖游戏棋盘上的数字(只能是矩形),整个棋盘被覆盖就完成了成功通关。欢迎挑战: https://playpatches.today/
xiaohack博客专注前沿科技动态与实用技术干货分享,涵盖 AI 代理、大模型应用、编程工具、文档解析、SEO 实战、自动化部署等内容,提供开源项目教程、科技资讯日报、工具使用指南,助力开发者、AI 爱好者获取前沿技术与实战经验。
玩法非常简单,拖动矩形涵盖游戏棋盘上的数字(只能是矩形),整个棋盘被覆盖就完成了成功通关。欢迎挑战: https://playpatches.today/

就是不听家人说的,外人说啥都认为对,沟通起来很费劲。年纪大了又不敢过度反驳,思想不开放,什么事情都倾向于迷信寻找慰藉 🥲。
如果您的软件面向Windows生态分发、涉及敏感数据或驱动开发,EV代码签名证书无疑是最优之选。选择权威CA机构(如JoySSL),完成严谨的身份核验,让您的软件从发布第一刻起便赢得系统与用户的信任。 EV(Extended Validation,扩展验证)代码签名证书,是遵循最严格国际验证标准的数字证书。它不仅仅验证开发者对代码的所有权,更通过严格的线下企业身份核实流程,确认申请者是一个合法、真实存在的实体机构。 与普通代码签名证书(如OV组织验证证书)相比,EV证书的审核堪称“数字身份的政审”。CA机构会要求企业提交营业执照、法人身份证明、对公账户证明、办公场地证明等全套材料,并通过工商数据库、银行渠道交叉验证,甚至直接拨打企业公开电话与法定代表人确认申请意愿。审核周期通常为3至7个工作日。这种穿透式审核从源头杜绝了虚假身份申请证书的可能,而普通证书曾出现过“空壳公司申请证书签署恶意软件”的案例,印证了身份核验差异带来的安全鸿沟。 EV证书最核心的安全机制在于私钥的强制硬件存储。其私钥必须存储在符合FIPS 140-2标准的硬件安全模块(HSM)或USB加密狗中,与开发者设备物理绑定,每次签名需插入加密狗并输入独立密码,形成“硬件+密码”的双重防护。普通证书的私钥若存储在本地硬盘,可能因电脑中毒导致泄露;而EV证书的私钥无法被导出或复制,从根源上杜绝了签名滥用风险。 对Windows平台开发者而言,“Windows SmartScreen已阻止启动此未验证的应用”是影响软件分发与用户信任的最大障碍。普通代码签名证书需从零开始积累信誉——只有当软件被足够多用户下载且未触发安全警报时,评分才会逐步提升,这一过程可能持续数周甚至数月。而EV证书凭借CA机构的穿透式审核,在签发时就为软件注入“初始信誉分”,新软件发布即可绕过拦截。测试数据显示,使用EV证书后首次发布的软件拦截率可降至8%,安装完成率提升至76%。 此外,EV证书具备“信誉继承”能力。当企业发布新版本软件时,SmartScreen会自动识别签名信息并关联历史信誉,某ERP厂商使用EV证书后,新版本发布时的初始信誉分达到历史版本的80%,较更换证书前提升65%。 EV证书在以下场景中具有不可替代性: EV代码签名证书通过穿透式身份核验、硬件级私钥保护、系统级即时信任三大核心优势,为开发者提供了从代码到用户的全链路安全屏障。虽然其审核周期较长(3-7个工作日)、成本高于OV证书,但在高安全需求场景中,它所提供的信任价值和合规保障是普通证书无法替代的。对于追求专业度与用户信任的开发者而言,EV证书不仅是一项技术投资,更是软件品牌资产的长期积累。
一、什么是EV代码签名证书?
二、为何EV证书是开发者的首选?
1. 硬件级私钥保护:杜绝私钥泄露
2. 系统级即时信任:突破SmartScreen拦截
3. 强制场景适配:驱动开发与合规通行证
三、总结
早上更新了一下 Chrome ,右上角的 Gemini 消失了,设置里面 AI 相关的内容也没了,Gemini 网页还可以打开
4 月 21 日,OPPO 发布 Find X9 Ultra,搭载第五代骁龙 8 至尊版移动平台,辅以 LPDDR5X 内存与 UFS 4.1 闪存;屏幕配备 6.82 英寸 QHD+ 直面屏,分辨率为 3168×1440,最高支持 144Hz 刷新率,典型值全局亮度 800 尼特、全局激发 1800 尼特;影像系统由 2 亿像素广角、5000 万像素超广角、2 亿像素长焦及 5000 万像素超长焦构成,前置搭载 5000 万像素摄像头;配备 7050mAh 电池,支持 100W 有线闪充及 50W 无线闪充;提供绒砂峡谷、极地冰川与大地苔原三种配色,定价 7499 元起(12+256GB)。来源

4 月 21 日,Beats 推出玛瑙黑配色 JENNIE 特别版 Beats Solo 4,该产品采用极简单色设计,配备两枚可拆卸黑色蝴蝶结,单侧 UltraPlush 耳罩刻印专属音乐符号,并随附同色系便携盒。新品将于 4 月 24 日上午 9 点正式发售,售价 1799 元,渠道涵盖 Apple.com 及 Beats 京东自营旗舰店。来源

同时,Beats 也宣布旗下配件产品组合新增一款 3 米 USB-C 转 USB-C 连接线,依然采用防缠结编织与内部加固设计,支持最高 240W 充电功率,可承载数据传输、音频输出及 CarPlay 车载连接,兼容配备 USB-C 接口的 Apple 及 Android 设备,提供闪电黑、奔涌灰、飚速蓝、劲速红四种配色,即日起在全球 50 余个国家与地区发售,可通过 apple.com.cn 订购,售价 229 元。来源

4 月 21 日,OpenAI 发布 ChatGPT Images 2.0 模型及 API 接口 gpt-image-2。该模型在指令遵循、视觉构图与文本渲染方面提升显著,支持包含中、日、韩、印地语及孟加拉语在内的多语言文本生成,宽高比支持范围为 3:1 至 1:3,API 模式下最高支持 2K 分辨率,模型知识库截止日期为 2025 年 12 月,启用「思考」模式后还能联网检索信息并具备自我校验逻辑,可一次性生成最多 8 张具有连贯性的图像。

即日起该模型面向所有 ChatGPT 及 Codex 用户开放,包含「思考」功能的高级输出仅限 ChatGPT Plus、Pro 及 Business 订阅用户使用;gpt-image-2 已同步上线 API,资费标准取决于选定的图像质量与分辨率。来源
4 月 21 日,宁德时代发布第三代神行超充电池、第三代麒麟电池及超换一体补能规划。第三代神行超充电池支持等效 10C 与峰值 15C 超充能力,常温环境满电耗时 6 分钟;第三代麒麟电池能量密度达 280Wh/kg,支持 1000 公里续航,配套热电分离安全技术;产品矩阵同步涵盖麒麟凝聚态电池、第二代骁遥超级增·混电池及钠新电池,其中钠新电池定于 2026 年四季度规模化量产。至 2026 年底,宁德时代计划建成 4000 座乘用车与重卡超换一体站,实现可换可充及按需配电功能。来源

4 月 20 日,GitHub Copilot 宣布调整个人订阅计划,即日起暂停 Pro、Pro+ 及 Student 计划的新用户注册,Pro+ 计划的使用限额调整为 Pro 计划的 5 倍以上,具体使用限额可通过 VS Code 与 Copilot CLI 实时查看,同时模型访问 Pro 计划不再支持 Opus 系列模型,Pro+ 计划仅保留 Opus 4.7,移除 Opus 4.5 和 Opus 4.6。受影响用户可在 5 月 20 日前通过账单设置页面取消订阅,并申请获取与剩余有效期对应的退款。来源
4 月 21 日,亚马逊追加 50 亿美元投资 Anthropic 并签署长期算力供应协议。本轮融资使亚马逊对 Anthropic 的累计直接投资额达 130 亿美元,根据协议,若双方达成特定商业里程碑,后续亚马逊还将追加 200 亿美元。Anthropic 将在未来十年内向亚马逊 AWS 投入超过 1000 亿美元用于采购包括 Graviton、Trainium2、Trainium4 在内的定制 AI 芯片,以应对 Claude 模型用户增长带来的基础设施压力;双方规划 2026 年底前实现 1 吉瓦算力供给,最终供应上限为 5 吉瓦。来源
4 月 22 日,微软宣布将 Xbox Game Pass Ultimate 的月费将从 29.99 美元下调至 22.99 美元,PC 版 Game Pass 月费从 16.49 美元下调至 13.99 美元。同时从今年(2026 年)起,未来的《使命召唤》新作在首发时将不再包含在 Game Pass Ultimate 或 PC Game Pass 中,而是在随后的年末假日季(大约一年后)加入,已在库中的《使命召唤》现有作品不受影响。来源
> 下载 少数派 2.0 客户端、关注 少数派公众号,解锁全新阅读体验 📰
> 实用、好用的 正版软件,少数派为你呈现



从代码签名的技术与合规角度,“未知开发者 / 未知发布者” 的核心原因是:你的软件没有被系统信任的数字证书签名,导致操作系统(Windows SmartScreen /macOS Gatekeeper)无法验证发布者身份与代码完整性。 下面从原理 → 证书选型 → Windows 签名 → macOS 签名 → 长期信任完整说明。 系统安全机制的核心判断: 只有正规代码签名才能彻底消除警告,用户端看到 “发布者:XX 公司”。 ↓ 代码签名证书:https://www.joyssl.com/certificate/select/code_signing.html?n... ↑ 主流 CA:DigiCert、Sectigo、GlobalSign、JoySSL(国内) 准备材料: 生成 CSR(证书签名请求) powershell cmd cmd bash 运行 bash 运行 bash 运行 必须加时间戳 SHA256 算法 证书安全 驱动程序特殊要求 避免无效方案 表格 Windows macOS 一句话:正规代码签名是唯一彻底解决 “未知开发者” 的方法,临时绕过只是权宜之计。一、为什么未签名会报 “未知开发者”?
二、代码签名证书类型与选型(Windows)
1. OV 代码签名(组织验证,主流)
2. EV 代码签名(扩展验证,最高信任)

三、Windows 完整代码签名流程(彻底解决 “未知发布者”)
1. 申请证书(CA 机构)
# PowerShell生成
New-SelfSignedCertificate -Type CodeSigningCert -Subject "CN=你的公司名" -KeyUsage DigitalSignature2. 用 SignTool 签名(Windows SDK)
(1)安装 SignTool
(2)签名命令(必加时间戳!)
signtool sign /f 证书.pfx /p 证书密码 /fd SHA256 /tr http://timestamp.digicert.com /td SHA256 你的软件.exe/f:证书文件/p:证书密码/fd SHA256:签名算法(必须 SHA256,旧 SHA1 无效)/tr:时间戳服务器(关键!避免证书过期后签名失效)(3)验证签名
signtool verify /pa /v 你的软件.exe3. 提升 SmartScreen 信誉(OV 证书必做)
四、macOS 代码签名(解决 “无法打开,因为来自未知开发者”)
1. 前提:苹果开发者账号
2. 证书类型
3. 签名 + 公证(macOS 10.15+ 必须)
(1)用 codesign 签名
# 签名(--deep递归签内嵌组件)
codesign --force --deep --sign "Developer ID Application: 你的公司名 (TeamID)" --timestamp 你的App.app--timestamp:添加时间戳(必加)security find-identity -v -p codesigning(2)苹果公证(Notarize)—— 强制!
# 上传公证
xcrun notarytool submit 你的App.zip --apple-id 你的邮箱 --password 专用密码 --team-id 你的TeamID
# 查看日志
xcrun notarytool log 提交ID --apple-id 你的邮箱 --password 专用密码 --team-id 你的TeamID
# stapler 绑定公证结果(离线也可验证)
xcrun stapler staple 你的App.app(3)验证
codesign -vvv 你的App.app
spctl -a -vvv 你的App.appaccepted、source=Developer ID五、关键最佳实践(必看)
六、效果对比(签名 vs 未签名)
状态 用户看到 系统警告 转化率 未签名 未知发布者 红色强拦截 低(流失 70%+) OV 签名(新) 公司名 轻度警告 中 OV(信誉满) 公司名 无警告 高 EV 签名 公司名 立即无警告 最高 macOS 签名 + 公证 可信开发者 无警告 高 七、总结(最简路径)
问题:用其普通方法对比(图1)无法对比到其主模块内子模块(gitsubmodule)的diff(图2) 解决:

先用命令输出整体的diffgit diff --submodule=diff you-project-v1.7.7 you-project-v1.7.6 > full_diff.patch
再用apply patch(图1) ,
然后引用打的patch文件包,如下图,sub_* 相关的子模块就可以愉快的看diff了
最近在折腾一个项目:WG-FRIEND
一句话介绍:
Semantic WireGuard/BoringTun lifecycle and client management helper
它的出发点其实很简单:
我这边最近比较常见的几个场景,是需要一台比较稳定的服务器做跨网络访问,需要远程回家,也需要把多台设备之间的 WireGuard 生命周期管理得更清楚一些。
但我一直觉得,现有这类方案里有个空档:
wg-quick 很好用,但更像“把接口拉起来”的工具所以用 Rust 实现 的 wg-friend 就此开始:将“拉起接口 / 管理服务 / 管理客户端 / 导入历史资产 / 做诊断”这些事情,从零散脚本提升成一个语义更明确的 control plane 。
目前这个项目主要做了几件事:
命令面我切成了四组:
serverclientservicedoctor我不太想继续沿用“全靠 shell 拼起来”的方式,而是想把常用动作收敛成更稳定的 CLI 语义。
wg-friend 会把可完整物化的客户端,纳入 /etc/wg-friend 下面的 canonical state 。
也就是说,进入管理域的前提不是“这个客户端貌似存在过”,而是它必须足够完整,能产出:
很多现有机器并不是从零开始的,已经有 /etc/wireguard、有过去导出的 client conf 、也可能混着 PiVPN 或手工维护的文件。
所以我做了 client import,去扫描本地已有客户端配置,校验完整性,推导公钥,对上 server peer set ,然后再写入 wg-friend 的 canonical state 。
我更希望这个项目能做的是:
让旧部署渐进迁移,而不是推倒重来。
这里我比较明确的设计是:
协议实现、服务托管、运维语义,这三层最好不要混成一团。
这个项目是 Rust 写的。
我没有做 TUI ,而是更偏向:
我想做的不是“一个很炫的界面”,而是一个真正能放到服务器上长期跑的 WireGuard/BoringTun helper 。
我现在对它的定位,大概就是:
BoringTun 不做 manager ,那这一层我来做。
如果你也有下面这些场景:
wg-quick + shell 更清晰一些欢迎看看,也欢迎直接拍砖。
目前还是比较早期,主要先把管理模型、状态模型和生命周期边界打清楚。
前面一篇文章,我们手写了了一个mini版的Tomcat,接下来我们从源码和架构的角度来学习Tomcat Tomcat的前身为Catalina,Catalina又是一个轻量级的Servlet容器。在美国,catalina是一个很美的小岛。所以Tomcat作者的寓意可能是想把Tomcat设计成一个优雅美丽且轻量级的web服务器。Tomcat从4.x版本开始除了作为支持Servlet的容器外,额外加入了很多的功能,比如:jsp、el、naming等等,所以说Tomcat不仅仅是Catalina。 在互联网兴起之初,当时的Sun公司(后面被Oracle收购)已然看到了这次机遇,于是设计出了Applet来对Web应用的支持。不过事实却并不是预期那么得好,Sun悲催地发现Applet并没有给业界带来多大的影响。经过反思,Sun就想既然机遇出现了,市场前景也非常不错,总不能白白放弃了呀,怎么办呢?于是又投入精力去搞一套规范出来,这时Servlet诞生了! 一个Servlet主要做下面三件事情: Servlet没有main方法,所以,如果要执行,则需要在一个容器里面才能执行,这个容器就是为了支持Servlet的功能而存在,Tomcat其实就是一个Servlet容器的实现。 官网:https://tomcat.apache.org/tomcat-8.0-doc/architecture/overvie... Tomcat 的架构设计以 模块化、分层、解耦 为核心,遵循 Java Servlet 规范,同时支持高性能、高扩展的 Web 服务。其整体架构可概括为 “连接器(Connector)- 容器(Container)” 双层模型,并通过 Lifecycle 生命周期管理机制 和 责任链模式(Pipeline-Valve) 实现组件协同。 Tomcat的架构呈“套娃式”嵌套:Server → Service → (Connector + Engine) → Host → Context → Wrapper 核心架构组成: Service:将一个或多个 Connector 与一个 Engine 绑定,构成独立服务单元。 Container(容器):负责加载和管理 Servlet,处理业务逻辑,包含四级嵌套容器: 上述模块的理解不是孤立的,它可以直接映射为Tomcat的web.xml配置,让我们联系起来看 假设来自客户的请求为:http://localhost:8080/test/index.jsp 请求被发送到本机端口8080,被在那里侦听的Coyote HTTP/1.1 Connector,然后 我们知道组成Tomcat的是各种各样的组件,每个组件各司其职,组件与组件之间有明确的职责划分,同时组件与组件之间又通过一定的联系相互通信。Tomcat整体就是一个个组件的堆砌! 我们在后续阅读Tomcat源码的时候,会发现代码里充斥着大量的类似于下面的代码。 而这实际上就是通过JMX来管理相应对象的代码。这儿我们不会详细讲述什么是JMX,我们只是简单地说明一下JMX的概念,参考JMX百度百科。 如果我们查阅各个组件的源代码,会发现绝大多数组件实现了Lifecycle接口,这也就是我们所说的基于生命周期。生命周期的各个阶段的触发又是基于事件的方式。 我们看下整体的初始化和启动的流程,在理解的时候可以直接和Tomcat架构设计中组件关联上: 看了下网上关于Tomcat的文章,很多直接关注在纯代码的分析,这种是很难的;我建议你一定要把代码加载进来自己看一下,然后这里我把它转化为核心的几个问题来帮助你理解。 Tomcat源码就从它的main方法开始。Tomcat的main方法在org.apache.catalina.startup.Bootstrap 里。让我们带着这个为看下Catalina的初始化的 通过上面几行关键代码的注释,我们就可以看出Catalina是如何初始化的。这里还留下一个问题,tomcat为什么要初始化不同的classloader呢?我们将在下文进行详解。 我们用 让我们带着这个为看下Catalina的初始化的 通过上面几行关键代码的注释,我们就可以看出Catalina是如何初始化的。这里还留下一个问题,tomcat为什么要初始化不同的classloader呢?我们将在下文进行详解。 在Bootstrap中我们可以看到有如下三个classloader 不妨再看下如何创建的? 方法的逻辑也比较简单就是从 catalina.property文件里找 common.loader, shared.loader, server.loader 对应的值,然后构造成Repository 列表,再将Repository 列表传入ClassLoaderFactory.createClassLoader 方法,ClassLoaderFactory.createClassLoader 返回的是 URLClassLoader,而Repository 列表就是这个URLClassLoader 可以加在的类的路径。 在catalina.property文件里 其中 shared.loader, server.loader 是没有值的,createClassLoader 方法里如果没有值的话,就返回传入的 parent ClassLoader,也就是说,commonLoader,catalinaLoader,sharedLoader 其实是一个对象。在Tomcat之前的版本里,这三个是不同的URLClassLoader对象。 初始化完三个ClassLoader对象后,init() 方法就使用 catalinaClassLoader 加载了org.apache.catalina.startup.Catalina 类,并创建了一个对象,然后通过反射调用这个对象的 setParentClassLoader 方法,传入的参数是 sharedClassLoader。最后吧这个 Catania 对象复制给 catalinaDaemon 属性。 可以复习下类加载机制的基础:解密类加载机制:深入理解JVM如何加载你的代码 Java是一门面向对象的语言,而对象又必然依托于类。类要运行,必须首先被加载到内存。我们可以简单地把类分为几类: 假如我们自己编写一个类 所以,Sun(后被Oracle收购)采用了另外一种方式来保证最基本的、也是最核心的功能不会被破坏。你猜的没错,那就是双亲委派模式! 双亲委派模式对类加载器定义了层级,每个类加载器都有一个父类加载器。在一个类需要加载的时候,首先委派给父类加载器来加载,而父类加载器又委派给祖父类加载器来加载,以此类推。如果父类及上面的类加载器都加载不了,那么由当前类加载器来加载,并将被加载的类缓存起来。 所以上述类是这么加载的 ContextClassLoader(上下文类加载器)就来解围了。 在java.lang.Thread里面有两个方法,get/set上下文类加载器 我们可以通过在SPI类里面调用getContextClassLoader来获取第三方实现类的类加载器。由第三方实现类通过调用setContextClassLoader来传入自己实现的类加载器, 这样就变相地解决了双亲委派模式遇到的问题。 原因在于一个Tomcat容器允许同时运行多个Web程序,每个Web程序依赖的类又必须是相互隔离的。因此,如果Tomcat使用双亲委派模式来加载类的话,将导致Web程序依赖的类变为共享的。 举个例子,假如我们有两个Web程序,一个依赖A库的1.0版本,另一个依赖A库的2.0版本,他们都使用了类xxx.xx.Clazz,其实现的逻辑因类库版本的不同而结构完全不同。那么这两个Web程序的其中一个必然因为加载的Clazz不是所使用的Clazz而出现问题!而这对于开发来说是非常致命的! 我们在这里一定要看下官网提供的类加载的文档 结合经典的类加载机制,我们完整的看下Tomcat类加载图 我们在这张图中看到很多类加载器,除了Jdk自带的类加载器,我们尤其关心Tomcat自身持有的类加载器。仔细一点我们很容易发现:Catalina类加载器和Shared类加载器,他们并不是父子关系,而是兄弟关系。为啥这样设计,我们得分析一下每个类加载器的用途,才能知晓。 Common类加载器,负责加载Tomcat和Web应用都复用的类 Shared类加载器,负责加载Tomcat下所有的Web应用程序都复用的类,而这些被加载的类在Tomcat中将不可见 同样的,我们可以看到通过ContextClassLoader(上下文类加载器)的setContextClassLoader来传入自己实现的类加载器 我们知道WebApp类加载器是Web应用私有的,而每个Web应用其实算是一个Context,那么我们通过Context的实现类应该可以发现。在Tomcat中,Context的默认实现为StandardContext,我们看看这个类的startInternal()方法,在这儿我们发现了我们感兴趣的WebApp类加载器。 入口代码非常简单,就是webappLoader不存在的时候创建一个,并调用setLoader方法。我们接着分析setLoader 这儿,我们感兴趣的就两行代码: 上一步,我们知道catalina load的触发,因为有参数所以是load(String[])方法。我们进而看下这个load方法做了什么? 总体流程如下: 已经弃用了,Tomcat10会删除这个方法。 设置额外的系统变量 分三大块,下面的代码还是很清晰的: 替换掉System.out, System.err为自定义的PrintStream 在 load 方法之后,Tomcat 就初始化了一系列的组件,接着就可以调用 start 方法进行启动了。 上面这段代码,逻辑非常简单,首先确定 getServer() 方法不为 null ,也就是确定 server 属性不为null,而 server 属性是在 load 方法就初始化了。 整段代码的核心就是 try-catch 里的 getServer().start() 方法了,也就是调用 Server 对象的 start() 方法来启动 Tomcat。本篇文章就先不对 Server 的 start() 方法进行解析了,下篇文章会单独讲。 调用完 Server#start 方法之后,注册了一个ShutDownHook,也就是 CatalinaShutdownHook 对象, CatalinaShutdownHook 的逻辑也简单,就是调用 Catalina 对象的 stop 方法来停止 tomcat。 最后就进入 if 语句了,await 是在 Bootstrap 里调用的时候设置为 true 的,也就是本文开头的时候提到的三个方法中的一个。await 方法的作用是停住主线程,等待用户输入shutdown 命令之后,停止等待,之后 main 线程就调用 stop 方法来停止Tomcat。 Catalina 的 stop 方法主要逻辑是调用 Server 对象的 stop 方法。 上面我们看到CatalinaShutdownHook, 这里有必要谈谈JVM的关闭钩子。 关闭钩子是指通过Runtime.addShutdownHook注册的但尚未开始的线程。这些钩子可以用于实现服务或者应用程序的清理工作,例如删除临时文件,或者清除无法由操作系统自动清除的资源。 JVM既可以正常关闭,也可以强行关闭。正常关闭的触发方式有多种,包括:当最后一个“正常(非守护)”线程结束时,或者当调用了System.exit时,或者通过其他特定于平台的方法关闭时(例如发送了SIGINT信号或者键入Ctrl-C)。 在正常关闭中,JVM首先调用所有已注册的关闭钩子。JVM并不能保证关闭钩子的调用顺序。在关闭应用程序线程时,如果有(守护或者非守护)线程仍然在执行,那么这些线程接下来将与关闭进程并发执行。当所有的关闭钩子都执行结束时,如果runFinalizersOnExit为true【通过Runtime.runFinalizersOnExit(true)设置】,那么JVM将运行这些Finalizer(对象重写的finalize方法),然后再停止。JVM不会停止或中断任何在关闭时仍然运行的应用程序线程。当JVM最终结束时,这些线程将被强行结束。如果关闭钩子或者Finalizer没有执行完成,那么正常关闭进程“挂起”并且JVM必须被强行关闭。当JVM被强行关闭时,只是关闭JVM,并不会运行关闭钩子(举个例子,类似于电源都直接拔了,还怎么做其它动作呢?)。 下面是一个简单的示例: 和(可能的)执行结果(因为JVM不保证关闭钩子的调用顺序,因此结果中的第二、三行可能出现相反的顺序): 可以看到,main函数执行完成,首先输出的是Main Thread Ends,接下来执行关闭钩子,输出Hook2 Ends和Hook1 Ends。这两行也可以证实:JVM确实不是以注册的顺序来调用关闭钩子的。而由于hook3在调用了addShutdownHook后,接着对其调用了removeShutdownHook将其移除,于是hook3在JVM退出时没有执行,因此没有输出Hook3 Ends。 另外,由于MyHook类实现了finalize方法,而main函数中第一行又通过Runtime.runFinalizersOnExit(true)打开了退出JVM时执行Finalizer的开关,于是3个hook对象的finalize方法被调用,输出了3行Finalize。 注意,多次调用addShutdownHook来注册同一个关闭钩子将会抛出IllegalArgumentException: 另外,从JavaDoc中得知:一旦JVM关闭流程开始,就只能通过调用halt方法来停止该流程,也不可能再注册或移除关闭钩子了,这些操作将导致抛出IllegalStateException。 如果在关闭钩子中关闭应用程序的公共的组件,如日志服务,或者数据库连接等,像下面这样: 由于关闭钩子将并发执行,因此在关闭日志时可能导致其他需要日志服务的关闭钩子产生问题。为了避免这种情况,可以使关闭钩子不依赖那些可能被应用程序或其他关闭钩子关闭的服务。实现这种功能的一种方式是对所有服务使用同一个关闭钩子(而不是每个服务使用一个不同的关闭钩子),并且在该关闭钩子中执行一系列的关闭操作。这确保了关闭操作在单个线程中串行执行,从而避免了在关闭操作之前出现竞态条件或死锁等问题。 通过Hook实现临时文件清理 Catalina 类承接了 Bootstrap 类的 load 和 start 方法,然后根据配置初始化了 Tomcat 的组件,并调用了 Server 类的 init 和 start 方法来启动 Tomcat。引入
Tomcat和Catalina是什么关系?
什么是Servlet?
所谓Servlet,其实就是Sun为了让Java能实现动态可交互的网页,从而进入Web编程领域而制定的一套标准!
核心架构设计

从web.xml配置和模块对应角度
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
<GlobalNamingResources>
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
<Engine name="Catalina" defaultHost="localhost">
<Realm className="org.apache.catalina.realm.LockOutRealm">
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
</Host>
</Engine>
</Service>
</Server>从一个完整请求的角度来看
通过一个完整的HTTP请求,我们还需要把它贯穿起来
从源码的设计角度看
从功能的角度将Tomcat源代码分成5个子模块,分别是:
从后续深入理解的角度
我们看完上述组件结构后,后续应该重点从哪些角度深入理解Tomcat呢?
Registry.getRegistry(null, null).invoke(mbeans, "init", false);
Registry.getRegistry(null, null).invoke(mbeans, "start", false);JMX(Java Management Extensions,即Java管理扩展)是一个为应用程序、设备、系统等植入管理功能的框架。JMX可以跨越一系列异构操作系统平台、系统体系结构和网络传输协议,灵活的开发无缝集成的系统、网络和服务管理应用。
启动过程详解
总体流程

启动过程代码浅析
Bootstrap主入口?
/**
* 初始化守护进程
*
* @throws Exception Fatal initialization error
*/
public void init() throws Exception {
// 初始化classloader(包括catalinaLoader),下文将具体分析
initClassLoaders();
// 设置当前的线程的contextClassLoader为catalinaLoader
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// 通过catalinaLoader加载Catalina,并初始化startupInstance 对象
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// 通过反射调用了setParentClassLoader 方法
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}Bootstrap如何初始化Catalina的?
Sequence Diagram插件来看main方法的时序图,但是可以发现它并没有帮我们画出Bootstrap初始化Catalina的过程,这和上面的组件初始化不符合?
/**
* 初始化守护进程
*
* @throws Exception Fatal initialization error
*/
public void init() throws Exception {
// 初始化classloader(包括catalinaLoader),下文将具体分析
initClassLoaders();
// 设置当前的线程的contextClassLoader为catalinaLoader
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
// 通过catalinaLoader加载Catalina,并初始化startupInstance 对象
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();
// 通过反射调用了setParentClassLoader 方法
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}启动过程:类加载机制详解
Tomcat初始化了哪些classloader
ClassLoader commonLoader = null;
ClassLoader catalinaLoader = null;
ClassLoader sharedLoader = null;如何初始化的呢?
private void initClassLoaders() {
try {
// commonLoader初始化
commonLoader = createClassLoader("common", null);
if (commonLoader == null) {
// no config file, default to this loader - we might be in a 'single' env.
commonLoader = this.getClass().getClassLoader();
}
// catalinaLoader初始化, 父classloader是commonLoader
catalinaLoader = createClassLoader("server", commonLoader);
// sharedLoader初始化
sharedLoader = createClassLoader("shared", commonLoader);
} catch (Throwable t) {
handleThrowable(t);
log.error("Class loader creation threw exception", t);
System.exit(1);
}
}可以看出,catalinaLoader 和 sharedLoader 的 parentClassLoader 是 commonLoader。
如何创建classLoader的?
private ClassLoader createClassLoader(String name, ClassLoader parent)
throws Exception {
String value = CatalinaProperties.getProperty(name + ".loader");
if ((value == null) || (value.equals("")))
return parent;
value = replace(value);
List<Repository> repositories = new ArrayList<>();
String[] repositoryPaths = getPaths(value);
for (String repository : repositoryPaths) {
// Check for a JAR URL repository
try {
@SuppressWarnings("unused")
URL url = new URL(repository);
repositories.add(new Repository(repository, RepositoryType.URL));
continue;
} catch (MalformedURLException e) {
// Ignore
}
// Local repository
if (repository.endsWith("*.jar")) {
repository = repository.substring
(0, repository.length() - "*.jar".length());
repositories.add(new Repository(repository, RepositoryType.GLOB));
} else if (repository.endsWith(".jar")) {
repositories.add(new Repository(repository, RepositoryType.JAR));
} else {
repositories.add(new Repository(repository, RepositoryType.DIR));
}
}
return ClassLoaderFactory.createClassLoader(repositories, parent);
}common.loader="${catalina.base}/lib","${catalina.base}/lib/*.jar","${catalina.home}/lib","${catalina.home}/lib/*.jar"
server.loader=
shared.loader=Class<?> startupClass = catalinaLoader.loadClass("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.getConstructor().newInstance();深入理解
什么是类加载机制
如果所有的类都使用一个类加载器来加载,会出现什么问题呢?
java.util.Object,它的实现可能有一定的危险性或者隐藏的bug。而我们知道Java自带的核心类里面也有java.util.Object,如果JVM启动的时候先行加载的是我们自己编写的java.util.Object,那么就有可能出现安全问题!双亲委派模型解决了类错乱加载的问题,也设计得非常精妙。

但它也不是万能的,在有些场景也会遇到它解决不了的问题,比如如下场景。
双亲委派模型问题是如何解决的?
在Java核心类里面有SPI(Service Provider Interface),它由Sun编写规范,第三方来负责实现。SPI需要用到第三方实现类。如果使用双亲委派模型,那么第三方实现类也需要放在Java核心类里面才可以,不然的话第三方实现类将不能被加载使用。但是这显然是不合理的!怎么办呢?
public void setContextClassLoader(ClassLoader cl)
public ClassLoader getContextClassLoader()为什么Tomcat的类加载器也不是双亲委派模型
我们知道,Java默认的类加载机制是通过双亲委派模型来实现的,而Tomcat实现的方式又和双亲委派模型有所区别。
Tomcat类加载机制是怎么样的呢
既然Tomcat的类加载机器不同于双亲委派模式,那么它又是一种怎样的模式呢?


public void init() throws Exception {
initClassLoaders();
// 看这里
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
...WebApp类加载器
到这儿,我们隐隐感觉到少分析了点什么!没错,就是WebApp类加载器。整个启动过程分析下来,我们仍然没有看到这个类加载器。它又是在哪儿出现的呢?
protected synchronized void startInternal() throws LifecycleException {
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
}public void setLoader(Loader loader) {
Lock writeLock = loaderLock.writeLock();
writeLock.lock();
Loader oldLoader = null;
try {
// Change components if necessary
oldLoader = this.loader;
if (oldLoader == loader)
return;
this.loader = loader;
// Stop the old component if necessary
if (getState().isAvailable() && (oldLoader != null) &&
(oldLoader instanceof Lifecycle)) {
try {
((Lifecycle) oldLoader).stop();
} catch (LifecycleException e) {
log.error("StandardContext.setLoader: stop: ", e);
}
}
// Start the new component if necessary
if (loader != null)
loader.setContext(this);
if (getState().isAvailable() && (loader != null) &&
(loader instanceof Lifecycle)) {
try {
((Lifecycle) loader).start();
} catch (LifecycleException e) {
log.error("StandardContext.setLoader: start: ", e);
}
}
} finally {
writeLock.unlock();
}
// Report this property change to interested listeners
support.firePropertyChange("loader", oldLoader, loader);
}((Lifecycle) oldLoader).stop(); // 旧的加载器停止
((Lifecycle) loader).start(); // 新的加载器启动启动过程:Catalina的加载
Catalina的引入
通过前面,我们知道了Tomcat的类加载机制和整体的组件加载流程;我们也知道通过Bootstrap初始化的catalinaClassLoader加载了Catalina,那么进而引入了一个问题就是Catalina是如何加载的呢?加载了什么呢?

/**
* 加载守护进程
*/
private void load(String[] arguments) throws Exception {
// Call the load() method
String methodName = "load";
Object param[];
Class<?> paramTypes[];
if (arguments==null || arguments.length==0) {
paramTypes = null;
param = null;
} else {
paramTypes = new Class[1];
paramTypes[0] = arguments.getClass();
param = new Object[1];
param[0] = arguments;
}
Method method =
catalinaDaemon.getClass().getMethod(methodName, paramTypes);
if (log.isDebugEnabled()) {
log.debug("Calling startup class " + method);
}
method.invoke(catalinaDaemon, param);// 本质上就是调用catalina的load方法
}Catalina的加载
/*
* Load using arguments
*/
public void load(String args[]) {
try {
if (arguments(args)) { // 处理命令行的参数
load();
}
} catch (Exception e) {
e.printStackTrace(System.out);
}
}/**
* Start a new server instance.
*/
public void load() {
// 如果已经加载则退出
if (loaded) {
return;
}
loaded = true;
long t1 = System.nanoTime();
// (已经弃用)
initDirs();
// Before digester - it may be needed
initNaming();
// 解析 server.xml
parseServerXml(true);
Server s = getServer();
if (s == null) {
return;
}
getServer().setCatalina(this);
getServer().setCatalinaHome(Bootstrap.getCatalinaHomeFile());
getServer().setCatalinaBase(Bootstrap.getCatalinaBaseFile());
// Stream redirection
initStreams();
// 启动Server
try {
getServer().init();
} catch (LifecycleException e) {
if (Boolean.getBoolean("org.apache.catalina.startup.EXIT_ON_INIT_FAILURE")) {
throw new java.lang.Error(e);
} else {
log.error(sm.getString("catalina.initError"), e);
}
}
if(log.isInfoEnabled()) {
log.info(sm.getString("catalina.init", Long.toString(TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t1))));
}
}
initDirs
/**
* @deprecated unused. Will be removed in Tomcat 10 onwards.
*/
@Deprecated
protected void initDirs() {
}initNaming
protected void initNaming() {
// Setting additional variables
if (!useNaming) {
log.info(sm.getString("catalina.noNaming"));
System.setProperty("catalina.useNaming", "false");
} else {
System.setProperty("catalina.useNaming", "true");
String value = "org.apache.naming";
String oldValue =
System.getProperty(javax.naming.Context.URL_PKG_PREFIXES);
if (oldValue != null) {
value = value + ":" + oldValue;
}
System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value);
if( log.isDebugEnabled() ) {
log.debug("Setting naming prefix=" + value);
}
value = System.getProperty
(javax.naming.Context.INITIAL_CONTEXT_FACTORY);
if (value == null) {
System.setProperty
(javax.naming.Context.INITIAL_CONTEXT_FACTORY,
"org.apache.naming.java.javaURLContextFactory");
} else {
log.debug("INITIAL_CONTEXT_FACTORY already set " + value );
}
}
}Server.xml的解析
protected void parseServerXml(boolean start) {
// Set configuration source
ConfigFileLoader.setSource(new CatalinaBaseConfigurationSource(Bootstrap.getCatalinaBaseFile(), getConfigFile()));
File file = configFile();
if (useGeneratedCode && !Digester.isGeneratedCodeLoaderSet()) {
// Load loader
String loaderClassName = generatedCodePackage + ".DigesterGeneratedCodeLoader";
try {
Digester.GeneratedCodeLoader loader =
(Digester.GeneratedCodeLoader) Catalina.class.getClassLoader().loadClass(loaderClassName).newInstance();
Digester.setGeneratedCodeLoader(loader);
} catch (Exception e) {
if (log.isDebugEnabled()) {
log.info(sm.getString("catalina.noLoader", loaderClassName), e);
} else {
log.info(sm.getString("catalina.noLoader", loaderClassName));
}
// No loader so don't use generated code
useGeneratedCode = false;
}
}
// 初始化server.xml的位置
File serverXmlLocation = null;
String xmlClassName = null;
if (generateCode || useGeneratedCode) {
xmlClassName = start ? generatedCodePackage + ".ServerXml" : generatedCodePackage + ".ServerXmlStop";
}
if (generateCode) {
if (generatedCodeLocationParameter != null) {
generatedCodeLocation = new File(generatedCodeLocationParameter);
if (!generatedCodeLocation.isAbsolute()) {
generatedCodeLocation = new File(Bootstrap.getCatalinaHomeFile(), generatedCodeLocationParameter);
}
} else {
generatedCodeLocation = new File(Bootstrap.getCatalinaHomeFile(), "work");
}
serverXmlLocation = new File(generatedCodeLocation, generatedCodePackage);
if (!serverXmlLocation.isDirectory() && !serverXmlLocation.mkdirs()) {
log.warn(sm.getString("catalina.generatedCodeLocationError", generatedCodeLocation.getAbsolutePath()));
// Disable code generation
generateCode = false;
}
}
// 用 SAXParser 来解析 xml,解析完了之后,xml 里定义的各种标签就有对应的实现类对象了
ServerXml serverXml = null;
if (useGeneratedCode) {
serverXml = (ServerXml) Digester.loadGeneratedClass(xmlClassName);
}
if (serverXml != null) {
serverXml.load(this);
} else {
try (ConfigurationSource.Resource resource = ConfigFileLoader.getSource().getServerXml()) {
// Create and execute our Digester
Digester digester = start ? createStartDigester() : createStopDigester();
InputStream inputStream = resource.getInputStream();
InputSource inputSource = new InputSource(resource.getURI().toURL().toString());
inputSource.setByteStream(inputStream);
digester.push(this);
if (generateCode) {
digester.startGeneratingCode();
generateClassHeader(digester, start);
}
digester.parse(inputSource);
if (generateCode) {
generateClassFooter(digester);
try (FileWriter writer = new FileWriter(new File(serverXmlLocation,
start ? "ServerXml.java" : "ServerXmlStop.java"))) {
writer.write(digester.getGeneratedCode().toString());
}
digester.endGeneratingCode();
Digester.addGeneratedClass(xmlClassName);
}
} catch (Exception e) {
log.warn(sm.getString("catalina.configFail", file.getAbsolutePath()), e);
if (file.exists() && !file.canRead()) {
log.warn(sm.getString("catalina.incorrectPermissions"));
}
}
}
}initStreams
protected void initStreams() {
// Replace System.out and System.err with a custom PrintStream
System.setOut(new SystemLogHandler(System.out));
System.setErr(new SystemLogHandler(System.err));
}Catalina 的启动
/**
* Start a new server instance.
*/
public void start() {
if (getServer() == null) {
load();
}
if (getServer() == null) {
log.fatal(sm.getString("catalina.noServer"));
return;
}
long t1 = System.nanoTime();
// Start the new server
try {
getServer().start();
} catch (LifecycleException e) {
log.fatal(sm.getString("catalina.serverStartFail"), e);
try {
getServer().destroy();
} catch (LifecycleException e1) {
log.debug("destroy() failed for failed Server ", e1);
}
return;
}
long t2 = System.nanoTime();
if(log.isInfoEnabled()) {
log.info(sm.getString("catalina.startup", Long.valueOf((t2 - t1) / 1000000)));
}
// Register shutdown hook
if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);
// If JULI is being used, disable JULI's shutdown hook since
// shutdown hooks run in parallel and log messages may be lost
// if JULI's hook completes before the CatalinaShutdownHook()
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
false);
}
}
if (await) {
await();
stop();
}
}Catalina 的关闭
/**
* Shutdown hook which will perform a clean shutdown of Catalina if needed.
*/
protected class CatalinaShutdownHook extends Thread {
@Override
public void run() {
try {
if (getServer() != null) {
Catalina.this.stop();
}
} catch (Throwable ex) {
ExceptionUtils.handleThrowable(ex);
log.error(sm.getString("catalina.shutdownHookFail"), ex);
} finally {
// If JULI is used, shut JULI down *after* the server shuts down
// so log messages aren't lost
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).shutdown();
}
}
}
}/**
* Stop an existing server instance.
*/
public void stop() {
try {
// Remove the ShutdownHook first so that server.stop()
// doesn't get invoked twice
if (useShutdownHook) {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
// If JULI is being used, re-enable JULI's shutdown to ensure
// log messages are not lost
LogManager logManager = LogManager.getLogManager();
if (logManager instanceof ClassLoaderLogManager) {
((ClassLoaderLogManager) logManager).setUseShutdownHook(
true);
}
}
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// This will fail on JDK 1.2. Ignoring, as Tomcat can run
// fine without the shutdown hook.
}
// Shut down the server
try {
Server s = getServer();
LifecycleState state = s.getState();
if (LifecycleState.STOPPING_PREP.compareTo(state) <= 0
&& LifecycleState.DESTROYED.compareTo(state) >= 0) {
// Nothing to do. stop() was already called
} else {
s.stop();
s.destroy();
}
} catch (LifecycleException e) {
log.error(sm.getString("catalina.stopError"), e);
}
}聊聊关闭钩子
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook();
}
Runtime.getRuntime().addShutdownHook(shutdownHook);public class T {
@SuppressWarnings("deprecation")
public static void main(String[] args) throws Exception {
//启用退出JVM时执行Finalizer
Runtime.runFinalizersOnExit(true);
MyHook hook1 = new MyHook("Hook1");
MyHook hook2 = new MyHook("Hook2");
MyHook hook3 = new MyHook("Hook3");
//注册关闭钩子
Runtime.getRuntime().addShutdownHook(hook1);
Runtime.getRuntime().addShutdownHook(hook2);
Runtime.getRuntime().addShutdownHook(hook3);
//移除关闭钩子
Runtime.getRuntime().removeShutdownHook(hook3);
//Main线程将在执行这句之后退出
System.out.println("Main Thread Ends.");
}
}
class MyHook extends Thread {
private String name;
public MyHook (String name) {
this.name = name;
setName(name);
}
public void run() {
System.out.println(name + " Ends.");
}
//重写Finalizer,将在关闭钩子后调用
protected void finalize() throws Throwable {
System.out.println(name + " Finalize.");
}
}Main Thread Ends.
Hook2 Ends.
Hook1 Ends.
Hook3 Finalize.
Hook2 Finalize.
Hook1 Finalize.Exception in thread "main" java.lang.IllegalArgumentException: Hook previously registered
at java.lang.ApplicationShutdownHooks.add(ApplicationShutdownHooks.java:72)
at java.lang.Runtime.addShutdownHook(Runtime.java:211)
at T.main(T.java:12)Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
try {
LogService.this.stop();
} catch (InterruptedException ignored){
//ignored
}
}
});使用场景
public class test {
public static void main(String[] args) {
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
public void run() {
System.out.println("auto clean temporary file");
}
}));
}
}小结
你有没有想过—— 当你打开 Cursor 写代码,当你在 Perplexity 搜索,当你用某个"美国产品"感叹"AI真厉害"的时候…… 底层跑的,可能是一个北京团队写的模型。 这个团队叫月之暗面,他们的产品叫 Kimi。 而就在2026年开年,他们做了一件让整个全球AI圈都没想到的事。 3月下旬,AI编程圈发生了一件"罗生门"级别的事件。 估值500亿美元的编程工具 Cursor,推出了新一代旗舰模型 Composer 2,主打"长周期智能体编程",宣传得有声有色。 然后——有人扒出来了。 一个网友操作了一下 base URL,发现里面藏着这样一串字符: 翻译一下:Cursor花了大价钱包装的旗舰模型,底座是 Kimi K2.5。 舆论炸了。马斯克转发评论。Cursor创始人不得不公开道歉。 月之暗面随后确认:双方通过 Fireworks AI 平台存在正式授权合作。 一个来自北京的开源模型,成了硅谷最热编程工具不敢说出口的秘密。 这一天,Kimi 赢得了任何广告都买不来的品牌溢价。 Kimi K2.5 于2026年1月27日正式发布开源。 发布20天内,Kimi的累计收入超过了2025年全年总收入。 1月订阅订单环比增长 8280%,在 Stripe 全球榜单上从百名开外直接冲进前十。 但数字背后,真正的故事是技术。 过去的AI,是你问一句、它答一句。 它可以同时调度最多 100个子Agent分身,并行处理 1500个步骤,所有角色分配、任务拆解、最终验收,全部由主Agent自动完成。 这不是对话,这是自动化作战。 处理100万字的文档,传统Transformer的计算量会呈平方级增长——越长越慢,慢到用不起。 Kimi 团队自研了 Kimi Linear,一种混合线性注意力架构,打破了"所有层必须全注意力"的行业惯例。 结果:在128K到100万字的超长上下文中,解码速度提升5到6倍。 过去模型的每一层,都无差别地叠加前面所有层的信息——重要的和不重要的一视同仁,层数越多,关键信息越被稀释。 Kimi 提出的 Attention Residuals(注意力残差),让模型像人一样"有选择地回忆"——每层根据当前需求,主动调取最值得参考的信息。 48B参数模型训练效率因此提升 1.25倍。 这篇论文发布后,马斯克公开表示"令人印象深刻",OpenAI前研究副总裁 Jerry Tworek 评价:这标志着"深度学习2.0"时代的到来。 顺带一提,这篇论文的第一作者——是一个来自深圳的17岁在读高中生。 2025年初,DeepSeek横空出世,以极低成本开源,让整个中国AI创业圈陷入存在危机。 外界的声音是:模型公司还有独立存在的价值吗? 月之暗面创始人杨植麟的回答,不是一句话,而是一个选择—— 继续死磕基础模型,坚持开源,放弃短期流量,押注技术长期主义。 他们是全球第一个在超大规模训练中跑通 Muon优化器 的公司,解决了Adam优化器十年来的扩展瓶颈。 他们把 MuonClip、Kimi Linear、Attention Residuals 全部开源,贡献给全球开发者社区。 2026年3月,英伟达GTC大会,杨植麟成为唯一受邀现场演讲的中国独立大模型公司创始人,用40分钟向全球系统披露Kimi的技术路线图。 从北京出发,站上硅谷最大的舞台。 4月,Kimi K2.6 Code Preview 已进入内测,专攻代码生成与Agent能力,预计5月正式发布。 与此同时,下一代旗舰 Kimi K3 已在研发中——据传参数规模将达到3到4万亿,直接对标美国头部模型。 月之暗面内部信透露的目标只有一个字: 超越 Anthropic。 有人问,中国AI和美国AI的差距到底有多大? K2.5 给出了一个答案: 当你以为在用美国产品的时候,你可能已经在用中国技术了。 这不是一个追赶的故事,这是一个已经发生的故事。 而月之暗面,才刚开始。 如果你觉得这篇文章有价值,欢迎转发给关注AI的朋友。 关注我,持续追踪全球AI最新动态。 本文由mdnice多平台发布一个中国AI,让硅谷最热独角兽公开道歉
它用美国顶尖实验室1%的资源,做出了让马斯克点赞、让Cursor跪地认错的模型。
01 | 那个让Cursor道歉的故事
accounts/anysphere/models/kimi-k2p5-rl-0317-s515-fast02 | K2.5到底强在哪里?
🔹 它重新定义了"Agent"
K2.5 不一样——它是一个指挥官。🔹 它解决了长文本的速度死穴
🔹 它用"注意力残差"重写了Transformer的记忆方式
03 | 一家公司,做了一个反常识的选择
04 | 下一步,他们盯着什么?
写在最后
一直按时滴药,眼压也降下来了,刚去医院检查回来,视野又有了一点进展,心里七上八下,医生忙得没工夫搭理你,问就是继续滴药不然就做激光或者手术。
有点无奈。。有没有患者朋友能交流下病情,或者青光眼患者交流群或者论坛啥的推荐?
大家好,我是R哥。 近日,Claude 又开始搞事情了,开始要求实名身份验证了。。 Claude 已经上线身份验证功能,针对一些特定场景你可能会看到身份验证提示。。。 Claude 公司简直特么丧心病狂啊。。。 验证条件有多苛刻?来看看吧: 1、需要一个身份证、护照、驾照、而且必须是原件,必须得拿在手上。。 2、一部手机或者带摄像头的电脑,可能会让你用手机或者电脑摄像头现场自拍一张。。 3、需要验证几分钟,官方说一般验证不到五分钟。 更离谱的是,验证通过,不代表你就稳了。。。 验证完成后可能还会封号: 比如,你的账号是在不支持的地区注册的、服务条款违规、未满 18 岁等等。。 中国是不支持的地区,而中国用户又是最多的,这针对性也太强了吧?? 关键是,它不是单纯加强安全,而是在不断抬高普通用户的使用成本,尤其是对依赖 Claude Code 的人来说,这种不确定性会直接影响工作流。 以后上手 Claude Code 的成本会越来越高,也变得越来越不可控,这样恶心的公司,建议及时撤离选择其他 AI 编程工具,比如:Codex、Gemini CLI 等等。 如果一个工具动不动就让你担心封号、验证、限制地区,那它再强,也很难成为真正靠谱的生产力工具。 我个人目前主要就是 Codex、Gemini CLI 相辅相成,GPT-5.4、Gemini 3 Pro 都是顶尖编程模型,不管文本生成、还是编程能力,一点也不输 Claude Code,主打一个稳定、靠谱、好用。 推荐教程: 好了,今天就暂时分享到这里了,R哥持续分享更多 AI 好玩的东西,R哥第一时间推送,关注我和我一起学 AI。 ⚠️ 版权声明: 本文系公众号 "AI技术宅" 原创,未经授权禁止转载,严禁搬运、抄袭、洗稿、侵权一律投诉,并保留追究其法律责任的权利。

如题,效果怎样?
写简历没思路,文笔写的像入..申请书,把我自己写笑了。主要自己实习不多。
看网上好多人说实习可以瞎编,但不是还有实习证明嘛,怕 hr 查最后弄得个信用不好,没有勇气编,也不知道怎么编。
把简历给 ai 改,可能是提示词的原因,改的也不行
如果有大佬能帮小弟提供思路方向,或者告诉我怎么对 ai 提问或者有哪些课,书能帮到我就行