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.mzph.cn/news/139534.shtml

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

相关文章

不同性别人群的股骨颈骨密度随年龄的变化趋势

增龄是发生骨质疏松的危险因素。因此&#xff0c;中老年人需要积极防范骨质疏松&#xff0c;以免发生骨折等不良事件。 为了探究不同性别人群的股骨颈骨密度随年龄的变化趋势&#xff0c;首先创建一个df&#xff0c;变量有id&#xff08;编号&#xff09;、age&#xff08;年龄…

【算法】算法题-20231110

一、力口&#xff1a;506. 相对名次 简单 给你一个长度为 n 的整数数组 score &#xff0c;其中 score[i] 是第 i 位运动员在比赛中的得分。所有得分都 互不相同 。 运动员将根据得分 决定名次 &#xff0c;其中名次第 1 的运动员得分最高&#xff0c;名次第 2 的运动员得分第…

Git之分支与版本->课程目标及知识点的应用场景,分支的场景应用,标签的场景应用

1.课程目标及知识点的应用场景 Git分支和标签的命名规范 分支 dev/test/pre/pro(即master) dev:开发环境--windows (自己的电脑) test:测试环境--windows/linux (公司专门的测试电脑 pre:灰度环境(非常大的公司非常重要的项目) pro:正式环境 灰度环境与正式环境的服务器配置…

logback异步日志打印阻塞工作线程

前言 最新做项目&#xff0c;发现一些历史遗留问题&#xff0c;典型的是日志打印的配置问题&#xff0c;其实都是些简单问题&#xff0c;但是往往简单问题引起严重的事故&#xff0c;比如日志打印阻塞工作线程&#xff0c;以logback和log4j2为例。logback实际上是springboot的…

【Python】AppUI自动化—appium自动化开发环境部署、APP测试案例(17)上

文章目录 一.appium简介1.什么是appium2.appium 的工作原理3.APP类型4.APP页面布局 二,appium开发环境部署&#xff08;python环境&#xff09;1.下载安装环境1.1.下载安装所需环境1.2.Appium-desktop&#xff08; Appium-Server-GUI &#xff09;配置1.3.Appium-Inspector 配置…

计算机msvcp140.dll重新安装的四个解决方法,专门解决dll文件丢失问题的方法

在我多年的电脑使用经历中&#xff0c;曾经遇到过一个非常棘手的问题&#xff0c;那就是电脑提示找不到msvcp140.dll文件。这个问题让我苦恼了很久&#xff0c;但最终还是找到了解决方法。今天&#xff0c;我就来分享一下我解决这个问题的四种方法&#xff0c;希望对大家有所帮…

python爬虫怎么翻页

爬虫程序的代码实现如下&#xff1a; #include <iostream> #include <string> #include <curl/curl.h>int main() {CURL *curl;CURLcode res;std::string readBuffer;curl_global_init(CURL_GLOBAL_DEFAULT);curl curl_easy_init();if(curl) {curl_easy_se…

AI 绘画 | Stable Diffusion精确控制ControlNet扩展插件

ControlNet ControlNet是一个用于控制AI图像生成的插件&#xff0c;通过使用Conditional Generative Adversarial Networks&#xff08;条件生成对抗网络&#xff09;的技术来生成图像。它允许用户对生成的图像进行更精细的控制&#xff0c;从而在许多应用场景中非常有用&#…

每次重启完IDEA,application.properties文件里的中文变成?

出现这种情况&#xff0c;在IDEA打开Settings-->Editor-->File Encodings 然后&#xff0c;你需要将问号改为你需要的汉字。 重启IDEA&#xff0c;再次查看你的.properties文件就会发现再没有变成问号了

order by的注入与Insert ,update和delete注入

order by的注入 Insert &#xff0c;update和delete注入

大二第四周总结——用原生js封装一个分页器

用原生js封装一个分页器 起因&#xff1a;这次项目还是用原生的js来写的&#xff0c;我负责的是后台&#xff0c;分页是后台最常见的一个功能了&#xff0c;于是干脆封装一下,废话少说&#xff0c;直接上代码 这里是基本的样式 .pagination {display: flex;width: 600px;hei…

PCB知识补充

系列文章目录 文章目录 系列文章目录参考文献PCB知识互连线电阻过孔/铜箔电流能力铜箔载流能力过孔载流能力 热设计电磁兼容及部分要求 参考文献 [1]牛森,张敏娟,银子燕.高速PCB多板互联的电源完整性分析[J].单片机与嵌入式系统应用,2023,23(09). [2]陈之秀,刘洋,张涵舒等.高…

提莫的idea的bug是真滴多

问题1&#xff1a;maven reload功能失效 我复制了一段代码到我项目里&#xff0c;这段代码依赖hutool包&#xff0c;于是我用idea快速导入&#xff0c;自动导入的是hutool-all:5.8.4。后来我发现这段还是有个函数报错&#xff0c;需要导入更高版本的hutool包才行&#xff0c;于…

Django中简单的增删改查

用户列表展示 建立列表 views.py def userlist(request):return render(request,userlist.html) urls.py urlpatterns [path(admin/, admin.site.urls),path(userlist/, views.userlist), ]templates----userlist.html <!DOCTYPE html> <html lang"en">…

【Java】反射

1.什么是反射机制? Java 反射机制是在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类中的所有属性和方法&#xff0c;对于任意一个对象&#xff0c;都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 Java 的反射机制…

Django 密码管理:安全实践与技术深入

在 Web 应用的开发中&#xff0c;密码管理是保障用户安全的关键环节。Django 作为一个强大的 Python Web 框架&#xff0c;提供了一套全面的系统来处理密码的存储、验证和安全。本文将详细探讨 Django 中的密码管理机制&#xff0c;包括密码存储、密码验证、密码安全策略以及自…

《软件工程与计算》期末考试真题范例及答案

今天分享一套针对《软件工程与计算》这本书的真题案例&#xff0c;有关《软件工程与计算》23章内容的重点知识整理&#xff0c;已经总结在了博客专栏中&#xff0c;有需要的自行阅读&#xff1a; 《软件工程与计算》啃书总结https://blog.csdn.net/jsl123x/category_12468792.…

2023.11.8 信息学日志

2023.11.7 信息学日志 1. CF33C Wonderful Randomized Sum题目描述题目概况思路点拨 2. CF30C题目描述题目概况思路点拨 3. CF18E Flag 2题目描述题目概况思路点拨 1. CF33C Wonderful Randomized Sum 题目描述 https://www.luogu.com.cn/problem/CF33C 题目概况 来源&…

进程状态和优先级

文章目录 进程状态Linux中具体的进程状态僵尸进程孤儿进程 进程优先级 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xff0c; 通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 点击跳转到网站。 进程状态 进程在操…

[LeetCode] 6.N字形变换

一、题目描述 将一个给定字符串 s 根据给定的行数 numRows &#xff0c;以从上往下、从左到右进行 Z 字形排列。 比如输入字符串为 "PAYPALISHIRING" 行数为 3 时&#xff0c;排列如下&#xff1a; P A H N A P L S I I G Y I R之后&#xff0c;你的输出…