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;}
}
  1. 判断当前pedding 是否为空
  2. 如果为空,代表当前GC没有触发回收软、弱、虚引用
  3. 如果不为空,代表当前GC回收软、弱、虚引用,并且放入到pedding中
  4. 把pedding的值放入到ReferenceQueue队列中
  5. 业务维护的ReferenceQueue队列,从队列中poll值去做对应的处理。

所以ReferenceQueue队列是业务层面自己维护,传入到Reference中,GC回收软、弱、虚引用后会把当前Reference放入到ReferenceQueue队列中。业务层面再通过poll取到Reference做对应的处理(可以是处理伴随对象)

下面是WeakHashMap对ReferenceQueue的使用。

WeakHashMap的使用

至此,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);}}}}…………}
}
  1. 这里根据策略决定是否要清理所有的软引用(一般是内存资源极度不够的时候才会)
  2. 新生代或者老年代的垃圾回收器进行垃圾回收(这也对应了YGC和FullGC)
  3. 在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回收的细节代码,笔者有吧注释给上,并且当作黑盒就好。

  1. 创建好各种GC回收所需要扫描器
  2. 这些扫描器最终都有一个共同的任务,就是把存活对象复制到to区或者老年代
  3. GC Root的扫描
  4. 根据已有的GC Root做广度遍历,找出GC Root引用的对象作为下一批GC Root继续找引用,直到遍历完整个堆
  5. 软、弱、虚引的处理(这也是接下来的重点)

经过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;
}
  1. 软引用才有策略,根据策略决定是否回收对象,如果策略不让回收的对象,那么就需要从DiscoveredList链表中remove,并且保持存活,直到下次GC 再尝试回收
  2. 经过策略的决策后,活下来的对象继续做过滤,这次过滤是判断软、弱、虚对象引用的对象是否还活着,如果活着那就不能处理这个引用(所以用不好,随时可能内存泄漏),如果引用对象是存活的,那么就需要从DiscoveredList链表中remove,并且保持存活,直到下次GC 再尝试回收
  3. 经过第二步的过滤,活下来的对象还要根据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回收部分,所以笔者只能竭尽所能,源码注释+总结+画图来尽量描述明白~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/191305.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

8255 boot介绍及bring up经验分享

这篇文章会简单的介绍8255的启动流程&#xff0c;然后着重介绍8255在实际项目中新硬件上的bring up工作&#xff0c;可以给大家做些参考。 8255 boot介绍 下面这些信息来自文档&#xff1a;《QAM8255P IVI Boot and CoreBSP Architecture Technical Overview》 80-42847-11 R…

2023年开发语言和数据库排行

2023年开发语言和数据库排行 一、开发语言相关1. Python1.1 Python优点1.2 Python缺点1.3 Python应用领域 2. C 语言2.1 C 语言优点2.2 C 语言缺点2.3 C语言应用领域 3. Java3.1 Java 优点3.2 Java缺点3.3 Java应用场景 4. C4.1 C 优点4.2 C 缺点4.3 C 应用场景 5. C#5.1 C# 优…

模拟退火算法MATLAB实现

介绍 算法试图随着控制参数T的降低&#xff0c;使目标函 数值f&#xff08;内能E&#xff09;也逐渐降低&#xff0c;直至趋于全局最 小值&#xff08;退火中低温时的最低能量状态&#xff09;&#xff0c;算法 工作过程就像固体退火过程一样。 Metropolis准则——–以概率接受…

ROS话题(Topic)通信:通信模型、Hello World与拓展

文章目录 一、话题通讯模型二、Topic Hello World2.1 创建并初始化功能包2.2 确定Topic名称及消息格式2.3 实现发布者与订阅者&#xff08;C版&#xff09;2.4 实现发布者与订阅者&#xff08;Python版&#xff09;2.5 关于Topic Hello World的注意 拓展1&#xff1a;devel下其…

预览PDF并显示当前页数

这里写目录标题 步骤实例实例效果图 步骤 1.安装依赖 npm install --save vue-pdf2.在需要的页面&#xff0c;引入插件 import pdf from vue-pdf3.使用 单页pdf可以直接使用 <pdf :src"获取到的pdf地址"></pdf>多页pdf通过循环实现 html标签部分 &l…

Banana Pi BPI-M5 Boot Log 导出说明

准备&#xff1a; Preparation: 1、 一块bpi的开发板&#xff0c;一根ttl的串口线&#xff0c;以及一张烧录好镜像的sd/tf卡&#xff08;烧录到eMMC也行&#xff09;。 1. A BPI development board, a TTL serial port cable, and an SD/TF card with a burned image (it ca…

高并发架构设计(三大利器:缓存、限流和降级)

引言 高并发背景 互联网行业迅速发展&#xff0c;用户量剧增&#xff0c;系统面临巨大的并发请求压力。 软件系统有三个追求&#xff1a;高性能、高并发、高可用&#xff0c;俗称三高。三者既有区别也有联系&#xff0c;门门道道很多&#xff0c;全面讨论需要三天三夜&#…

Rust编程中的共享状态并发执行

1.共享状态并发 虽然消息传递是一个很好的处理并发的方式&#xff0c;但并不是唯一一个。另一种方式是让多个线程拥有相同的共享数据。在学习Go语言编程过程中大家应该听到过一句口号:"不要通过共享内存来通讯"。 在某种程度上&#xff0c;任何编程语言中的信道都类…

stm32超声波测距不准的解决方法(STM32 delay_us()产生1us)及stm32智能小车超声波测距代码(C语言版本)

首先要说明一下原理&#xff1a;使用stm32无法准确产生1us的时间&#xff0c;但是超声波测距一定要依赖时间&#xff0c;时间不准&#xff0c;距离一定不准&#xff0c;这是要肯定的&#xff0c;但是在不准确的情况下&#xff0c;要测量一个比较准确的时间&#xff0c;那么只能…

PHP中$_SERVER全局变量

在PHP中&#xff0c;$_SERVER 是一个全局数组变量&#xff0c;它包含了有关服务器和当前脚本的信息。$_SERVER 数组中的每个元素都是服务器环境的一个参数&#xff0c;如请求的方法、请求的 URI、客户端 IP 地址等。 PATH 系统环境变量的值&#xff0c;包含了多个目录的路径…

【Word自定义配置,超简单,图文并茂】自定义Word中的默认配置,比如标题大小与颜色(参考科研作图配色),正文字体等

▚ 01 自定义样式Styles中的默认标题模板 &#x1f4e2;自定义标题的显示效果&#xff0c;如下图所示&#xff1a; 1.1 自定义标题的模板Normal.dotm 1.1.1 选择所需修改的标题 新建一个空白Word文档&#xff0c;依次选择菜单栏的开始Home&#xff0c;样式Styles&#xff0c;…

Python生成随机数插件Faker的用法

目录 引言 一、Faker库的安装 二、Faker库的基本用法 1、导入Faker类 2、创建Faker对象 3、使用Faker对象生成随机数据 三、Faker库的高级用法 1、自定义数据生成规则 2、使用子模块进行特定领域的数据生成 3、与其他库结合使用 四、Faker库的应用场景 1、单元测试…

TCP与UDP

文章目录 TCP与UDP传输层的作用端口号UDPTCPUDP首部的格式TCP首部格式 TCP与UDP TCP/IP中有两个具有代表性的传输层协议&#xff0c;它们分别是TCP和UDP。TCP提供可靠的通信传输&#xff0c;而UDP则常被用于让广播和细节控制交给应用的通信传输。总之&#xff0c;根据通信的具…

MTK Camera2 的OPEN API流程认知

MTK的设计架构 再了解Camera的open api调用之前我们&#xff0c;需要了解Camera的架构&#xff0c;这样才能提高阅读代码的效率。 代码跟读&#xff1a; 在这个图中大致介绍了OpenCamera的具体调用&#xff0c;下面我们逐步分析Camera的open调用流程。 逐步分析 一、 我们抛…

如何使用PHPStudy本地快速搭建网站并实现远程访问

文章目录 [toc]使用工具1. 本地搭建web网站1.1 下载phpstudy后解压并安装1.2 打开默认站点&#xff0c;测试1.3 下载静态演示站点1.4 打开站点根目录1.5 复制演示站点到站网根目录1.6 在浏览器中&#xff0c;查看演示效果。 2. 将本地web网站发布到公网2.1 安装cpolar内网穿透2…

Flutter笔记:绘图示例 - 一个简单的(Canvas )时钟应用

Flutter笔记 绘图示例 - 一个简单的&#xff08;Canvas &#xff09;时钟应用 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_2855…

【算法|动态规划 | 区间dp No.2】AcWing 1068.环形石子合并

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【AcWing算法提高学习专栏】【手撕算法系列专栏】 &#x1f354;本专栏旨在提高自己算法能力的同时&#xff0c;记录一下自己的学习过程&a…

内衣洗衣机和手洗哪个干净?好用的内衣洗衣机推荐

在日常生活中&#xff0c;我们的衣服不可避免地会沾染上各种细菌、毛发和污渍&#xff0c;将它们与贴身衣物混合清洗&#xff0c;很容易发生交叉感染&#xff0c;而被感染后&#xff0c;贴身衣物也有可能导致我们人体引起皮肤病。这也是为什么大部分人都喜欢用手洗的原因&#…

Android WebView专题

WebView 专题 第一个WebView程序&#xff1a;加载远程网址 Layout添加WebView组件&#xff1b; <WebViewandroid:id"id/webView_first"android:layout_width"match_parent"android:layout_height"match_parent"/>初始化组件&#xff0c;加…

Socket网络编程(服务端和客户端代码示例)

本文主要讲解Socket网络编程。 首先介绍socket&#xff0c;包括TCP和UDP通信过程&#xff1b;然后介绍常用的函数&#xff1b;最后编写client-server例子&#xff0c;并进行测试。 文章目录 Socket介绍TCP通信过程服务器端通信过程&#xff1a;客户端通信过程&#xff1a; UDP通…