Java到底是编译型还是解释型?拆解javac与JIT双编译机制
说实话我刚入行那会儿也被这个问题绕得头晕。面试官问“Java是编译型语言还是解释型语言”我脑子里第一反应是Java不是有javac吗那当然是编译型啊。可转头又看到“JIT实时编译”这个词有人说Java是解释执行的还有人说HotSpot会运行时编译瞬间就懵了。时间久了才真正想明白这个问题的根源在于Java世界里存在两个完全不同的“编译”动作一个发生在你写完代码敲下javac的那一刻另一个发生在程序运行期间、你毫不知情的某个瞬间。它们虽然都叫“编译”但干的活儿、面对的输入、追求的目标完全不是一回事。这篇文章就把这两个“编译”彻底拆开来讲。我会从javac静态编译到底做了什么说起再讲JIT实时编译是怎么工作的、为什么它才是Java性能的关键最后分享一些我自己用JVM参数和工具观察JIT行为的实操经验以及新手特别容易搞混的几个误区。不管你是准备面试还是写了一段时间Java想搞清楚底层跑得怎么样这篇内容应该都能帮上忙。1. 两个“编译”之间的暧昧关系名字一样活儿完全不同很多人的困惑来自一个很朴素的事实同一个词“编译”在Java世界里被用在两个完全不同的阶段。第一个编译发生在开发期。你写完一个.java源文件执行javac命令它会把这个人类可读的文本文件变成.class字节码文件。这一步面向的是“源代码”处理完之后你得到的是一个可以到处移动的中间产物。字节码既不是机器码也不是源代码它是一个故意设计成“跨平台通用”的指令集。第二个编译发生在运行期。你把.class文件交给JVM去跑JVM一开始会用解释器逐条“翻译”字节码并执行。但如果它发现某个方法被调用得极其频繁成了一个“热点”就会启动JIT实时编译把这段字节码直接“编译”成当前CPU能识别的机器码再让CPU去执行真正的原生指令。你看这里就有个很微妙的点javac编译出来的.class根本就不是给CPU直接跑的它只是给JVM看的“中间稿”。JVM拿到这份中间稿之后依然可以继续用它最偷懒的方式——解释执行只有当它觉得有必要的时候才会动用JIT实时编译来冲刺性能。所以“Java是编译型还是解释型”这个问题本身就不严谨你不说清楚是哪个阶段的“编译”答案永远是残缺的。我用一个生活化的类比来解释。假设你是一个话剧导演拿到一个剧本源代码。你不可能让演员直接按剧本文字上台所以你需要先做一轮“案头工作”把剧本改成分场分幕的台本标注每个角色的上场顺序、台词节奏——这就是javac的工作产出中间台本字节码。但到了正式演出时有的演员临场反应快有的则需要事先把整段台词背得滚瓜烂熟再上台。那些天天都要说的核心台词导演会让演员事先反复排练、形成肌肉记忆到台上张口就来——这就是JIT实时编译。它针对的是“演出中真正高频使用的段落”而不是剧本里的所有内容。所以别再问“Java到底是编译型还是解释型”了。更准确的说法是Java是静态编译javac与运行时编译JIT并存的混合执行体系。理解到这一层后面的内容才有讨论的基础。2. javac的活儿到底是啥语法体检、语义把关、生成字节码唯独不做性能优化聊到javac很多人以为它和C语言的gcc是一样的角色都是把高级语言变成机器码。其实差远了。javac更像一个“质检员加翻译员”它的核心使命不是把代码变快而是把代码变得“可运行、且符合规范”。从一个.java文件到.class文件javac内部大体走这几步词法分析把源代码的字符流切成一个个token比如关键字、变量名、符号。语法分析根据Java语法规则把这些token组装成一棵抽象语法树AST。这一步会检查你的括号平不平衡、有没有把if写成了iff。语义分析做类型检查、变量作用域解析、常量折叠等。比如你把String当int用或者调用了不存在的方法都在这个阶段报错。字节码生成遍历处理好的AST生成对应的.class文件。注意在这些步骤里javac几乎不做那种为了让代码执行得更快而做的深度优化。它确实会做一点常量折叠之类的顺手优化比如int a 1 2很可能直接生成iconst_3但也就停留在这种“搓手可得”的层面。更深层的优化比如循环展开、方法内联、锁消除、逃逸分析javac一概不做。这不是javac“懒惰”而是它的定位决定的。javac面对的是“所有平台上的所有可能硬件”它根本没有足够信息去判断怎么做优化更合适。打个比方你不可能在北京出发前就决定开车路上到底是左转还是右转能省油——你得看到实时路况才知道。同样的道理代码到底会跑在什么CPU上、内存带宽如何、哪段逻辑会成为热点这些都是运行期才知道的信息静态编译阶段根本无从下手。所以你可以把javac理解为“负责把人话翻译成JVM的官方语言并顺手检查稿子有没有语病”的角色。它不负责让程序跑得快那是JIT和运行时系统的活儿。这里我把两个“编译”的核心差异列成一张表方便你对比对比维度javac静态编译JIT实时编译发生时机开发期运行之前运行期程序执行过程中输入.java源文件.class字节码热点代码输出.class字节码文件当前CPU可直接执行的机器码主要目标语法检查、类型检查、规范校验提升热点代码执行性能优化深度几乎不做性能优化深度优化包括内联、逃逸分析等执行位置命令行工具里虚拟机运行时系统内部两者本质上是不同的“物种”但因为中文都叫“编译”就被混为一谈了。3. JIT真正发力点热点检测、解释执行到机器码的切换逻辑理解了javac不负责优化就得接着问那JIT实时编译又是怎么知道“哪段代码值得编译”的答案是它不猜它靠统计数据说话。JVM在运行初期会默认采用解释执行的方式逐条解释字节码。这个阶段速度不快但有一个好处它能在执行过程中收集到大量“真实运行数据”。JVM内部为每个方法都维护着两个计数器调用计数器Invocation Counter和回边计数器Back Edge Counter。调用计数器就是记录这个方法被调用了多少次。回边计数器记录的是方法内部循环往回跳转的次数简单说就是某段循环代码被重复执行了多少次。这两个计数器的设计很有意思它们不是单纯数数而是带权重的。比如在循环里反复执行的那部分代码回边计数器的增长会更快因为循环体内的代码每多执行一次对性能的影响就比一个普通方法调用更大。当某个方法的调用计数和回边计数加起来超过了一个阈值在经典HotSpot虚拟机里这个值可以通过-XX:CompileThreshold设置默认通常是10000次左右JVM就会把该方法判定为“热点方法”并把它放入编译队列。后台会有编译线程去专门处理这个队列把它从字节码翻译成机器码。这里有个关键点你不能忽略JIT实时编译不是“打断正在执行的程序”去编译的。它走的是“一边解释执行一边后台编译”的并行路线。当方法被判定为热点并提交编译后编译线程在后台忙活而程序依然用解释模式跑着。等到编译完成下一次进入该方法时JVM就把执行入口切换到编译后的机器码上。不过还有一个更聪明的情况。假设你有一段循环体跑了上百万次总不能等整个方法都编译完才切换吧这时候JVM会采用OSR栈上替换On-Stack Replacement)技术——循环还没跑完只要检测到这段循环代码达到了热点标准它可以“原地”把当前正在执行的解释帧替换成编译后的机器码帧循环的下一次迭代就开始跑编译后的版本。这个切换非常精妙你几乎感知不到但对性能的提升是巨大的。说到性能差距我做个不算严谨但足够直观的比较解释执行每条字节码可能要做多次查表、类型判断、操作数栈操作而编译后的机器码就是CPU直接执行的原生指令。在纯计算密集型的场景下优化到位的JIT编译代码通常比解释执行快一个数量级某些极端情况甚至可以快到两个数量级。这也是为什么同一个Java程序跑着跑着会“越跑越快”——一开始是JVM在“攒数据”攒够了就开始火力全开。4. 分层编译和OSR为什么启动阶段那么慢跑一会儿又变快了如果你仔细观察过Java进程可能会发现一个现象程序刚启动时响应很慢但跑了一段时间后同样的请求处理速度明显提升。一方面这有缓存变热的因素另一方面JIT实时编译的“分层设计”也在里面扮演重要角色。早期的HotSpot虚拟机只有两种执行模式解释模式和一个单一的JIT编译器。这个单一JIT编译器的优化级别非常高但编译本身耗时也长。如果一个方法被判定为热点JVM直接上最高级别的优化编译期间程序可能卡顿而且一旦优化激进过头可能还会出现需要反优化deoptimization的情况。后来虚拟机引入了分层编译Tiered Compilation机制默认把执行级别分成了好几层第0层解释执行。第1层C1编译器编译也就是客户端编译器。它编译速度快优化相对浅但足以应付“不复杂的热点”。第2层和第3层介于C1和C2之间的中间产物比如记录了一些分析数据的C1编译版本。第4层C2编译器编译也就是服务端编译器。它编译慢但优化极其激进是追求极致性能时才会用的级别。分层编译的“套路”和开一家餐馆很像。刚开业时后厨不熟悉只能按方抓药出菜慢——这是解释执行。后来发现有几道招牌菜点单率特别高主厨先把这些菜的备料流程固定下来做到上菜快且稳定——这是C1编译。再之后针对最热门的爆款菜专门请了研究型大厨去重新设计整个烹饪流程把耗时压到极致——这是C2编译。整个过程里食客不需要知道后厨怎么安排只需要看到出菜越来越快就行。在这个演进过程里OSR技术还有一个更细致的工作它不只是循环场景里的“救火队员”还是分层编译之间的“桥梁”。当一段代码在第0层解释执行时OSR会把它提升到C1编译版本当C1版本运行过程中又积累了足够多的分析数据后OSR又可能再次触发把它从C1版本替换到C2版本。一个方法在短短几十秒内可能经历了从解释到C1再到C2的多次“升级”。我印象很深的一次调试经历是一段批量处理数据的代码刚开始的前一万条数据处理速度很一般我以为是自己算法写得有问题差点就动手重构了。后来在日志里看到调用计数逐渐跨过阈值处理速度突然飙升才意识到是JIT在后台“接管”了。所以遇到Java程序“一开始慢后来快”时先别急着怀疑代码功力很可能只是JIT的预热过程还没完成。5. 命令行验一验我用PrintCompilation和JITWatch观察JIT的工作痕迹说了这么多原理不来点实操总觉得不够落地。JIT实时编译本来就是运行期发生在虚拟机内部的事普通日志根本看不到但JVM给了我们很好的观察窗口。最基础的就是在启动命令里加-XX:PrintCompilation参数。我一般会在测试环境临时加上这个参数跑一段压力测试然后观察控制台输出。效果类似这样java -XX:PrintCompilation -jar my-service.jar输出会列出一行行编译记录每行通常包含时间戳、编译层级、方法名和编译方式。截取一小段大概长这样55 16 3 com.example.OrderService::calculatePrice (45 bytes) 62 17 4 com.example.OrderService::discountForVip (38 bytes) 68 18 3 com.example.core.CartCache::get (23 bytes)这里每行开头的数字是“从启动以来的毫秒数”告诉你这个方法是在哪个时间点被编译的。接着的数字是编译编号。再后面的数字3或4代表编译层级比如3通常指C1编译的高层4就是C2编译。行尾的括号是这个字节码方法的字节数。你要是看到某个方法从“3”升级为“4”说明它经历了C1到C2的完整路径。但PrintCompilation的信息密度太高新手看起来容易蒙。我更推荐两个进阶观察方式第一个是-XX:PrintCompilation配合-XX:UnlockDiagnosticVMOptions -XX:PrintInlining后者会在输出里附带上“内联”日志清楚地告诉你哪些小方法被“塞进”了调用方方法内部。对观察JIT的优化手段很有帮助。第二个是可视化工具JITWatch。它可以把这些编译日志解析成漂亮的图表按包、按类、按方法分组查看编译次数和耗时还能看到某个方法具体做了什么优化。JITWatch对初学者特别友好你不需要懂每一行日志的含义只要会看柱状图就够了。实际操作时我一般先用命令行带参数跑一遍产生日志文件再用JITWatch导入分析。举个例子我去年排查一个订单服务接口的响应波动用PrintCompilation看到核心的checkout()方法被编译到了第4层然后进一步用JITWatch发现该方法内的一个金额计算小助手被内联了但因为内联后代码块变得很大触发了C2的“最大内联大小”限制优化被降级。找到原因后我把那个小助手方法改得精简一点再跑压测P99延迟直接降了30%。这就是观察JIT的实用价值——它不是玄学是真的可以用工具“看见”的。如果你在JITWatch里发现一个热点方法迟迟没有编译记录的上升反而长时间停留在解释层那通常意味着代码路径里存在动态类加载、反射调用或者调用深度太复杂导致编译器无法做出准确分析。这时候你就该反思一下代码是不是把动态分发用得太多了。6. 实际调优中容易搞混的几个地方CompileThreshold、逃逸分析和“没有JIT也能跑”的误区讲到这里核心原理已经差不多了但光知道原理还不够JIT实时编译在实战里有不少让人“摔跟头”的地方我挑几个最常见的误区聊聊。第一个误区是调整-XX:CompileThreshold就能加速JIT生效。这个想法有道理但在现代JVM里远没那么简单。从Java 8开始分层编译是默认开启的此时调用计数器阈值和回边计数器阈值的计算方式变得很复杂并没有一个CompileThreshold就能覆盖全部逻辑。我见过有人为了“让服务启动后尽快变快”把CompileThreshold调成1000结果C1和C2的编译切换频率大增后端编译线程忙得不可开交最后响应反而更慢了。没有充分压测数据支撑时我建议你不要动这个参数保持默认观察即可。第二个误区是禁用JIT能让程序对“热点变化”更敏感。早期有些做规则引擎的团队会觉得反正规则经常变、热点不稳定干脆用-Xint强制解释执行。他们忽略了一个事实——解释执行的性能往往只有JIT执行性能的十分之一甚至更低。禁用JIT带来的不是“更好的适应性”而是全面的性能坍塌。我记得有人做过一个统计同等负载下纯解释执行模式下的CPU使用率能比混合模式高出好几倍。所以除了学习原理、观察程序行为你几乎没有任何理由在生产环境关闭JIT。第三个误区是过于迷信“JIT一定会把代码优化到极致”。JIT确实很强但它不是万能的。它依赖运行时统计信息来决策如果代码里大量使用反射、动态代理、字符串拼接生成SQL等行为会让类型信息变得模糊JIT能做的优化就很有限。此外Debug模式下的调试器也会影响JIT行为——当你打断点时JVM为了保证调试语义正确可能会主动撤销deoptimize某些已经编译的优化代码让程序回退到解释层重新执行。这也是为什么“挂上调试器之后程序慢得不正常”的原因之一。这里还涉及一个我特别想强调的概念JIT的反优化能力。JIT编译出优化版本后如果运行时发现某些假设不成立比如原本以为某个字段永远不会为null结果突然为null了它不会硬着头皮继续跑错误的机器码而是会废弃当前优化版本回退到解释器重新解释执行这个过程就是“反优化”。所以看PrintCompilation日志时你会看到某些方法后面标着made not entrant或zombie不用紧张这只是JIT识时务地放弃了一个不靠谱的优化版本。我记忆里有一次特别有意思的排查服务压测时发现某个排序方法反复被C2编译然后很快又变成“made not entrant”。打开日志一看原来那个排序方法内部会根据不同数据规模选择不同的排序策略导致JIT每一次都预测错数据分布编译完的版本用不了多久就不适用了。后来我在代码里把策略选择逻辑抽出去让热点方法保持“单一且稳定”的行为编译结果这才稳定下来。这种问题很难靠直觉发现但观察JIT日志就能很清晰地暴露出来。最后再分享一个小插曲有一次我在给一个旧的批处理系统做性能分析启动参数里带着-XX:PrintCompilation发现日志里频繁出现某个工具类方法被编译然后又失效的记录。一开始我以为是系统资源不足折腾了好几天最后才发现原来是那段代码里嵌套了三层匿名内部类每一层都触发了不同的热点路径JIT每次都被“带偏”。把匿名类改成静态方法后编译路径变得稳定整体耗时也下去了。这件事给我的体会是JIT实时编译不是一个坐在后台“自动帮你搞定一切”的黑盒它更像一个需要你用日志、用工具去沟通和配合的搭档。理解它的工作原理你才能在写代码时主动为它提供更清晰、更稳定的优化线索遇到性能问题时你也有能力分辨到底是算法问题、GC问题还是JIT编译路径出了问题。下次再有人跟你说“Java的编译很快”你可以问他一句“你说的是javac还是JIT这两个编译差的可不是一点点哦。”

相关新闻

用Go与go-zero搭建电影小程序后台:接口设计、第三方API封装与部署实战

用Go与go-zero搭建电影小程序后台:接口设计、第三方API封装与部署实战

简介:爱看电影小程序的后台Go语言源码,基于go-zero微服务框架编写,适合有一定Go基础、正在开发电影类小程序或希望学习微服务后台结构的开发者。压缩包共78个文件,整体仅76KB,以Go源文件、yaml配置、md文档为主&#x…

2026/10/18 18:16:55 阅读更多 →
MiniCPM-V-4.7-1B-mxfp4架构深度解析:线性注意力与全注意力混合设计的秘密

MiniCPM-V-4.7-1B-mxfp4架构深度解析:线性注意力与全注意力混合设计的秘密

【免费下载链接】MiniCPM-V-4.7-1B-mxfp4 项目地址: https://ai.gitcode.com/hf_mirrors/mlx-community/MiniCPM-V-4.7-1B-mxfp4 点击查看 免费下载 MiniCPM-V-4.7-1B-mxfp4 是一个面向 Apple Silicon 的 1B 参数多模态视觉语言模型,它采用线性注意力与…

2026/10/18 18:15:55 阅读更多 →
零基础入门Vision Transformer图像分类:Python项目实战与避坑指南

零基础入门Vision Transformer图像分类:Python项目实战与避坑指南

简介:这份资源是面向计算机相关专业在校学生、教师及企业员工的Vision Transformer图像分类项目完整实现,适用于毕业设计、课程作业、期末课设及初期项目立项演示等场景。项目基于Python实现,包含可运行的训练与预测脚本、模型定义、数据集加…

2026/10/18 18:15:55 阅读更多 →

最新新闻

集成学习回归实战:从评估到调参的完整指南

集成学习回归实战:从评估到调参的完整指南

1. 集成学习在回归场景的整体设计思路1.1 单模型容易“翻车”,问题到底出在哪先说一个我经常见到的现象:很多初学者拿着一棵决策树直接做回归,训练集上R高得离谱,一到测试集就崩。换成线性回归,预测值甚至可能出现负数…

2026/10/18 18:54:04 阅读更多 →
DecEx-RAG:过程监督与智能剪枝如何将多跳检索提速近6倍

DecEx-RAG:过程监督与智能剪枝如何将多跳检索提速近6倍

1. 为什么拖着不做多跳检索:延迟痛点比想象中更致命我在做检索增强生成相关项目时,最早遇到的一个尴尬局面是:单跳问答的效果已经不错了,但业务方提的需求几乎都是多跳的——"这个产品线在华东区的季度增长率,和竞…

2026/10/18 18:54:04 阅读更多 →
思科模拟器RIPV2配置详解:4个子网互联互通实验

思科模拟器RIPV2配置详解:4个子网互联互通实验

前几天帮人搭了个模拟环境,需求很直白:在一个小型局域网里把4个子网全部打通,让任意一台电脑都能访问另外3个子网里的主机。我直接打开思科模拟器(Packet Tracer),没走静态路由,也没上OSPF&…

2026/10/18 18:54:04 阅读更多 →
CSS3 object-fit全解析:告别图片变形,封面视频自适应不再难

CSS3 object-fit全解析:告别图片变形,封面视频自适应不再难

先问个实在问题:做前端页面的时候,你有没有遇到过这种情况——明明一张挺好看的图片,塞进固定宽高的容器之后,不是被压成扁扁的“矮冬瓜”,就是被裁得只剩中间一小块,怎么调都不对劲。做视频封面的时候更头…

2026/10/18 18:54:04 阅读更多 →
DRM显示时序代码解析:从数据结构到实战调试

DRM显示时序代码解析:从数据结构到实战调试

调试显示驱动这几年,我做得最多的动作不是写新功能,而是跟显示时序打交道。很多人一听“DRM显示时序代码”就觉得这是底层驱动里最枯燥的一段,但其实把它拆开之后,你会发现它就是一组精确到纳秒的“几何描述”,定义了屏…

2026/10/18 18:54:04 阅读更多 →
Codex 本地运行正常,Docker 里却报错?从环境复现到最小修复(TaoToken 统一 Key 通道版)

Codex 本地运行正常,Docker 里却报错?从环境复现到最小修复(TaoToken 统一 Key 通道版)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 18:53:04 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →