新闻详情

新闻详情

首页 / 资讯中心 / 详情

JVM锁升级全解析:从偏向锁到重量级锁的性能优化指南

发布时间:2026/10/1 4:34:42来源:尧图网络
JVM锁升级全解析:从偏向锁到重量级锁的性能优化指南
1. 为什么面试官总爱问锁升级从一次线上故障说起做Java开发的人几乎没人能避开锁升级这个话题。我最早认真研究它不是因为面试而是因为一次线上事故。当时一个订单系统的接口在高峰期频繁出现线程阻塞日志里全是BLOCKED状态的线程堆积。我们用jstack抓线程快照发现大量线程卡在同一个synchronized方法上。明明这个方法内部逻辑很简单就是查询缓存然后更新状态怎么会把线程堵成这样后来排查发现这段代码用的是synchronized修饰的实例方法所有请求进来都要抢同一把锁。更麻烦的是锁竞争激烈之后JVM把锁从无锁状态一路升级成了重量级锁线程从用户态陷入内核态等待性能急剧恶化。直到那次事故我才真正意识到锁升级不是面试题里的概念游戏它直接决定你的程序在并发压力下是平稳运行还是瞬间崩溃。从那次之后我把Java锁升级的整个机制从头到尾啃了一遍。这篇文章不打算给你背八股文而是想把锁升级这条链路讲透——它解决什么问题、每一步怎么触发、阈值参数怎么调、实战中怎么利用它优化代码。无论你是准备面试还是正在排查线上并发问题这篇内容应该都能帮上忙。2. 为什么JVM要设计升级而不是一锁到底2.1 如果只有重量级锁会怎样在Java 1.5之前synchronized只有一种实现方式直接依赖操作系统的互斥量Mutex。线程获取锁时如果锁被占用这个线程会被挂起从用户态切换到内核态由操作系统调度。这个过程我们称为线程上下文切换代价非常高——一次切换大概要消耗几十微秒到上百微秒的时间。用个生活化的类比你去银行柜台办业务进门先取号如果前面有人在办你就只能坐在椅子上干等等着柜台工作人员喊号。你的时间完全由银行调度自己什么都做不了。这就是重量级锁的工作方式线程一旦拿不到锁就彻底放弃CPU进入休眠等待。问题在于很多synchronized代码块实际执行时间极短比如就做一个变量赋值耗时几百纳秒。让一个线程为了几百纳秒的临界区操作去经历一次内核态切换就像你为了取一张纸专门打出租车跑一趟公司——成本远高于收益。早期Java并发性能被人诟病很大一部分原因就在这。2.2 乐观锁思想带来的转机后来研究者发现大多数锁竞争场景里临界区被执行的时间非常短而且并发冲突并不频繁。与其一上来就用重量级锁不如让线程先乐观一点假设没有竞争用更轻量的手段直接进临界区如果发现真的有竞争再逐步升级到更重的方案。这就是锁升级的核心思路先无锁再轻量最后才重量。像极了先自己动手试试搞不定再请专业团队。锁升级的完整链路是无锁状态 - 偏向锁 - 轻量级锁 - 重量级锁。但不是每次升级都必须经历每一级JVM会根据实际的竞争情况跳过某些阶段。下面我逐个拆解。3. 偏向锁假设只有一个线程在用3.1 偏向锁的工作原理偏向锁的思想很朴素如果一个锁从始至终只被一个线程访问那这个线程根本不需要做任何同步操作每次进入临界区直接进去就行。JVM做的只是在对象头的Mark Word里记录这个线程的ID表示这把锁偏向这个线程。Mark Word很关键它是对象头的一部分不同状态下存储的内容不同。在偏向锁状态下Mark Word里存的是偏向线程ID、偏向时间戳、对象分代年龄以及偏向锁标志位01和锁标志位01。当一个线程第一次进入synchronized块时JVM会通过CAS操作把当前线程ID写入Mark Word。如果成功这个线程下次再来就不做任何检查直接进入。如果失败说明锁已经被其他线程占用那就触发撤销进入下一阶段。3.2 偏向锁什么时候被撤销偏向锁撤销的核心条件是出现了第二个线程竞争这把锁。注意是竞争不是使用。哪怕第二个线程只是偶尔访问一次也会导致偏向锁撤销。撤销偏向锁需要等待一个安全点Safe Point也就是所有线程都暂停执行的时刻。JVM在安全点检查持有偏向锁的线程是否存活如果线程已经退出临界区就把对象头恢复成无锁状态如果线程还在临界区内就升级为轻量级锁。这个撤销过程并不便宜。所以如果一份代码有强烈竞争比如多个线程高频访问同一个锁对象偏向锁反而会成为性能负担——它不断被撤销、不断重新偏向白白浪费CPU。这就是为什么JVM后来引入了偏向锁延迟Biased Locking Startup Delay机制默认在JVM启动后4秒才开启偏向锁。原因很简单刚启动时应用还在初始化大量新线程会访问对象此时偏向锁频繁撤销的代价远大于收益。另外从JDK 15开始偏向锁被标记为废弃JDK 18中默认禁用。因为现代硬件和JVM优化让偏向锁的收益越来越小而撤销成本在竞争激烈时反而很明显。我给一个建议如果你的应用跑的是JDK 15不要因为偏向锁被废了就觉得恐慌实际上对大多数场景影响极其有限但如果你还在维护JDK 8/11的老项目理解偏向锁仍然有价值。3.3 怎么确认偏向锁是否生效想验证偏向锁的实际状态最直接的办法是加JVM参数打印锁相关信息-XX:PrintBiasedLockingStatistics不过这个参数在部分JDK版本里不可用。更通用的做法是写个小Demo用jolJava Object Layout库查看对象头的布局和锁标记位。jol是OpenJDK提供的工具专门用来分析对象内存布局非常直观。dependency groupIdorg.openjdk.jol/groupId artifactIdjol-core/artifactId version0.17/version /dependency然后配合一段简单代码public class LockLayoutDemo { public static void main(String[] args) throws Exception { Object obj new Object(); // 打印未加锁状态的对象头 System.out.println(ClassLayout.parseInstance(obj).toPrintable()); synchronized (obj) { // 打印偏向锁/轻量级锁状态的对象头 System.out.println(ClassLayout.parseInstance(obj).toPrintable()); } } }运行后你会看到对象头里锁标志位的变化。注意如果你在JDK 15以上的版本跑这段代码偏向锁默认关闭看到的可能直接就是无锁状态加轻量级锁的切换。这个实验特别适合面试前自己跑一遍比背概念理解深刻得多。4. 轻量级锁用CAS自旋换取性能4.1 轻量级锁的执行流程当第二个线程出现偏向锁撤销后锁会升级为轻量级锁。轻量级锁的核心是不立刻让线程进入内核态挂起而是在用户态用CAS操作尝试获取锁。如果获取不到线程就在原地自旋不断重试。具体流程是这样的线程A持有轻量级锁Mark Word里记录了指向线程A栈帧中锁记录的指针。线程B进来抢锁先把对象头Mark Word复制到自己的栈帧锁记录里Displaced Mark Word。线程B通过CAS尝试把Mark Word替换为指向自己栈帧的指针。如果成功线程B拿到锁如果失败说明锁还在被别人持有进入自旋。自旋是空转CPU做CAS重试直到持有锁的线程释放锁或者自旋次数超过阈值。轻量级锁的意义在于它假设临界区执行很快线程B自旋一小会儿就能等到锁被释放这样就避免了用户态到内核态的切换。4.2 自旋的代价和自适应自旋自旋不是免费的。一个线程自旋时CPU核心被它占着什么正事都不干。如果锁被持有时间很长自旋就是在浪费CPU。所以JVM引入了自适应自旋JVM会根据上一次同一个锁上的自旋结果动态调整自旋次数。简单说如果上次自旋成功等到了锁说明这个锁竞争窗口短下次可以多自旋几次如果上次自旋失败自旋满也没等到锁说明竞争激烈下次少自旋或者直接升级重量级锁。这个机制很聪明它让JVM对每把锁的竞争情况有了记忆类似于缓存算法里的历史反馈。4.3 轻量级锁和偏向锁的标志位区别看对象头的时候很多人容易混淆两者。记住一点就够了偏向锁biased_lock_flag 1lock_flag 01轻量级锁biased_lock_flag 0lock_flag 00重量级锁lock_flag 10GC标记lock_flag 11[\frac{11}{8}] 我实测下来用jol看锁标志位是最可靠的验证方法。原理背再多不如自己跑一遍Demo记得清楚。5. 重量级锁什么时候彻底沉下去5.1 从自旋到阻塞的临界点当锁竞争进一步加剧——比如多个线程长时间自旋仍然抢不到锁——JVM会把轻量级锁升级为重量级锁。升级后未抢到锁的线程会进入操作系统的互斥量阻塞队列由系统调度唤醒。此时线程不再消耗CPU自旋而是安静地在队列中等待。这里有个关键阈值问题究竟是什么触发了升级在JDK 6之后由于自适应自旋的存在没有一个固定的自旋次数阈值。整体逻辑是JVM根据历史的自旋结果动态决定是继续自旋还是升级。同时JVM参数-XX:UseSpinningJDK 6后默认开启和-XX:PreBlockSpin默认10在老版本JDK中控制自旋次数。新版JDK中这些参数多数已经失效或不再建议使用但理解它们仍然有助于理解JVM的设计意图。简化来说触发重量级锁的时机通常有两种同一把锁上自旋失败的次数积累到一定水平JVM认为竞争过于激烈。有线程在等待锁的过程中被中断或者等待超时。一旦升级为重量级锁后续所有线程的加锁操作都走操作系统互斥量成本显著提升。5.2 重量级锁的对象头发生了什么升级为重量级锁后对象头里的Mark Word不再存储线程ID或锁记录指针而是存储指向Monitor对象的指针。Monitor是JVM内部实现同步机制的组件它维护着一个Owner线程引用和两个等待队列EntryList和WaitSet。这里补一个很多人忽略的细节锁升级是不可逆的偏向锁和轻量级锁都是一次性过渡一旦升到重量级就不会降级回去直到锁对象被GC回收重新分配。这意味着如果你的业务热点代码频繁触发重量级锁性能只会越来越差不会自己恢复。所以线上排查时如果发现某把锁处于重量级状态不要指望它自己好转必须从代码层面解决竞争问题。5.3 如何判断当前锁处于什么状态我通常是两步走第一步用jstack抓线程栈。如果大量线程处于java.lang.Thread.State: BLOCKED (on object monitor)说明锁很可能已经升级到重量级线程在等待Monitor锁。第二步用jol查看锁对象头。这里需要注意jstack里显示的locked 0x...地址如果指向同一个对象且线程状态是BLOCKED再配合对象头的锁标志位确认基本就能下定论。我整理了一个表方便对照状态和表现锁状态对象头Mark Word简化线程表现性能特征无锁存储hashCode等自由进入临界区无同步开销偏向锁存线程ID单线程反复进入开销几乎为零轻量级锁存栈帧锁记录指针多线程CAS自旋用户态自旋无内核切换重量级锁存Monitor指针未抢到锁线程阻塞内核态调度开销最大实测过程中我见过很多锁状态误判。最典型的是有人看到synchronized修饰的静态方法想当然认为它一定会经历完整升级链路。实际上如果应用只用一个线程调用对象头可能一直停留在偏向锁状态JDK 8默认开启且过了延迟时间。如果JVM启动4秒内访问又或者线程数本身不多锁可能一直是无锁状态。6. 锁消除和锁粗化JVM背地里做的两件反升级事聊锁升级如果不提锁消除和锁粗化理解就是不完整的。这两个机制恰恰和升级方向相反——它们努力让锁不发生或者减少加锁次数。6.1 锁消除代码白拿了锁锁消除发生在JIT编译阶段基于逃逸分析Escape Analysis。如果JVM判断一个锁对象不会被其他线程访问也就是没有逃逸出当前线程那么JVM会直接把加锁代码优化掉等于你写了synchronized但实际运行时不加锁。一个典型场景public String concat(String a, String b) { StringBuffer sb new StringBuffer(); sb.append(a); sb.append(b); return sb.toString(); }StringBuffer内部方法都是synchronized的但因为sb是方法局部变量不会逃逸出去JVM就会把这里的加锁操作直接消除。所以别一看synchronized就觉得一定有锁竞争JIT优化可能会让你白担心一场。不过要注意逃逸分析依赖JIT的实时编译结果不同JDK版本、不同运行环境优化结论可能不同。我遇到过一种情况同一个方法在测试环境性能很好锁被消除上了生产环境因为条件分支变化导致对象逃逸性能立刻崩了。排查起来非常隐蔽。6.2 锁粗化减少加锁次数锁粗化是另一个方向的优化JVM发现相邻多个synchronized块加的是同一把锁与其每次进出都加锁解锁不如把范围扩大一次性加锁减少重复开销。比如public void doSomething() { synchronized (lock) { step1(); } synchronized (lock) { step2(); } synchronized (lock) { step3(); } }JIT可能合并成一个大synchronized块。这个优化对性能有帮助但它会延长持锁时间。如果合并后临界区里包含耗时操作反而可能增加竞争概率。所以锁粗化不是越粗越好它更多时候是JIT的自动行为应用层不必刻意追求。6.3 为什么理解这些对排查问题很重要线上排查并发问题最怕的就是对着代码猜性能。代码里synchronized很多但实际有没有发生锁竞争锁处于什么状态不深入JVM层面根本看不见。理解锁消除和锁粗化至少能帮你建立这样一个判断框架先确认锁对象是否会被多线程访问排除锁消除再确认临界区范围是否被JVM合并调整考虑锁粗化再判断实际竞争强度决定锁处于哪一级这个框架比盲目优化代码靠谱得多。7. 锁升级在AQS和并发工具类中的体现面试问到锁升级经常是先从synchronized入手然后延伸到ReentrantLock和AQS。很多初学者容易混淆AQS的锁和synchronized的锁升级是一回事吗答案不是但思想相通。7.1 AQS里没有偏向锁和轻量级锁AQSAbstractQueuedSynchronizer是ReentrantLock、Semaphore、CountDownLatch等并发工具的基础。AQS的核心是用一个volatile int state变量表示锁状态用CLH队列管理等待线程。它的等待机制基于LockSupport.park/unpark和重量级锁类似会让线程进入阻塞状态。但AQS没有偏向锁、轻量级锁的分级。原因很简单AQS从设计之初就是面向多线程竞争的工具不存在单线程独占或低竞争的优化目标。如果你了解底层ReentrantLock在竞争不激烈时确实比synchronized表现更稳定原因是它完全在用户态做状态判断只有真正拿不到锁才park线程。而synchronized在JDK 8的老版本里重量级锁的获取和释放都涉及内核调用。JDK 8之后的synchronized也在持续优化两者差距逐渐缩小。7.2 锁升级思想对AQS的启示虽然AQS没有锁升级但它的设计吸收了类似的哲学先尽量便宜地尝试获取锁失败的代价才是有成本的。ReentrantLock的nonfairTryAcquire先做一次CAS如果成功直接拿到锁失败才进入队列。这就是轻量尝试 重量等待的组合。和synchronized从自旋到阻塞的路径如出一辙。面试时如果被问到AQS和synchronized的区别从锁升级这个角度切入会显得很有深度两者在竞争不激烈时都倾向于便宜路径但在线程阻塞机制、锁释放顺序、可中断性、公平性支持上有本质差异。8. 实战排查一次锁升级引发的性能危机全解析回到文章开头说的线上事故我把整个排查链路完整还原一下。当时那个接口的核心代码不长我简化成下面的样子public class OrderService { private final OrderDao orderDao; private final CacheClient cacheClient; public synchronized void updateOrder(Order order) { // 1. 查缓存 Order cached cacheClient.get(order.getId()); // 2. 模拟耗时业务校验 slowValidate(order); // 3. 更新数据库 orderDao.update(order); // 4. 更新缓存 cacheClient.set(order.getId(), order); } private void slowValidate(Order order) { // 模拟耗时操作 try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }问题很明显synchronized修饰整个方法持锁时间包含了一次100ms的Thread.sleep()再加上数据库和缓存网络调用一把锁被持有几百毫秒。高并发下线程全部堆在锁上自旋自旋失败后升级到重量级锁然后全部阻塞接口RT从几十毫秒飙到数秒。排查链路是这样的8.1 第一步抓线程快照确认锁状态用jstack连续抓了三次间隔5秒看到大量BLOCKED线程锁对象都指向同一个地址。这个地址反查后发现是OrderService实例。再配合jstat和JFR查看锁定样本确认锁已经处于重量级状态——因为线程根本没在RUNNABLE状态自旋而是全部在BLOCKED。判断逻辑如果线程处于RUNNABLE状态但集中在某个方法上大概率在自旋等待轻量级锁如果线程处于BLOCKED状态等待monitor基本是重量级锁无疑。8.2 第二步缩小锁粒度修复姿势不唯一但我当时选择先缩小锁粒度把锁从整个方法挪到真正需要保护的关键步骤上。真正的临界区其实是更新数据库更新缓存这个组合操作前面的读缓存和校验完全不需要锁。改造后public void updateOrder(Order order) { Order cached cacheClient.get(order.getId()); slowValidate(order); synchronized (this) { orderDao.update(order); cacheClient.set(order.getId(), order); } }锁持有的时间从几百毫秒降到几十毫秒。效果立竿见影BLOCKED线程数量明显下降锁竞争不再触发重量级升级了。但这里还有一个坑slowValidate虽然不在锁内但它在高并发下会让请求线程堆积在synchronized块之前导致响应变慢的假象。所以后来我把耗时校验挪到了上游服务或者改成异步校验彻底解耦。8.3 第三步确认锁对象选择是否正确排查时还发现OrderService是个单例Bean所以this锁能保证全局互斥。如果它意外变成多例this锁就完全失效。当时为了保险我把锁对象改成一个专门的静态锁对象private static final Object UPDATE_LOCK new Object();为什么要单独定义锁对象一是避免和其他synchronized(this)代码互相干扰二是静态对象保证全局唯一不会因为Bean实例化方式变化产生多把锁。这是实打实踩过坑之后养成的习惯。8.4 第四步加监控观察锁竞争指数解决线上问题后我加了两个监控指标锁等待时间Lock Waited TimeBLOCKED线程数用JFR或者Micrometer都能采集。观察一段时间确认锁等待时间从高位回落到正常范围才算真正闭环。实测心得验证锁优化是否到位不能只看接口RT还要看线程状态分布和锁等待时间。RT好转可能是缓存命中率提升的假象但锁等待时间直接反映同步竞争程度这个指标骗不了人。9. 怎么写代码才能减少不必要的锁升级聊了这么多原理落到实际操作层面我总结几条写代码时真正有用的经验。这些不是网上抄来的是这些年排查并发问题攒下的。9.1 能用无锁数据结构就不用锁ConcurrentHashMap、AtomicInteger、LongAdder这些并发组件在绝大多数场景下性能优于synchronized保护的普通结构。它们的底层大量使用CAS属于无锁方案压根不给锁升级机会。比如统计请求量别用synchronized(map)去维护一个MapString, Integer做累加。用ConcurrentHashMap的compute或者直接用LongAdder更稳。9.2 锁粒度能小就别大很多人写synchronized喜欢直接锁方法省事。但省事的代价是持锁时间变长、竞争概率变大、锁升级概率升高。正确的姿势是先思考哪几行代码是真正需要互斥的只锁它们。如果临界区里包含IO、网络请求、睡眠等耗时操作再小的锁都可能变成性能瓶颈。这种情况优先考虑能不能用ReadWriteLock能不能把读操作拆出去能不能用乐观锁版本号CAS代替悲观锁9.3 别让高竞争代码自我恶化有些代码天生竞争激烈比如全局唯一ID生成、库存扣减。这类代码如果只用synchronized保护JVM很容易把锁升级到重量级性能越来越差。我常用的替代方案库存扣减数据库UPDATE ... WHERE stock 0配合乐观锁或者用Redis Lua脚本。唯一IDLongAdder、雪花算法、数据库序列。热点计数LongAdder分片累加。一句话如果分析出这把锁注定高竞争就别让synchronized承担了趁早换方案。9.4 别过度优化不需要优化的锁反过来也重要。如果一个synchronized保护的是一个几乎无竞争的代码块比如定时任务里偶尔执行的清理操作那维持synchronized完全没问题。不要为了避免锁升级强行替换成ReentrantLock或CAS增加复杂度还看不出性能收益。搞懂锁升级机制不是为了在每一处代码里都防着它而是为了在真正需要时知道怎么下手。10. jstack、JFR和压测验证我验证锁状态的完整三步最后分享一套我常用的验证流程。每次调优完锁代码我都会按这个流程跑一遍确认锁确实处于预期状态。10.1 第一步确定基线行为写一个能稳定复现竞争场景的压测Demo比如用多个线程循环调用synchronized方法统计吞吐量和RT。压测工具我用JMHJava Microbenchmark Harness它在Maven里引入即可dependency groupIdorg.openjdk.jmh/groupId artifactIdjmh-core/artifactId version1.37/version /dependency dependency groupIdorg.openjdk.jmh/groupId artifactIdjmh-generator-annprocess/artifactId version1.37/version /dependencyJMH能精确测量微基准比直接写循环计时可靠得多。它还会规避JIT预热、死代码消除等坑结果是可信的。10.2 第二步抓线程状态分布压测过程中用jstack多次抓取线程快照统计RUNNABLE线程占比包含自旋等待BLOCKED线程占比重量级锁等待WAITING线程占比其他等待理论上优化后的代码BLOCKED线程应显著减少。如果压测时BLOCKED仍然很多说明锁竞争问题没解决升级正是瓶颈。10.3 第三步用JFR看锁等待详情JDK 11内置的JFRJava Flight Recorder可以开启Lock Instances事件里面会详细记录到每把锁的等待时间、持有时间、竞争线程数。命令如下java -XX:StartFlightRecordingfilenamelock.jfr,duration60s,settingsprofile YourApplication然后用JMC打开lock.jfr定位到Lock相关事件就能看到哪把锁是热点、谁在持有、谁在等待。这一步对定位高竞争锁非常高效。我实测的感受是jstack适合快速判断JFR适合深度分析。组合使用基本覆盖所有锁排查场景。写完整篇我最想说的一点是锁升级不是面试题库里需要硬背的八股选项而是一套JVM根据竞争情况动态调整的决策系统。理解它背后的乐观尝试 - 逐步加码思路你再去看ReentrantLock、ConcurrentHashMap、甚至数据库事务隔离级别都能发现相似的设计哲学。下次面试官如果再问锁升级你大可以从一次线上故障讲起告诉他你是怎么用jstack抓线程、怎么用jol看对象头、怎么一步步把重量级锁降成轻量级竞争——这样的回答比背诵无锁 - 偏向锁 - 轻量级锁 - 重量级锁四个名词有说服力得多。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

大模型投研实战:AI辅助财报解读与风险扫描的落地方案 2026/10/1 5:43:48

大模型投研实战:AI辅助财报解读与风险扫描的落地方案

先说结论:大模型距离真正接管基金经理的位子,还差得远,但作为投研辅助工具,它已经在很多环节实打实地碾压了传统方法的效率。这篇文章不聊虚的概念,只讲我过去半年真实跑过的路线:怎么用大模型做财报解读、…

阅读更多 →
AI对话驱动Blender建模:智慧仓储数字孪生实战 2026/10/1 5:43:48

AI对话驱动Blender建模:智慧仓储数字孪生实战

前阵子做智慧仓储项目,我一直在找一个能让AI直接操作3D软件的方案。起因很实际:仓储数字孪生场景里,货架、库位、AGV路径这些模型如果全手动在Blender里搭,光阵列复制和层级整理就能耗掉一整天;但如果只让AI帮你写建模…

阅读更多 →
Jev模型接入指南:从密钥申请到Codex使用全流程 2026/10/1 5:43:48

Jev模型接入指南:从密钥申请到Codex使用全流程

最近这几天,我在技术群里反复被同一个词刷屏:Jev。先是有人贴了张截图,说自己把 Jev 接到了 Codex 里跑起来了,底下瞬间跟了十几条追问:“密钥从哪领”“模型 ID 填什么”“官网到底是不是那个”。我一开始没当回事&am…

阅读更多 →
盆栽检测数据集整理与YOLOv8训练实战:从COCO标签到mAP调优 2026/10/1 5:43:48

盆栽检测数据集整理与YOLOv8训练实战:从COCO标签到mAP调优

简介:植物盆栽检测数据集从COCO2017中提取而来,专门面向目标检测与YOLO系列模型训练场景,适合计算机视觉初学者、算法工程师及盆栽识别项目开发者使用。数据集中仅保留potted plant一个类别,共4624张真实场景图片,并配…

阅读更多 →
森林火灾图像分类数据集实战:从数据清洗到模型微调与可解释性验证 2026/10/1 5:43:48

森林火灾图像分类数据集实战:从数据清洗到模型微调与可解释性验证

简介:这份森林火灾图像分类数据集面向计算机视觉学习者与火灾监测方向的研究者,提供约13,000张已标注图像,按有火、无火两类组织,并划分训练集与测试集,可直接用于图像分类模型的训练与泛化能力验证。资源包共2000个文…

阅读更多 →
ASP.NET Framework中httpRuntime配置详解与Server is too busy故障排查 2026/10/1 5:43:41

ASP.NET Framework中httpRuntime配置详解与Server is too busy故障排查

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

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉