JVM源码剖析之软、弱、虚引用的处理细节
目录
写在前面:
源码剖析:
Java层面:
JVM层面:
使用危险点:
总结:
版本信息:
jdk版本:jdk8u40
垃圾回收器:Serial new/old
写在前面:
不同的垃圾回收器所对应的算法不一样,效率更不一样。在JDK8中默认为ParallelScavenge new/old。而笔者写文时使用Serial new/old,两者算法一致,只不过ParallelScavenge new/old发挥了多线程的优势,所以在算法细节上大同小异。
对于大大大大大大部分Java业务场景来说都是强引用,基本上不会使用到软、弱、虚引用。而在JDK1.2推出的软、弱、虚引用大部分出现场景都是在缓存中,在JDK类库ThreadLocal、WeakHashMap。框架:Mybatis、Netty、以及各种缓存框架等等。至于为什么要用在缓存中呢,也很好理解,因为这些引用实际上可有可无,完美契合于缓存,在有的时候给系统加速,在系统内存紧张的时候清除缓存给核心业务使用。
源码剖析:
这篇文章的篇幅会比较长,也不容易理解。因为对于软、弱、虚引用处理细节体现在Java层面和JVM层面,恰好JVM层面又与GC垃圾回收细节强关联,所以笔者只能竭尽所能~
Java层面:
在Java层面,就不得不补充一些前置知识,以及Java层面如何处理这些引用。

上图是软、弱、虚引用最基本的表示,这里需要区分2个不同的对象,一个是软、弱、虚对象,一个是软、弱、虚引用的对象。


所以下文需要介绍软、弱、虚对象的回收机制和区分具体的使用场景(相信大家八股文多多少少背过,这里跟八股文会有一点点出入)
软:当系统资源紧张但是又没那么那么紧张的时候根据最近最少使用回收软引用(LRU算法),当系统资源非常非常紧张的时候直接全部回收。可以携带引用对象,也可以使用ReferenceQueue去处理伴随对象
弱:只要发生GC就会回收。可以携带引用对象,也可以使用ReferenceQueue去处理伴随对象
虚:只要发生GC就会回收。不能携带引用对象。只能使用ReferenceQueue去处理伴随对象
上文有介绍软、弱、虚对象的回收机制,这里有提到ReferenceQueue队列,所以下文开始介绍Java层面如何使用ReferenceQueue做回收。
// java.lang.ref.Reference类中静态方法
static {// 创建一个ReferenceHandler线程。Thread handler = new ReferenceHandler(tg, "Reference Handler");handler.setPriority(Thread.MAX_PRIORITY); handler.setDaemon(true); handler.start();
}
在java.lang.ref.Reference类中静态方法中创建了一个ReferenceHandler线程。所以接下来看线程的执行体。
public void run() {while (true) {tryHandlePending(true);}
}static boolean tryHandlePending(boolean waitForNotify) {Reference<Object> r;…………synchronized (lock) {if (pending != null) {// 如果pedding不为null,那么就代表GC回收到了软、弱、虚引用r = pending;pending = r.discovered;r.discovered = null;} else {if (waitForNotify) {// 当还没产生pending链表的时候(也即没有触发GC回收软、弱、虚引用)// 当前线程直接去阻塞,等待被JVM唤醒。lock.wait();}return waitForNotify;}}…………// 把GC回收到了软、弱、虚引用放入到对应的ReferenceQueue中。// 等待业务自己去处理ReferenceQueue队列。ReferenceQueue<? super Object> q = r.queue;if (q != ReferenceQueue.NULL) q.enqueue(r);return true;
}boolean enqueue(Reference<? extends T> r) { synchronized (lock) {ReferenceQueue<?> queue = r.queue;if ((queue == NULL) || (queue == ENQUEUED)) {return false;}// 头插法r.queue = ENQUEUED;r.next = (head == null) ? r : head;head = r;queueLength++;lock.notifyAll();return true;}
}
- 判断当前pedding 是否为空
- 如果为空,代表当前GC没有触发回收软、弱、虚引用
- 如果不为空,代表当前GC回收软、弱、虚引用,并且放入到pedding中
- 把pedding的值放入到ReferenceQueue队列中
- 业务维护的ReferenceQueue队列,从队列中poll值去做对应的处理。
所以ReferenceQueue队列是业务层面自己维护,传入到Reference中,GC回收软、弱、虚引用后会把当前Reference放入到ReferenceQueue队列中。业务层面再通过poll取到Reference做对应的处理(可以是处理伴随对象)
下面是WeakHashMap对ReferenceQueue的使用。

至此,Java层面的处理已经看完,接下来我们需要明白JVM是如何GC处理软、弱、虚引用,并且放入到pedding中,这样就全部闭环~
JVM层面:
具体的GC回收过程本文肯定是忽略,当作黑盒即可~
/hotspot/src/share/vm/memory/genCollectedHeap.cpp 文件中
// /hotspot/src/share/vm/memory/genCollectedHeap.cpp
// 这里是GC垃圾回收的过程
void GenCollectedHeap::do_collection(bool full,bool clear_all_soft_refs,size_t size,bool is_tlab,int max_level) {…………// 是否需要清理所有的软引用const bool do_clear_all_soft_refs = clear_all_soft_refs ||collector_policy()->should_clear_all_soft_refs();{…………for (int i = starting_level; i <= max_level; i++) {if (_gens[i]->should_collect(full, size, is_tlab)) {{// 从这里可以看出,不同带都有一个引用的处理器。ReferenceProcessor* rp = _gens[i]->ref_processor();rp->enable_discovery(true /*verify_disabled*/, true /*verify_no_refs*/);// 改变回收策略rp->setup_policy(do_clear_all_soft_refs); // 不同代进行垃圾回收。_gens[i]->collect(full, do_clear_all_soft_refs, size, is_tlab);// gc回收后,把回收的软、弱、虚引用赋值给pedding,交给Java层面处理// 这里也对应到上下文了。if (!rp->enqueuing_is_done()) {rp->enqueue_discovered_references();} else { rp->set_enqueuing_is_done(false);}}}}…………}
}
- 这里根据策略决定是否要清理所有的软引用(一般是内存资源极度不够的时候才会)
- 新生代或者老年代的垃圾回收器进行垃圾回收(这也对应了YGC和FullGC)
- 在GC回收后把回收到的软、弱、虚引用赋值给pedding,交给Java层面处理
所以接下来需要看到老年代的垃圾回收器进行垃圾回收的时候如何处理的软、弱、虚引用。
/hotspot/src/share/vm/memory/defNewGeneration.cpp 文件中
// 新生代的垃圾回收
void DefNewGeneration::collect(bool full,bool clear_all_soft_refs,size_t size,bool is_tlab) {…………// 用于扫描软、弱、虚引用是否存活。ScanWeakRefClosure scan_weak_ref(this);// 对象扫描器,用于GC root的复制FastScanClosure fsc_with_no_gc_barrier(this, false);FastScanClosure fsc_with_gc_barrier(this, true);// Klass的GC root扫描。KlassScanClosure klass_scan_closure(&fsc_with_no_gc_barrier,gch->rem_set()->klass_rem_set());// GC Root广度搜索的扫描器// 也就是找到GC Root的引用作为下一批GC Root,直到找完所有的存活对象。FastEvacuateFollowersClosure evacuate_followers(gch, _level, this,&fsc_with_no_gc_barrier,&fsc_with_gc_barrier);// 寻找根GC Root。// 因为是新生代的算法,所以这里会把根GC Root复制到to区或者是老年代。gch->gen_process_strong_roots(_level,true, // Process younger gens, if any,// as strong roots.true, // activate StrongRootsScopetrue, // is scavengingSharedHeap::ScanningOption(so),&fsc_with_no_gc_barrier,true, // walk *all* scavengable nmethods&fsc_with_gc_barrier,&klass_scan_closure);// 根据GC Root找出GC Root所有的引用// 因为这里是处理引用,所以这里会处理软、弱、虚等等引用。evacuate_followers.do_void();// 用于处理引用对象的存活。FastKeepAliveClosure keep_alive(this, &scan_weak_ref);ReferenceProcessor* rp = ref_processor();// 根据clear_all_soft_refs这个bool字段决定是否清理全部的软引用。rp->setup_policy(clear_all_soft_refs);// 具体的处理细节。const ReferenceProcessorStats& stats =rp->process_discovered_references(&is_alive, &keep_alive, &evacuate_followers,NULL, _gc_timer);…………
}
以上是YGC时,新生代的回收,不管是Full GC还是YGC都会对软、弱、虚引用做处理,所以挑选YGC来做分析(因为YGC简单一些,但是对于软、弱、虚引用做处理都是一样的)
由于处理软、弱、虚引用一定会和GC回收细节强关联,所以很多是GC回收的细节代码,笔者有吧注释给上,并且当作黑盒就好。
- 创建好各种GC回收所需要扫描器
- 这些扫描器最终都有一个共同的任务,就是把存活对象复制到to区或者老年代
- GC Root的扫描
- 根据已有的GC Root做广度遍历,找出GC Root引用的对象作为下一批GC Root继续找引用,直到遍历完整个堆
- 软、弱、虚引的处理(这也是接下来的重点)
经过GC Root全部查找后,Java堆的对象排布可能是这样
注意,这里的软、弱、虚对象和软、弱、虚对象所引用对象是有区别的,复制算法只会把软、弱、虚对象做复制,软、弱、虚对象引用的对象要后续再做处理。
在看ReferenceProcessor类process_discovered_references方法之前,需要介绍一下ReferenceProcessor类。
/hotspot/src/share/vm/memory/referenceProcessor.hpp 文件中
class ReferenceProcessor : public CHeapObj<mtGC> {protected:static ReferencePolicy* _default_soft_ref_policy;static ReferencePolicy* _always_clear_soft_ref_policy;ReferencePolicy* _current_soft_ref_policy;uint _num_q; uint _max_num_q; // 作为基地址。DiscoveredList* _discovered_refs; DiscoveredList* _discoveredSoftRefs; // 基于基地址的第一部分DiscoveredList* _discoveredWeakRefs; // 基于基地址的第二部分DiscoveredList* _discoveredFinalRefs; // 基于基地址的第三部分DiscoveredList* _discoveredPhantomRefs; // 基于基地址的第四部分
}
可以很清楚的看到,这里有策略对象和几个DiscoveredList链表。链表中是保存了被处理的软、弱、虚的Java对象。并且在遍历完所有的GC Root后,这里会把软、弱、虚的Java对象行程如下的链表。
所以接下来,看到process_discovered_references方法具体处理细节。
/hotspot/src/share/vm/memory/referenceProcessor.cpp 文件中
ReferenceProcessorStats ReferenceProcessor::process_discovered_references(BoolObjectClosure* is_alive,OopClosure* keep_alive,VoidClosure* complete_gc,AbstractRefProcTaskExecutor* task_executor,GCTimer* gc_timer) {_soft_ref_timestamp_clock = java_lang_ref_SoftReference::clock();// 软引用的处理size_t soft_count = 0;{GCTraceTime tt("SoftReference", trace_time, false, gc_timer);soft_count =process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true,is_alive, keep_alive, complete_gc, task_executor);}// 修改时间戳。// 时间戳用于LRU算法,寻找最近最少使用的软引用。update_soft_ref_master_clock();// 弱引用的处理size_t weak_count = 0;{GCTraceTime tt("WeakReference", trace_time, false, gc_timer);weak_count =process_discovered_reflist(_discoveredWeakRefs, NULL, true,is_alive, keep_alive, complete_gc, task_executor);}// 最终引用处理,这个一般是用于收尾工作size_t final_count = 0;{GCTraceTime tt("FinalReference", trace_time, false, gc_timer);final_count =process_discovered_reflist(_discoveredFinalRefs, NULL, false,is_alive, keep_alive, complete_gc, task_executor);}// 虚引用处理size_t phantom_count = 0;{GCTraceTime tt("PhantomReference", trace_time, false, gc_timer);phantom_count =process_discovered_reflist(_discoveredPhantomRefs, NULL, false,is_alive, keep_alive, complete_gc, task_executor);}return ReferenceProcessorStats(soft_count, weak_count, final_count, phantom_count);
}
可以看到不管是软、弱、虚引用的处理都是调用process_discovered_reflist方法。
/hotspot/src/share/vm/memory/referenceProcessor.cpp 文件中
size_t
ReferenceProcessor::process_discovered_reflist(DiscoveredList refs_lists[],ReferencePolicy* policy,bool clear_referent,BoolObjectClosure* is_alive,OopClosure* keep_alive,VoidClosure* complete_gc,AbstractRefProcTaskExecutor* task_executor)
{// 根据策略决定是否能处理引用。// 策略只有软引用才有。// 弱、虚引用是不配有策略的,弱、虚引用只要发生GC久回收if (policy != NULL) {for (uint i = 0; i < _max_num_q; i++) {process_phase1(refs_lists[i], policy,is_alive, keep_alive, complete_gc);}} // 遍历剩下的队列,继续做过滤操作// 这个过滤是判断软、弱、虚对象引用的对象是否还活着,如果活着那就不能处理这个引用。for (uint i = 0; i < _max_num_q; i++) {process_phase2(refs_lists[i], is_alive, keep_alive, complete_gc);}// 根据clear_referent变量决定最终是否处理引用。for (uint i = 0; i < _max_num_q; i++) {process_phase3(refs_lists[i], clear_referent,is_alive, keep_alive, complete_gc);}return total_list_count;
}
- 软引用才有策略,根据策略决定是否回收对象,如果策略不让回收的对象,那么就需要从DiscoveredList链表中remove,并且保持存活,直到下次GC 再尝试回收
- 经过策略的决策后,活下来的对象继续做过滤,这次过滤是判断软、弱、虚对象引用的对象是否还活着,如果活着那就不能处理这个引用(所以用不好,随时可能内存泄漏),如果引用对象是存活的,那么就需要从DiscoveredList链表中remove,并且保持存活,直到下次GC 再尝试回收
- 经过第二步的过滤,活下来的对象还要根据clear_referent变量决定最终是否处理引用对象。这一步只有虚引用才不能处理引用(因为虚对象不能引用对象),如果clear_reference为false,那么就需要从DiscoveredList链表中remove,并且保持存活,直到下次GC 再尝试回收,但是虚引用为false也没关系,因为他指向本来就是null。
所以接下来可以看一下软引用的策略处理。
这里就比较简单了,要不永远回收、要不永远不回收,要不根据LRU算法得到最近最少使用的软引用,优先回收没用的~
所以在本文最上面写到:软引用,在内存紧张的时候但是不是非常紧张的时候会回收最少使用的(根据LRU算法),在内存非常非常紧张的时候策略直接是AlwaysCLearPolicy策略了,就回收所有软引用~
当经过层层过滤后,最终存活的软、弱、虚对象就存在不同DiscoveredList链表中。我们在Java层面是从pedding获取到对象,所以这边还需要把不同的DiscoveredList链表设置到pedding中。
所以接下来回到GenCollectedHeap::do_collection方法,看到enqueue_discovered_references方法
/hotspot/src/share/vm/memory/referenceProcessor.cpp 文件中
bool ReferenceProcessor::enqueue_discovered_references(AbstractRefProcTaskExecutor* task_executor) {return enqueue_discovered_ref_helper<oop>(this, task_executor);
}template <class T>
bool enqueue_discovered_ref_helper(ReferenceProcessor* ref,AbstractRefProcTaskExecutor* task_executor) {// 拿到Reference类中的pedding变量的地址,因为pending是一个静态变量,所以从mirror拿。T* pending_list_addr = (T*)java_lang_ref_Reference::pending_list_addr();// 把链表链到pedding上ref->enqueue_discovered_reflists((HeapWord*)pending_list_addr, task_executor);return old_pending_list_value != *pending_list_addr;
}void ReferenceProcessor::enqueue_discovered_reflists(HeapWord* pending_list_addr,AbstractRefProcTaskExecutor* task_executor) {// 串行化遍历4个链表。for (uint i = 0; i < _max_num_q * number_of_subclasses_of_ref(); i++) {// 只需要把每个链表的头部链到pending就行了。enqueue_discovered_reflist(_discovered_refs[i], pending_list_addr);_discovered_refs[i].set_head(NULL);_discovered_refs[i].set_length(0);}
}
这里就是把经过层层筛选的软、弱、虚链表中的对象链到Reference类中pedding字段上。最终交给Java层面的ReferenceHandler线程去处理。
使用危险点:
上面我们把所有的处理细节都分析完了,所以接下来回忆到一处细节点。
/hotspot/src/share/vm/memory/referenceProcessor.cpp 文件中,process_discovered_reflist方法,这个方法是做过滤处理,在process_phase2这个方法做过滤的时候,会判断软、弱、虚对象的引用对象是否存活,如果存活的情况下是不能做回收的。所以这里很容易发生内存泄露,看到如下的Java代码。
public class ReferenceTest {public static void main(String[] args) {WeakHashMap<Object,User> weakHashMap = new WeakHashMap<>();Object o1 = new Object();weakHashMap.put(o1,new User("lihayyds")); // 只要o1不释放这就是内存泄露。weakHashMap.put("1",new User("lihayyds")); // "1"是JVM字符串常量池指向的,所以这也是一个内存泄露byte[] bytes1 = new byte[1024 * 1024 * 1024];byte[] bytes2 = new byte[1024 * 1024 * 1024];byte[] bytes3 = new byte[1024 * 1024 * 1024];byte[] bytes4 = new byte[1024 * 1024 * 1024];byte[] bytes5 = new byte[1024 * 1024 * 1024];// 手动Full GC。System.gc();// Reference Queue 处理后的大小,因为在size里面会去处理System.out.println("Reference Queue 处理后的大小为:"+weakHashMap.size());}
}class User{String name;public User(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
结果如上图所示,发生GC后,弱引用根本没有回收,就是因为弱引用指向的对象被其他地方强引用,导致于在做筛选的过程中,被筛选出去了,不能去回收它。那么如果外部的这个强引用不释放,那么这个弱引用引用的对象和弱引用对象永远无法回收,从而无法达到弱引用的优势,变相地说,这就是内存泄漏~
那么下面改进一下Java代码。
public class ReferenceTest {public static void main(String[] args) {WeakHashMap<Object,User> weakHashMap = new WeakHashMap<>();// 这里直接不让外部引用这个Object对象weakHashMap.put(new Object(),new User("lihayyds")); weakHashMap.put(new Object(),new User("lihayyds")); byte[] bytes1 = new byte[1024 * 1024 * 1024];byte[] bytes2 = new byte[1024 * 1024 * 1024];byte[] bytes3 = new byte[1024 * 1024 * 1024];byte[] bytes4 = new byte[1024 * 1024 * 1024];byte[] bytes5 = new byte[1024 * 1024 * 1024];// 手动Full GC。System.gc();// Reference Queue 处理后的大小,因为在size里面会去处理System.out.println("Reference Queue 处理后的大小为:"+weakHashMap.size());}
}class User{String name;public User(String name) {this.name = name;}public String getName() {return name;}public void setName(String name) {this.name = name;}
}
如上图所示,弱引用引用的对象不让外部有强引用后,直接正常了,发生GC就回收了~
总结:
因为流程特别大,强关联GC回收部分,所以笔者只能竭尽所能,源码注释+总结+画图来尽量描述明白~
相关文章:

JVM源码剖析之软、弱、虚引用的处理细节
目录 写在前面: 源码剖析: Java层面: JVM层面: 使用危险点: 总结: 版本信息: jdk版本:jdk8u40 垃圾回收器:Serial new/old 写在前面: 不同的垃圾回收…...

Linux服务器上搭建JupyterNotebook教程
搭建需知 1.确保是Linux服务器; 2.已经在linux服务器上安装好anaconda3; 搭建教程 请按照顺序依次执行下面的命令: 1、安装Jupyter Notebook 执行以下命令,安装jupyter notebook conda install jupyter【注】 如果anaconda3…...
记录bug1
项目场景: 提示:这里简述项目相关背景: 例如:项目场景:示例:通过蓝牙芯片(HC-05)与手机 APP 通信,每隔 5s 传输一批传感器数据(不是很大) 问题描述 提示:这里描述项目中遇到的问题࿱…...

【MySQL】rank()、row_number()、dense_rank()用法详解
建表测试 测试表数据:test1 CREATE DATABASE /*!32312 IF NOT EXISTS*/db_test /*!40100 DEFAULT CHARACTER SET utf8 */; USE db_test; /*Table structure for table test1 */ DROP TABLE IF EXISTS test1; CREATE TABLE test1 ( id int(10) NOT NULL, score i…...

NFT合约部署
部署合约: 1.web3 NFT合约部署工具 https://remix.ethereum.org/ 2.tron NFT合约部署工具 https://www.tronide.io/ 3.部署 web3 ERC721代码: // SPDX-License-Identifier: MIT pragma solidity ^0.8.2;import "openzeppelin/contracts/token/ERC7…...
【C++】从入门到精通第三弹——友元函数与静态类成员
这里写目录标题 静态类成员友元友元方法 静态类成员 类成员一般都需要通过对象来访问,不可以通过类名直接访问,但是当我们将类成员定义为静态类成员,则允许使用类名直接访问。 静态类成员是在类成员前定义static关键字。 1 #include<iost…...
acwing算法基础之搜索与图论--floyd算法
目录 1 基础知识2 模板3 工程化 1 基础知识 floyd算法的时间复杂度为O(n^3),它用来解决多源最短路问题。它的原理是基于动态规划。 floyd算法的关键步骤: k从1到n。i从1到n。j从1到n,d[i][j] min(d[i][j], d[i][k] d[k][j])。经过上述三…...

Zabbix监控SSL证书有效期
一、介绍 由于业务需要,最近通过 Let’s Encrypt 申请了一些 SSL 证书,而证书有效期为 3 个月,需要在证书到期之前 renew。由于域名较多经常忘记 renew,导致证书过期,因此想通过 Zabbix 的方式监控证书的到期时间&…...
Arduino OneButton按键处理库实现单击/双击/长按功能
Arduino OneButton按键处理库实现单击/双击长按功能 ✨在Arduino开发平台下,按键的单击/双击/长按功能,在通过使用OneButton库,很容易就可以轻松实现。这就是支持C/C模块化设计的好处,避免重复性开发的工作。 🔖本文将…...
day52 django的下载与安装
课程的大致安排 大概两周的时间都是围绕着Django框架的学习,包括后续要学习的drf、Redis、celery、es等技术栈都是围绕Django展开的,因此、要求所有的同学必须认证学习了 市场中所有使用Python开发的web项目,Django框架占有率达到90%以上 …...

WebGL智慧城市软件项目
WebGL开发智慧城市项目时,需要考虑多个方面,包括技术、隐私、安全和可持续性。以下是一些需要注意的关键问题,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。 1.隐私和数据安全…...
VMware重装后没有虚拟网卡
我重装VMware之后网络适配器里面没有虚拟网卡,找了CSDN上很多博主说的,都没用。 最终去看了b站的up视频就成功了。 原因是因为第一次安装后卸载不干净,软件在电脑上的注册表没有删掉。 要用下面两个软件清理一下残留文件,这两个…...
软件安全基础
传参基础 64位汇编传参,当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。 当参数为7个以上时, 前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和32位汇编一样。 我们这边要利用wr…...

探索项目管理软件的多重用途和益处
项目管理软件俨然成了当下项目管理话题中的热门词条,作为一个辅助性管理工具,项目管理软件有什么用?真的值得购入吗? 什么是项目管理软件 顾名思义,项目管理软件就是指在项目管理过程使用的各种软件工具。项目管理软件…...

Arduino ESP8266使用AliyunIoTSDK.h连接阿里云物联网平台
文章目录 1、AliyunIoTSDK简介2、相关库安装3、阿里云创建产品,订阅发布4、对开源的Arduino ESP8266源代码修改5、使用阿里云点亮一个LED灯6、设备向阿里云上传温度数据7、项目源码 1、AliyunIoTSDK简介 AliyunIoTSDK是arduino的一个库,可以在arduino的…...

【车载开发系列】AutoSar中的CANTP
【车载开发系列】AutoSar中的CANTP 【车载开发系列】AutoSar中的CANTP 【车载开发系列】AutoSar中的CANTP一. CANTP相关术语二. CANTP相关概念1)单帧:SF(Single Frame)2)首帧:FF(First Frame)3)连续帧CF(Consecutive F…...
JUL日志
文章目录 JUL日志JUL日志讲解Properties配置文件编写日志配置文件Lombok快速开启日志Mybatis日志系统 JUL日志 如果使用System.out.println来打印信息,项目中存在大量的控制台输出语句,会显得很凌乱,而且日志的粒度是不够细的,假…...

ZZ308 物联网应用与服务赛题第G套
2023年全国职业院校技能大赛 中职组 物联网应用与服务 任 务 书 (G卷) 赛位号:______________ 竞赛须知 一、注意事项 1.检查硬件设备、电脑设备是否正常。检查竞赛所需的各项设备、软件和竞赛材料等; 2.竞赛任务中所使用…...
如何使用 vcpkg 编译Google-V8脚本引擎(ECMA/JavaScript)?
WIN32K下一键杀死所有同名进程命令行:taskkill /F /IM chrome.exe ############################## 1、准备 Visual Studio 2019 2、准备 Visual Studio 2022 3、安装 VC、MSBuild 编译集成环境 4、安装 Python 3.x,早期V8发行版本编译安装 2.x 5、…...

系列二十二、idea Live Templates
一、idea Live Templates 1.1、Java Group 1.1.1、fast fast 快速在类上添加注解Data AllArgsConstructor NoArgsConstructor Accessors(chain true) ToString(callSuper true) 1.1.2、getThreadName getThreadName快速获取当前线程的名字Thread.currentThread().getName…...

业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...

Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
腾讯云V3签名
想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...

接口自动化测试:HttpRunner基础
相关文档 HttpRunner V3.x中文文档 HttpRunner 用户指南 使用HttpRunner 3.x实现接口自动化测试 HttpRunner介绍 HttpRunner 是一个开源的 API 测试工具,支持 HTTP(S)/HTTP2/WebSocket/RPC 等网络协议,涵盖接口测试、性能测试、数字体验监测等测试类型…...

打手机检测算法AI智能分析网关V4守护公共/工业/医疗等多场景安全应用
一、方案背景 在现代生产与生活场景中,如工厂高危作业区、医院手术室、公共场景等,人员违规打手机的行为潜藏着巨大风险。传统依靠人工巡查的监管方式,存在效率低、覆盖面不足、判断主观性强等问题,难以满足对人员打手机行为精…...
「全栈技术解析」推客小程序系统开发:从架构设计到裂变增长的完整解决方案
在移动互联网营销竞争白热化的当下,推客小程序系统凭借其裂变传播、精准营销等特性,成为企业抢占市场的利器。本文将深度解析推客小程序系统开发的核心技术与实现路径,助力开发者打造具有市场竞争力的营销工具。 一、系统核心功能架构&…...
MySQL 主从同步异常处理
阅读原文:https://www.xiaozaoshu.top/articles/mysql-m-s-update-pk MySQL 做双主,遇到的这个错误: Could not execute Update_rows event on table ... Error_code: 1032是 MySQL 主从复制时的经典错误之一,通常表示ÿ…...
【安全篇】金刚不坏之身:整合 Spring Security + JWT 实现无状态认证与授权
摘要 本文是《Spring Boot 实战派》系列的第四篇。我们将直面所有 Web 应用都无法回避的核心问题:安全。文章将详细阐述认证(Authentication) 与授权(Authorization的核心概念,对比传统 Session-Cookie 与现代 JWT(JS…...
规则与人性的天平——由高考迟到事件引发的思考
当那位身着校服的考生在考场关闭1分钟后狂奔而至,他涨红的脸上写满绝望。铁门内秒针划过的弧度,成为改变人生的残酷抛物线。家长声嘶力竭的哀求与考务人员机械的"这是规定",构成当代中国教育最尖锐的隐喻。 一、刚性规则的必要性 …...