欢迎来到星空体育平台官网入口

电脑公司专业版 深度优化版 XP旗舰版 XP专业版64位 Win10正式版
当前位置: 主页 > IT资讯 > 网络

在面试时,常常会被问及Java中引用类型的原理,请你深入探讨

时间:2024-06-24    来源:zoc7RcITctunhMtq7EzA    人气:

1. 唯一性索引的选择

唯一性索引的值是独一无二的,可通过该索引快速确定特定记录。 举例来说,学生表中的学号是一个具有独一无二特性的字段。建立唯一性索引可以快速确定特定学生的信息。使用姓名作为标识可能会导致重名问题,降低检索速度。对于经常需要进行排序、分组和关联操作的字段,建立索引是很重要的。这些字段经常用于ORDER BY、GROUP BY、DISTINCT和UNION等操作,如果不进行索引,排序操作会耗费大量时间。建立索引可以有效地避免进行排序操作。对于经常作为查询条件的字段,建立索引是很重要的。如果某个字段经常被用来查询条件,将会影响整个表的查询速度。因此,在该字段上创建索引,可以加快整个表的查询速度。

4.对索引数量进行限制。索引不是越多越好。每一个索引都会占用一定的磁盘空间,索引数量越多,所需的磁盘空间也会相应增加。在修改表结构时重新构建和更新索引会很繁琐。索引越多,更新表的速度会变得更慢。

5.优先使用数据较少的索引

,因为当索引值很长时,查询速度会受到影响。举个例子,对一个CHAR(100)字段进行全文检索显然比对CHAR(10)字段进行全文检索需要更多的时间。6. 请优先使用字段前缀进行索引,如果字段值较长,则最好使用值的前缀来建立索引。比如说,对于TEXT和BLOG这两种字段进行全文搜索会非常耗费时间。提高检索速度的一个方法是只检索字段的前几个字符。

7. 当数据表中的数据被大量修改或者数据的使用方式发生变化时,原先未被频繁使用或者已经不再需要的索引可以被删除。定期查找并删除这些索引,以减少其对更新操作的影响,这是数据库管理员的责任。

8.最左前缀匹配规则是非常重要的。MySQL会持续向右匹配,直到遇到范围查询符号(例如>、<、between、like),然后停止匹配。例如,如果有条件 a=1 and b=2 c>'3 and d=4',若建立(a,b,c,d)顺序的索引,d将无法使用索引。但若建立(a,b,d,c)的索引,则所有条件都可以使用索引,且a、b和d的顺序可以随意调整。9。x和n的值可以互换。假设a = 1,b = 2,c = 3,在建立(a,b,c)索引时,可以任意顺序。MySQL的查询优化器会自动优化成索引可以识别的形式。在选择索引时,尽量选择区分度高的列。

区分度的计算方法是count(distinct col)/count(*),它表示字段中不重复值的比例。比例越大,我们需要扫描的记录数就越少。唯一键的区分度为1,而某些字段,比如状态和性别,在大数据场景中可能区分度接近0。可能会有人问,这个比例的经验值是多少呢?由于应用背景不同,这个数值也难以明确,通常我们要求参与连接的字段值至少为0.1,也就是平均每扫描10条记录。对于

11来说,索引列不能参与计算,保持该列的“纯净”。当以from_unixtime(create_time) = '2014-05-29'条件进行检索时,索引无法使用。这是因为在B+树中只存储数据表中字段的值,而检索时需要对所有元素应用函数才能进行比较,这显然会造成很大的成本。所以应该这样书写语句:create_time = unix_timestamp('2014-05-29');

12.应该尽可能扩展现有的索引,而不是新建索引。假设表格中已经存在索引a,现在需要新增(a,b)的索引,只需修改现有的索引即可。需要注意的是:索引的选择最终目的是为了提高查询速度。所提供的原则是最基本的规范,但应该避免过于束缚于这些准则。读者需要在未来的学习和工作中不断实践。根据具体应用情况进行分析和评估,选择最适合的索引方式。在Java中,总共有4种引用类型(实际上还有其他一些引用类型,如FinalReference):强引用、软引用、弱引用、虚引用。

在Java中,强引用即指我们常用的形式,例如Object a = new Object();,并没有对应的Reference类。本文的重点是对软引用、弱引用和虚引用的实现进行分析,这三种引用类型均继承自Reference类,其主要逻辑也在Reference类中。在进行分析之前,我们可以先提出几个问题。大部分网络文章中对弱引用的定义是:只有在内存不足时才会被回收,那么内存不足的具体标准是什么呢?内存不足是指计算机可用内存不够星空体育网页版。大多数网络文章都提到,虚引用不影响对象的生命周期。它主要用于跟踪垃圾回收器对对象进行回收的动作。这是真的吗?JDK中在哪些情况下会使用虚引用呢? 在查看Reference.java文件之前,让我们先来看一下其中几个字段

public abstract class Reference<T> {     // 这是对象的引用     private T referent;             // 它是回收队列,使用者在Reference的构造函数中指定     volatile ReferenceQueue<?  super T> queue;  //当这个引用被添加到queue时,该字段会被设为queue中的下一个元素,从而形成链表结构   //在GC执行时,JVM底层会维护一个名为DiscoveredList的链表,其中存放Reference对象,discovered字段指向该链表中的下一个元素,JVM 设置       transient private Reference<T> discovered;       // 用于线程同步的锁对象      static private class Lock { }     private static Lock lock = new Lock();     // 等待加入队列的 Reference 对象,在 GC 时由 JVM 设置,Java层的线程(ReferenceHandler)将源源不断地从待处理中提取元素并加入队列     private static Reference<Object> pending = null; } 

。  一个Reference对象的生命周期可分为以下两个部分:Native层和Java层。Native层在进行垃圾回收时,会将需回收的Reference对象放入DiscoveredList中(代码位于referenceProcessor.cpp文件的process_discovered_references方法中),然后将DiscoveredList中的元素移到PendingList中(代码位于referenceProcessor.cpp文件的enqueue_discovered_ref_helper方法中),PendingList中的第一个对象就是Reference类中的pending对象。

查看Java层的代码

private static class ReferenceHandler 继承 Thread     在run方法中 {        循环    true条件下 {      尝试处理挂起任务(true);          }    尝试处理挂起任务的静态boolean方法      {      Reference<Object> r;     Cleaner c;     尝试 {          同步 {      如果 挂起 !if(p == null){\n p = pending;\ \n//如果是Cleaner对象,则记录下来,下面做特殊处理\nc = r instanceof Cleaner ?清洁工具(Cleaner):\nr指向null;\n//指向PendingList的下一个对象\npending = r.discovered;\nr.discovered = null;\ else {\n//如果pending为null就先等待,当PendingList中有新对象加入时,JVM会执行通知(waitForNotify)。lock.wait(); // 如果等待就重试\nreturn waitForNotify;\n... // 如果是Cleaner对象, 使用clean方法来进行资源回收。如果c存在,则执行。若(p为null)  c.clean();  return true;    //将Reference加入ReferenceQueue,从ReferenceQueue中poll元素,开发者可感知对象回收事件。if (p != null) {\n p.clean();\n return true;\ \n// 将Reference加入到ReferenceQueue,开发者可以通过从ReferenceQueue中poll元素感知到对象被回收的事件。 超级 对象> q = r.queue;          如果 (q != ReferenceQueue.NULL) q.enqueue(r);         return true;  } 

整个过程十分简单:不断地从PendingList中提取元素,然后将其添加到ReferenceQueue中,开发者可以通过从ReferenceQueue中poll元素来感知对象已被回收的事件。需要注意的是,对于继承自虚引用的Cleaner类型对象,有一些额外的处理:当其所指向的对象被回收时,会调用clean方法。该方法的主要作用是执行相应的资源释放操作。在堆外内存DirectByteBuffer中,就是使用Cleaner进行堆外内存的回收。这也是虚引用在Java中的一个典型应用。查看完了Reference的实现之后,再观察几个实现类,分别了解它们之间的差异。软引用是Java中的一种引用类型,它允许对象保留在内存中,但不会阻止垃圾回收器对其进行回收。SoftReference类扩展自Reference类,并包含一个静态私有的时钟和时间戳变量。SoftReference类的构造函数可以接受一个参数referent,将其传递给父类Reference的构造函数,并将时钟值赋给时间戳变量。 引用队列

size_t ReferenceProcessor::处理_discovered_reflist(   DiscoveredList               引用列表[],   ReferencePolicy*             策略,清除引用者的布尔值clear_referent,检查对象是否存活的布尔值is_alive,保持引用的OopClosure。   VoidClosure*                 complete_gc,   AbstractRefProcTaskExecutor* task_executor) {  ...   //您还记得之前提到的DiscoveredList吗?refs_lists" 就是 DiscoveredList。对于DiscoveredList的处理分为几个阶段,其中SoftReference的处理就在第一个阶段 ...           for (uint i = 0; i < _max_num_q; i++) {          process_phase1(refs_lists[i], policy,                        is_alive, keep_alive, complete_gc);       }  ... }  //这个阶段的主要目标是在内存充足时,从refs_list中移除相应的SoftReference。 void ReferenceProcessor::process_phase1(DiscoveredList&    ref_list,                          ReferencePolicy*   polic,BoolObjectClosure的is_alive函数表示对象是否存活,OopClosure的keep_alive属性。VoidClosure *complete_gc() DiscoveredListIterator iter(refs_list, keep_alive, is_alive);   // 决定 哪些 轻松 可达 引用 应该 保持 活动状态。   while (iter.has_next()) {     iter.load_ptrs(DEBUG_ONLY(!是否是原子发现() /* 允许空引用对象 */));     //检查引用对象是否存活     bool 引用对象已死 = (iter.referent() !discovery_is_atomic函数中,它用于检查引用对象是否存活。referent_is_dead变量被定义为(iter.referent() != NULL) && !。iter.引用是否存活();     // 在对象不再存活时,会调用对应的ReferencePolicy来判断是否应回收该对象     if (引用已死 &&         !政策-> 应清除引用(iter.obj(),  _soft_ref_timestamp_clock)) {       如果 (TraceReferenceGC) { gclog_or_tty->print_cr("通过策略删除引用("INTPTR_FORMAT": %s" )", (void*)iter.obj(), 迭代器.obj()->klass()->internal_name());//从列表中删除引用对象\niter.remove();//使引用对象重新激活\niter.make_active();//保留被引用物体\niter.make_referent_alive();//移动到下一个引用对象\ else {\niter.next();\ ...xrefs_lists中储存了本次垃圾回收发现的一种引用类型(虚引用、软引用、弱引用等),方法process_discovered_reflist的功能是从refs_lists中移除不需要被回收的对象,最终refs_lists中仅剩下需要被回收的元素,然后将第一个元素赋值给之前提到的Reference.java的pending字段。ReferencePolicy总共包含四种实现:NeverClearPolicy、AlwaysClearPolicy、LRUCurrentHeapPolicy和LRUMaxHeapPolicy。

在面试时,常常会被问及Java中引用类型的原理,请你深入探讨

其中,NeverClearPolicy永远返回false,表示永不清除SoftReference,在JVM中此类未被使用。AlwaysClearPolicy则永远返回true,在referenceProcessor.hpp#setup方法中可将策略设置为AlwaysClearPolicy。至于何时需要使用AlwaysClearPolicy,有兴趣的人可以自行探究。

LRU当前堆策略和LRU最大堆策略的should_clear_reference方法是完全一样的:

bool LRU最大堆策略::should_clear_reference(oop p, 通过检查常驻内存对象的时间戳, 可以获取时间戳时钟。 计算时间间隔{jlong}interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p); 确保interval 不小于0,进行“清醒性检查”;\n如果自上次整理/垃圾回收后引用已被访问,则间隔将为零。\n如果间隔小于等于_max_interval,则返回false;\n否则返回true;\ntimestamp_clock是SoftReference的静态字段clock。类

java_lang_ref_SoftReference::timestamp(p)的相应字段是timestamp。若上一次GC后调用了SoftReference#get方法,则时间间隔为0;否则为连续几次GC之间的时间差。如果在上次GC之后调用了SoftReference#get方法,那么间隔值为0;否则,间隔值为相邻GC之间的时间。

_max_interval指的是一个临界值,在LRUCurrentHeapPolicy和LRUMaxHeapPolicy两种策略中有所不同。

void LRUCurrentHeapPolicy::安装() {   _最大_间隔 = (Universe::获取上次GC后堆空闲() / M) * SoftRefLRUPolicyMSPerMB;   确保(_最大_间隔 >= 0,Check()"函数;\n无效LRUMaxHeapPolicy::设置(){ \n size_t max_heap = MaxHeapSize; \n max_heap -= Universe::get_heap_used_at_last_gc(); \n max_heap /= M; \n _max_interval = max_heap * SoftRefLRUPolicyMSPerMB; \n assert(_max_interval >= 0, \nSanity check",看到这段内容,你就能确定SoftReference何时被回收,与所使用的策略(通常是LRUCurrentHeapPolicy),堆的可用空间大小,以及此SoftReference上一次调用get方法的时间相关。WeakReference类是一个泛型类,继承自Reference类。它包含一个构造函数,可以接收一个referent参数。可以观察到,对于软引用和弱引用,clear_referent字段都会传入true,这符合我们的期望:当对象不可访问时,引用字段将被设为null,随后对象将被回收(对于软引用而言,如果内存足够,在阶段1,相关引用将从refs_list中移除,在阶段3时refs_list为空)。对于Final references和 Phantom references,如果clear_referent字段设为false,表示被这两种引用类型引用的对象,只要Reference对象还存活,就不会被回收,除非有其他特殊处理。

但对于Final references和 Phantom references,clear_referent字段传入的是false,也就意味着被这两种引用类型引用的对象,如果没有其他额外处理,只要Reference对象还存活,那引用的对象是不会被回收的。Final references和对象是否重写了finalize方法有关,不在本文分析范围之内,我们接下来看看Phantom references。在Java层面,WeakReference只是继承了Reference,并没有做任何改动。referent字段是在什么时间被设置为null的呢?为了搞清楚这个问题,让我们再来看一下前面提到的

process_discovered_reflist方法:

 

size_t ReferenceProcessor::process_discovered_reflist(   DiscoveredList               refs_lists[],   ReferencePolicy*             policy,(p)(p)(p)布尔(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)清除指示器, (p)(p)(p)布尔对象闭包*(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)存活, (p)(p)(p)对象闭包*(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)(p)保持存活,   VoidClosure*                 complete_gc,   AbstractRefProcTaskExecutor* task_executor) {  ...    //第一阶段: 从refs_lists中移除所有软引用,这些引用不再存活,但尚未被垃圾回收(仅当refs_lists为软引用时,policy不为null)   if (policy !如果(p == NULL)   if(mt_processing)    RefProcPhase1Task phase1(*this,refs_lists,policy, true /*marks_oops_alive*/);       task_executor->execute(phase1);     } else {       for (uint i = 0; i < _max_num_q; i++) { 执行第一阶段(refs_lists[i], 策略, is_alive, keep_alive, complete_gc);\n否则 {\n // 如果策略 == NULL,则断言 refs_lists 不为空\ 发现Soft引用后需要指定策略。「必须为soft引用指定政策。」;\n//第二阶段:移除所有指向仍存活对象的引用。\n如果(mt_processing)\n{\nRefProcPhase2Task phase2(*this, refs_lists, !发现是原子操作() /*标记糊涂活着的*/);     任务执行器->执行(phase2);   } else {     对于 (uint i = 0; i < _max_num_q; i++) {       处理阶段2(refs_lists[i], 是否存活, 保持存活,完成gc操作);\n第三阶段:\n根据clear_referent的值来决定是否回收未存活的对象\nif (mt_processing) {\n RefProcPhase3Task phase3(*this, refs_lists, clear_referent, …) /*marks_oops_alive*/);      task_executor->execute(phase3);   } else {      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; void ReferenceProcessor::process_phase3(DiscoveredList &refs_list,清除引用者。BoolObjectClosure* is_alive,OopClosure* keep_alive完整GC的VoidClosure* complete_gc的资源标记rm; 已发现列表迭代器iter(refs_list, keep_alive, is_alive);   当 (iter.has_next()) {     iter.update_discovered();     iter.load_ptrs(DEBUG_ONLY(false /* allow_null_referent */));     如果 (clear_referent) {       // 将Referent设置为Null       //将Reference的referent字段置为null,之后将会被垃圾回收机制回收。如果iter.clear_referent();不符合条件,则执行以下代码。保持引用对象存在并标记为活跃状态。该对象在下一次垃圾回收时不会被释放。iter.make_referent_alive(); 不论是弱引用还是其他引用类型,将referent字段置为null的操作将在process_phase3中进行,具体行为将取决于clear_referent的值。clear_referent的值取决于所引用的数据类型。参考处理器统计ReferenceProcessorStats:ReferenceProcessor::process_discovered_references(BoolObjectClosure* is_alive, OopClosure* keep_alive,VoidClosure* complete_gc, AbstractRefProcTaskExecutor* task_executor,申明GCTimer指针gc_timer,检查是否可以处理引用列表,执行发现引用列表方法的第三字段为clear_referent,软引用计数器soft_count设为0,开始跟踪软引用的GC时间。 trace_time, false, gc_timer);     soft_count =       process_discovered_reflist(_discoveredSoftRefs, _current_soft_ref_policy, true,                                  is_alive, keep_alive,完成垃圾回收任务执行器完整;更新软引用主时钟;\n//弱引用\nsize_t弱引用计数=0;\n{\n GCTraceTime tt("WeakReference", trace_time, false, gc_timer);     弱引用计数 =       处理已发现引用列表(_discoveredWeakRefs, NULL, true,                                   is_alive, keep_alive, complete_gc,执行者);}}}}} // 最终参考资料大小  size_t final_count = 0; {{{{{{{GCTraceTime("FinalReference",trace_time,false,启动定时器(gc_timer);最终计数=处理发现的引用列表(_discoveredFinalRefs,NULL,false);isAlive, keepAlive, completeGC, taskExecutor); \n// 虚引用\nsize_t phantomCount = 0;\n{\n GCTraceTime tt("PhantomReference", traceTime, 错误,gc_timer);幻象计数=处理已发现的引用列表(_discoveredPhantomRefs,空,错误,is_alive,keep_alive,complete_gc,通过查看代码,可以发现对于软引用(Soft references)和弱引用(Weak references),clear_referent字段都被设置为true,这和我们的期望是一致的:在对象不可达之后,引用字段会被置为null,然后对象就会被回收(对于软引用来说,如果内存足够的话,在第一阶段,相关引用会从refs_list中被移除,在第三阶段,refs_list会变为空集合)。Final references和Phantom references对于clear_referent字段的传入参数是false。这表示被这两种引用类型引用的对象,在Reference对象仍然存活的情况下,不会被回收,除非有其他额外的处理。

但对于Final references和 Phantom references,clear_referent字段传入的是false,也就意味着被这两种引用类型引用的对象,如果没有其他额外处理,只要Reference对象还存活,那引用的对象是不会被回收的。Final references和对象是否重写了finalize方法有关,不在本文分析范围之内,我们接下来看看Phantom references。PhantomReference是一个类,继承自Reference类。它有一个公共方法get(),该方法返回null。另外,PhantomReference还有一个构造函数,参数为referent和ReferenceQueue。在这里,我们可以观察到虚引用的get方法会永远返回null值。让我们来看一个示例。public static void demo() throws InterruptedException {\n Object obj = new Object();\n ReferenceQueue refQueue = new ReferenceQueue<>();\n PhantomReference phanRef = new PhantomReference<>(obj, refQueue);         从phanRef获取对象         // 在此处获得的是null         System.out.println(objg);         // 使obj成为垃圾         obj=null;         System.gc();         Thread.sleep(3000);         // 垃圾回收后将phanRef添加到refQueue中         引用<? 扩展 Object> phanRefP = refQueue.poll();          // 这里输出true         System.out.println(phanRefP==phanRef);     } 

通过上面的代码可以看出,虚引用会在指向的对象不可访问时收到一个‘通知’(实际上所有继承自References的类都有这个功能)。需要注意的是,在GC完成后,phanRef.referent仍然指向之前创建的Object。即表示Object对象一直未被释放!造成这种情况的原因已在上一节末尾提及:对于最终引用和幻影引用,clear_referent字段传递的值为false,意味着被这两种引用类型引用的对象在垃圾回收中不会被清除,除非有其他处理方式。在前一节的结尾已经提到了造成这种现象的原因:对于最终引用和虚引用,如果clear_referent字段被传入false,那意味着被这两种引用类型引用的对象,在垃圾回收中不会被释放。对于虚引用而言,通过refQueue.remove();获取引用对象后,可以调用clear方法来强制解除引用和对象之间的关系,从而使得对象在下一次垃圾回收时可以被回收。

对于文章一开始提出的问题,经过分析后,我们已经可以给出答案:

1. 我们经常在网络上看到软引用的说明是:只有在内存不足的情况下才会被回收,那么内存不足是如何定义的呢?为什么会提示内存不足?软引用在内存不足时可能被垃圾回收掉,具体的内存不足定义取决于引用对象被获取的时间以及当前堆可用内存的大小,具体的计算公式已在先前的文本中给出。星空体育网站

2. 在网络上关于虚引用的阐述是:虚引用类似虚构,与其他几种引用有所不同,虚引用并不会影响对象的生命周期。主要用途是追踪对象被垃圾回收器回收的过程。这是否属实呢?在严格意义上讲,虚引用会影响对象的生命周期。若未作任何处理,只要虚引用未被回收,被引用的对象将永远不会被回收。通常情况下,一旦从ReferenceQueue中获取到PhantomReference对象,如果该PhantomReference对象不会被回收(如被其他GC ROOT可达的对象引用),则需要调用clear方法来取消PhantomReference与其引用对象之间的关联。

3. Jdk中的弱引用有哪些典型应用场景?在DirectByteBuffer类中,通过虚引用的子类Cleaner.java来实现堆外内存的回收,接下来会撰写一篇文章深入探讨堆外内存的各个方面。xnxpxnx

在面试时,常常会被问及Java中引用类型的原理,请你深入探讨

星空体育登录入口

星空体育官方版


星空体育网站 星空体育手机版 星空体育官方版

推荐文章

公众号