-
在面试时,常常会被问及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的构造函数,并将时钟值赋给时间戳变量。 引用队列 超级 T> q) { 超级(引用, q); this.timestamp = 时钟; } 公共 T 获取() { T o = 超级获取(); 如果{o} != null && 此时间戳 !空引用的实现非常简单,只需添加两个新字段:clock和timestamp。时钟是一个静态变量,每当进行GC时,该字段都会被设为当前时间。每当调用get方法时,timestamp字段会被赋值为当前时间(clock),条件是当前时间不同且对象未被回收。这两个字段各自有什么用途?软引用是在内存不足时才会被回收,这与什么有关系呢?只有查看JVM源码才能了解清楚,因为决定对象是否可以被回收是由垃圾回收器来实现的。
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。
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