1.選擇唯一性索引
唯一性索引的值是唯一的,可以更快速的通過該索引來確定某條記錄。例如,學生表中學號是具有唯一性的字段。為該字段建立唯一性索引可以很快的確定某個學生的信息。如果使用姓名的話,可能存在同名現象,從而降低查詢速度。
2.為經常需要排序、分組和聯合操作的字段建立索引
經常需要ORDER BY、GROUP BY、DISTINCT和UNION等操作的字段,排序操作會浪費很多時間。如果為其建立索引,可以有效地避免排序操作。
3.為常作為查詢條件的字段建立索引
如果某個字段經常用來做查詢條件,那么該字段的查詢速度會影響整個表的查詢速度。因此,為這樣的字段建立索引,可以提高整個表的查詢速度。
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.=和in可以亂序。
比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意順序,mysql的查詢優化器會幫你優化成索引可以識別的形式
10.盡量選擇區分度高的列作為索引。
區分度的公式是count(distinct col)/count(*),表示字段不重復的比例,比例越大我們掃描的記錄數越少,唯一鍵的區分度是1,而一些狀態、性別字段可能在大數據面前區分度就 是0,那可能有人會問,這個比例有什么經驗值嗎?使用場景不同,這個值也很難確定,一般需要join的字段我們都要求是0.1以上,即平均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):強引用、軟引用、弱引用、虛引用。
其中強引用就是我們經常使用的Object a = new Object(); 這樣的形式,在Java中并沒有對應的Reference類。
本篇文章主要是分析軟引用、弱引用、虛引用的實現,這三種引用類型都是繼承于Reference這個類,主要邏輯也在Reference中。
問題
在分析前,先拋幾個問題?
- 網上大多數文章對于軟引用的介紹是:在內存不足的時候才會被回收,那內存不足是怎么定義的?什么才叫內存不足?
- 網上大多數文章對于虛引用的介紹是:形同虛設,虛引用并不會決定對象的生命周期。主要用來跟蹤對象被垃圾回收器回收的活動。真的是這樣嗎?
- 虛引用在Jdk中有哪些場景下用到了呢?
Reference
我們先看下Reference.java中的幾個字段
- public abstract class Reference<T> {
- //引用的對象
- private T referent;
- //回收隊列,由使用者在Reference的構造函數中指定
- volatile ReferenceQueue<? super T> queue;
- //當該引用被加入到queue中的時候,該字段被設置為queue中的下一個元素,以形成鏈表結構
- volatile Reference next;
- //在GC時,JVM底層會維護一個叫DiscoveredList的鏈表,存放的是Reference對象,discovered字段指向的就是鏈表中的下一個元素,由JVM設置
- transient private Reference<T> discovered;
- //進行線程同步的鎖對象
- static private class Lock { }
- private static Lock lock = new Lock();
- //等待加入queue的Reference對象,在GC時由JVM設置,會有一個java層的線程(ReferenceHandler)源源不斷的從pending中提取元素加入到queue
- private static Reference<Object> pending = null;
- }
一個Reference對象的生命周期如下:
主要分為Native層和Java層兩個部分。
Native層在GC時將需要被回收的Reference對象加入到DiscoveredList中(代碼在referenceProcessor.cpp中
process_discovered_references方法),然后將DiscoveredList的元素移動到PendingList中(代碼在referenceProcessor.cpp中enqueue_discovered_ref_helper方法),PendingList的隊首就是Reference類中的pending對象。
看看Java層的代碼
- private static class ReferenceHandler extends Thread {
- ...
- public void run() {
- while (true) {
- tryHandlePending(true);
- }
- }
- }
- static boolean tryHandlePending(boolean waitForNotify) {
- Reference<Object> r;
- Cleaner c;
- try {
- synchronized (lock) {
- if (pending != null) {
- r = pending;
- //如果是Cleaner對象,則記錄下來,下面做特殊處理
- c = r instanceof Cleaner ? (Cleaner) r : null;
- //指向PendingList的下一個對象
- pending = r.discovered;
- r.discovered = null;
- } else {
- //如果pending為null就先等待,當有對象加入到PendingList中時,jvm會執行notify
- if (waitForNotify) {
- lock.wait();
- }
- // retry if waited
- return waitForNotify;
- }
- }
- }
- ...
- // 如果時CLeaner對象,則調用clean方法進行資源回收
- if (c != null) {
- c.clean();
- return true;
- }
- //將Reference加入到ReferenceQueue,開發者可以通過從ReferenceQueue中poll元素感知到對象被回收的事件。
- ReferenceQueue<? super Object> q = r.queue;
- if (q != ReferenceQueue.NULL) q.enqueue(r);
- return true;
- }
流程比較簡單:就是源源不斷的從PendingList中提取出元素,然后將其加入到ReferenceQueue中去,開發者可以通過從ReferenceQueue中poll元素感知到對象被回收的事件。
另外需要注意的是,對于Cleaner類型(繼承自虛引用)的對象會有額外的處理:在其指向的對象被回收時,會調用clean方法,該方法主要是用來做對應的資源回收,在堆外內存DirectByteBuffer中就是用Cleaner進行堆外內存的回收,這也是虛引用在java中的典型應用。
看完了Reference的實現,再看看幾個實現類里,各自有什么不同。
SoftReference
- public class SoftReference<T> extends Reference<T> {
- static private long clock;
- private long timestamp;
- public SoftReference(T referent) {
- super(referent);
- this.timestamp = clock;
- }
- public SoftReference(T referent, ReferenceQueue<? super T> q) {
- super(referent, q);
- this.timestamp = clock;
- }
- public T get() {
- T o = super.get();
- if (o != null && this.timestamp != clock)
- this.timestamp = clock;
- return o;
- }
- }
軟引用的實現很簡單,就多了兩個字段:clock和timestamp。clock是個靜態變量,每次GC時都會將該字段設置成當前時間。timestamp字段則會在每次調用get方法時將其賦值為clock(如果不相等且對象沒被回收)。
那這兩個字段的作用是什么呢?這和軟引用在內存不夠的時候才被回收,又有什么關系呢?
這些還得看JVM的源碼才行,因為決定對象是否需要被回收都是在GC中實現的。
- 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)
- {
- ...
- //還記得上文提到過的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);
- }
- ...
- }
- //該階段的主要目的就是當內存足夠時,將對應的SoftReference從refs_list中移除。
- void
- ReferenceProcessor::process_phase1(DiscoveredList& refs_list,
- ReferencePolicy* policy,
- BoolObjectClosure* is_alive,
- OopClosure* keep_alive,
- VoidClosure* complete_gc) {
- DiscoveredListIterator iter(refs_list, keep_alive, is_alive);
- // Decide which softly reachable refs should be kept alive.
- while (iter.has_next()) {
- iter.load_ptrs(DEBUG_ONLY(!discovery_is_atomic() /* allow_null_referent */));
- //判斷引用的對象是否存活
- bool referent_is_dead = (iter.referent() != NULL) && !iter.is_referent_alive();
- //如果引用的對象已經不存活了,則會去調用對應的ReferencePolicy判斷該對象是不時要被回收
- if (referent_is_dead &&
- !policy->should_clear_reference(iter.obj(), _soft_ref_timestamp_clock)) {
- if (TraceReferenceGC) {
- gclog_or_tty->print_cr("Dropping reference (" INTPTR_FORMAT ": %s" ") by policy",
- (void *)iter.obj(), iter.obj()->klass()->internal_name());
- }
- // Remove Reference object from list
- iter.remove();
- // Make the Reference object active again
- iter.make_active();
- // keep the referent around
- iter.make_referent_alive();
- iter.move_to_next();
- } else {
- iter.next();
- }
- }
- ...
- }
refs_lists中存放了本次GC發現的某種引用類型(虛引用、軟引用、弱引用等),而
process_discovered_reflist方法的作用就是將不需要被回收的對象從refs_lists移除掉,refs_lists最后剩下的元素全是需要被回收的元素,最后會將其第一個元素賦值給上文提到過的Reference.java#pending字段。
ReferencePolicy一共有4種實現:NeverClearPolicy,AlwaysClearPolicy,LRUCurrentHeapPolicy,LRUMaxHeapPolicy。
其中NeverClearPolicy永遠返回false,代表永遠不回收SoftReference,在JVM中該類沒有被使用,AlwaysClearPolicy則永遠返回true,在referenceProcessor.hpp#setup方法中中可以設置policy為AlwaysClearPolicy,至于什么時候會用到AlwaysClearPolicy,大家有興趣可以自行研究。
LRUCurrentHeapPolicy和LRUMaxHeapPolicy的should_clear_reference方法則是完全相同:
- bool LRUMaxHeapPolicy::should_clear_reference(oop p,
- jlong timestamp_clock) {
- jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p);
- assert(interval >= 0, "Sanity check");
- // The interval will be zero if the ref was accessed since the last scavenge/gc.
- if(interval <= _max_interval) {
- return false;
- }
- return true;
- }
timestamp_clock就是SoftReference的靜態字段clock,
java_lang_ref_SoftReference::timestamp(p)對應是字段timestamp。如果上次GC后有調用SoftReference#get,interval值為0,否則為若干次GC之間的時間差。
_max_interval則代表了一個臨界值,它的值在LRUCurrentHeapPolicy和LRUMaxHeapPolicy兩種策略中有差異。
- void LRUCurrentHeapPolicy::setup() {
- _max_interval = (Universe::get_heap_free_at_last_gc() / M) * SoftRefLRUPolicyMSPerMB;
- assert(_max_interval >= 0,"Sanity check");
- }
- void LRUMaxHeapPolicy::setup() {
- size_t max_heap = MaxHeapSize;
- max_heap -= Universe::get_heap_used_at_last_gc();
- max_heap /= M;
- _max_interval = max_heap * SoftRefLRUPolicyMSPerMB;
- assert(_max_interval >= 0,"Sanity check");
- }
看到這里你就知道SoftReference到底什么時候被被回收了,它和使用的策略(默認應該是LRUCurrentHeapPolicy),堆可用大小,該SoftReference上一次調用get方法的時間都有關系。
WeakReference
- public class WeakReference<T> extends Reference<T> {
- public WeakReference(T referent) {
- super(referent);
- }
- public WeakReference(T referent, ReferenceQueue<? super T> q) {
- super(referent, q);
- }
- }
可以看到,對于Soft references和Weak references clear_referent字段傳入的都是true,這也符合我們的預期:對象不可達后,引用字段就會被置為null,然后對象就會被回收(對于軟引用來說,如果內存足夠的話,在Phase 1,相關的引用就會從refs_list中被移除,到Phase 3時refs_list為空集合)。
但對于Final references和 Phantom references,clear_referent字段傳入的是false,也就意味著被這兩種引用類型引用的對象,如果沒有其他額外處理,只要Reference對象還存活,那引用的對象是不會被回收的。Final references和對象是否重寫了finalize方法有關,不在本文分析范圍之內,我們接下來看看Phantom references。
可以看到WeakReference在Java層只是繼承了Reference,沒有做任何的改動。那referent字段是什么時候被置為null的呢?要搞清楚這個問題我們再看下上文提到過的
process_discovered_reflist方法:
- 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)
- {
- ...
- //Phase 1:將所有不存活但是還不能被回收的軟引用從refs_lists中移除(只有refs_lists為軟引用的時候,這里policy才不為null)
- if (policy != 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++) {
- process_phase1(refs_lists[i], policy,
- is_alive, keep_alive, complete_gc);
- }
- }
- } else { // policy == NULL
- assert(refs_lists != _discoveredSoftRefs,
- "Policy must be specified for soft references.");
- }
- // Phase 2:
- // 移除所有指向對象還存活的引用
- if (mt_processing) {
- RefProcPhase2Task phase2(*this, refs_lists, !discovery_is_atomic() /*marks_oops_alive*/);
- task_executor->execute(phase2);
- } else {
- for (uint i = 0; i < _max_num_q; i++) {
- process_phase2(refs_lists[i], is_alive, keep_alive, complete_gc);
- }
- }
- // Phase 3:
- // 根據clear_referent的值決定是否將不存活對象回收
- if (mt_processing) {
- RefProcPhase3Task phase3(*this, refs_lists, clear_referent, true /*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,
- bool clear_referent,
- BoolObjectClosure* is_alive,
- OopClosure* keep_alive,
- VoidClosure* complete_gc) {
- ResourceMark rm;
- DiscoveredListIterator iter(refs_list, keep_alive, is_alive);
- while (iter.has_next()) {
- iter.update_discovered();
- iter.load_ptrs(DEBUG_ONLY(false /* allow_null_referent */));
- if (clear_referent) {
- // NULL out referent pointer
- //將Reference的referent字段置為null,之后會被GC回收
- iter.clear_referent();
- } else {
- // keep the referent around
- //標記引用的對象為存活,該對象在這次GC將不會被回收
- 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) {
- NOT_PRODUCT(verify_ok_to_handle_reflists());
- ...
- //process_discovered_reflist方法的第3個字段就是clear_referent
- // Soft references
- 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);
- }
- update_soft_ref_master_clock();
- // Weak references
- 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);
- }
- // Final references
- 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);
- }
- // Phantom references
- 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);
- }
- ...
- }
可以看到,對于Soft references和Weak references clear_referent字段傳入的都是true,這也符合我們的預期:對象不可達后,引用字段就會被置為null,然后對象就會被回收(對于軟引用來說,如果內存足夠的話,在Phase 1,相關的引用就會從refs_list中被移除,到Phase 3時refs_list為空集合)。
但對于Final references和 Phantom references,clear_referent字段傳入的是false,也就意味著被這兩種引用類型引用的對象,如果沒有其他額外處理,只要Reference對象還存活,那引用的對象是不會被回收的。Final references和對象是否重寫了finalize方法有關,不在本文分析范圍之內,我們接下來看看Phantom references。
PhantomReference
- public class PhantomReference<T> extends Reference<T> {
- public T get() {
- return null;
- }
- public PhantomReference(T referent, ReferenceQueue<? super T> q) {
- super(referent, q);
- }
- }
可以看到虛引用的get方法永遠返回null,我們看個demo。
- public static void demo() throws InterruptedException {
- Object obj = new Object();
- ReferenceQueue<Object> refQueue =new ReferenceQueue<>();
- PhantomReference<Object> phanRef =new PhantomReference<>(obj, refQueue);
- Object objg = phanRef.get();
- //這里拿到的是null
- System.out.println(objg);
- //讓obj變成垃圾
- obj=null;
- System.gc();
- Thread.sleep(3000);
- //gc后會將phanRef加入到refQueue中
- Reference<? extends Object> phanRefP = refQueue.remove();
- //這里輸出true
- System.out.println(phanRefP==phanRef);
- }
從以上代碼中可以看到,虛引用能夠在指向對象不可達時得到一個'通知'(其實所有繼承References的類都有這個功能),需要注意的是GC完成后,phanRef.referent依然指向之前創建Object,也就是說Object對象一直沒被回收!
而造成這一現象的原因在上一小節末尾已經說了:對于Final references和 Phantom references,clear_referent字段傳入的時false,也就意味著被這兩種引用類型引用的對象,如果沒有其他額外處理,在GC中是不會被回收的。
對于虛引用來說,從refQueue.remove();得到引用對象后,可以調用clear方法強行解除引用和對象之間的關系,使得對象下次可以GC時可以被回收掉。
End
針對文章開頭提出的幾個問題,看完分析,我們已經能給出回答:
1.我們經常在網上看到軟引用的介紹是:在內存不足的時候才會回收,那內存不足是怎么定義的?為什么才叫內存不足?
軟引用會在內存不足時被回收,內存不足的定義和該引用對象get的時間以及當前堆可用內存大小都有關系,計算公式在上文中也已經給出。
2.網上對于虛引用的介紹是:形同虛設,與其他幾種引用都不同,虛引用并不會決定對象的生命周期。主要用來跟蹤對象被垃圾回收器回收的活動。真的是這樣嗎?
嚴格的說,虛引用是會影響對象生命周期的,如果不做任何處理,只要虛引用不被回收,那其引用的對象永遠不會被回收。所以一般來說,從ReferenceQueue中獲得PhantomReference對象后,如果PhantomReference對象不會被回收的話(比如被其他GC ROOT可達的對象引用),需要調用clear方法解除PhantomReference和其引用對象的引用關系。
3.虛引用在Jdk中有哪些場景下用到了呢?
DirectByteBuffer中是用虛引用的子類Cleaner.java來實現堆外內存回收的,后續會寫篇文章來說說堆外內存的里里外外。
原文地址:https://www.toutiao.com/a7051934204413886990/