關(guān)于弱引用WeakReference所引用的對(duì)象的回收規(guī)則
什么是弱引用
- 弱引用實(shí)例:java.lang.ref.WeakReference類(lèi)或者其子類(lèi)的一個(gè)實(shí)例,就是一個(gè)弱引用實(shí)例。
- 弱引用:如果一個(gè)弱引用實(shí)例的成員變量referent引用了一個(gè)對(duì)象obj,那么就稱(chēng)這個(gè)弱引用實(shí)例對(duì)obj的引用是弱引用。
- 弱引用對(duì)象:被一個(gè)弱引用實(shí)例引用的對(duì)象,稱(chēng)為弱引用對(duì)象。
public class WeakReference<T> extends Reference<T> { /** * 構(gòu)造一個(gè)弱引用實(shí)例,此弱引用實(shí)例會(huì)引用給定的參數(shù)對(duì)象 */ public WeakReference(T referent) { super(referent); } /** * 構(gòu)造一個(gè)引用了給定對(duì)象referent的弱引用實(shí)例,同時(shí)將referent對(duì)象注冊(cè)到一個(gè)引用隊(duì)列 */ public WeakReference(T referent, ReferenceQueue<? super T> q) { super(referent, q); } }
示例
在實(shí)際業(yè)務(wù)場(chǎng)景中,我們通常會(huì)定義一個(gè)WeakReference的子類(lèi)來(lái)解決我們的需求。 例如:
class Apple{ String color; void Apple(String color){ this.color = color; } String getColor(){ return color; } void setColor(String color){ this.color = color; } public String toString(){ return new StringBuilder("Apple[color=").append(this.color).append("]").toString(); } // 當(dāng)對(duì)象被GC回收時(shí),會(huì)回調(diào)finalize()方法 protected void finalize() throws Throwable{ System.out.println(this); } }
如下所定義的Salad類(lèi),其實(shí)例是一個(gè)弱引用實(shí)例,其實(shí)例會(huì)持有一個(gè)Apple類(lèi)對(duì)象的弱引用。
當(dāng)一個(gè)Apple實(shí)例對(duì)象只被salad類(lèi)實(shí)例(或者其它弱引用實(shí)例)引用時(shí),它就會(huì)被GC回收。
class Salad extends WeakReference<Apple>{ Apple apple; void Salad(Apple apple){ super(apple); } }
弱引用 WeakReference 相關(guān)的GC回收規(guī)則
當(dāng)一個(gè)對(duì)象只被弱引用實(shí)例引用(持有)時(shí),這個(gè)對(duì)象就會(huì)被GC回收。
WeakReference類(lèi)的javadoc:
一個(gè)弱引用實(shí)例,不會(huì)對(duì)它(的成員變量referent)所引用的對(duì)象的finalizable(是否可銷(xiāo)毀)、finalized(銷(xiāo)毀)和 reclaimed(GC回收)產(chǎn)生任何影響。
如果GC在某個(gè)時(shí)間點(diǎn)確定某對(duì)象是弱可達(dá)的(只被某個(gè)或某些弱引用對(duì)象引用),那么它就會(huì)清除對(duì)該弱可達(dá)對(duì)象的所有弱引用(將引用了弱可達(dá)對(duì)象的弱引用實(shí)例的referent置為null:referent=null),同時(shí)還會(huì)找出從"GC Roots"到該對(duì)象的強(qiáng)引用鏈和軟引用鏈上的所有弱可達(dá)對(duì)象,然后也會(huì)清除對(duì)這些弱可達(dá)對(duì)象的所有弱引用。
同時(shí),GC會(huì)將以上弱可達(dá)對(duì)象標(biāo)記為可銷(xiāo)毀的(finalizable)。然后會(huì)立刻或者在稍后的某個(gè)時(shí)間點(diǎn),將以上那些清除了的弱引用實(shí)例對(duì)象入隊(duì)到它們?cè)趧?chuàng)建時(shí)就注冊(cè)到的queue中去。(參考Reference)
弱引用通常用于實(shí)現(xiàn)規(guī)范化映射(WeakHashMap、WeakCache)。
注意
- 上述規(guī)則中,會(huì)被GC標(biāo)記為finalizable的的是弱引用實(shí)例引用的對(duì)象,而非弱引用實(shí)例本身
- 如果顯式地聲明了一個(gè)變量E e,并使之指向一個(gè)對(duì)象:e = new E(),這時(shí)變量e就是這個(gè)新創(chuàng)建的對(duì)象的一個(gè)強(qiáng)引用。如果變量e所引用的這個(gè)對(duì)象同時(shí)又被WeakReference的一個(gè)實(shí)例持有,則由于存在對(duì)對(duì)象的一個(gè)強(qiáng)引用e,對(duì)象并不符合上述回收規(guī)則,因此對(duì)象至少在變量e的作用域范圍內(nèi)都不會(huì)被回收。
示例:
public class Test{ public static void main(String[] args) throws InterruptedException{ // saladWithRedApple 引用的Apple對(duì)象符合弱引用回收規(guī)則 Salad saladWithRedApple = new Salad(new Apple("red ")); Apple green = new Apple("green"); // saladWithGreenApple 引用的Apple對(duì)象不符合弱引用回收規(guī)則,因?yàn)樗瑫r(shí)被green這個(gè)強(qiáng)引用所引用 Salad saladWithGreenApple = new Salad(green); System.gc(); try{ Thread.sleep(5000); }catch(InterruptedException e){ e.printStackTrace(); } out.println(saladWithRedApple.get()==null); // true out.println(saladWithGreenApple.get()==null); // false } }
Reference
此類(lèi)是所有Reference類(lèi)的實(shí)例對(duì)象(所有Reference的實(shí)現(xiàn)類(lèi)的實(shí)例對(duì)象)的抽象基類(lèi),此類(lèi)中定義了所有Reference類(lèi)的實(shí)例對(duì)象的通用操作。由于引用類(lèi)對(duì)象同垃圾回收兩者之間有密切的關(guān)系(對(duì)象的回收本身就與對(duì)象的引用關(guān)系密切,例如初代垃圾收集器就是判斷對(duì)象是否還被變量引用來(lái)確定對(duì)象是否可以被會(huì)回收的),因此子類(lèi)可能不會(huì)直接實(shí)現(xiàn)Reference類(lèi)(而是實(shí)現(xiàn)WeakReference等類(lèi),以避免出現(xiàn)垃圾對(duì)象不被及時(shí)回收的情況。
注:如果用戶直接實(shí)現(xiàn)Reference類(lèi),就相當(dāng)于一個(gè)定義一個(gè)強(qiáng)引用類(lèi),因?yàn)镚C對(duì)于用戶自定義的類(lèi)并沒(méi)有做任何特殊處理。但GC對(duì)于JDK中定義的 SoftReference 和 WeakReference 等,都做了特殊處理,因此就有了不要直接實(shí)現(xiàn)Reference類(lèi)的建議)。
在Reference類(lèi)中定義了以下幾個(gè)實(shí)例變量:
private T referent; /* 會(huì)被GC進(jìn)行特殊處理 */ /* 此引用實(shí)際指向的對(duì)象 */ volatile ReferenceQueue<? super T> queue; /* 當(dāng)前實(shí)例創(chuàng)建時(shí)如果對(duì)此queue賦值,則稱(chēng)當(dāng)前實(shí)例注冊(cè)到了此queue */ Reference next; /* 用于確定當(dāng)前實(shí)例是否處于active狀態(tài)。active:null, pending:this, enqueued:next element in queue, inactive:this */ transient private Reference<T> discovered; /* used by VM */
Reference對(duì)實(shí)例對(duì)象定義了4種內(nèi)部狀態(tài)(沒(méi)有顯式地用枚舉類(lèi)聲明出來(lái)):
- 活動(dòng)的(active):剛創(chuàng)建的Reference實(shí)例處于活動(dòng)狀態(tài)。垃圾收集器會(huì)對(duì)active狀態(tài)的引用所指向的實(shí)際對(duì)象referent做特殊處理:當(dāng)垃圾收集器監(jiān)測(cè)到active狀態(tài)的實(shí)例的referent的可達(dá)性變成了某個(gè)特定狀態(tài)時(shí),會(huì)將當(dāng)前Reference實(shí)例的狀態(tài)由active更改為pending或者inactive。具體取決于實(shí)例創(chuàng)建時(shí),是否注冊(cè)到了一個(gè)ReferenceQueue隊(duì)列(即r的queue是否為null),如果實(shí)例創(chuàng)建時(shí)注冊(cè)了queue(注意注冊(cè)到queue與添加到queue不是一個(gè)概念,這里的注冊(cè)到了queue,實(shí)際是指r持有了一個(gè)ReferenceQueue實(shí)例的引用),則實(shí)例狀態(tài)改為pending,并會(huì)被添加到掛起隊(duì)列(pendinglist,掛起隊(duì)列同queue不是一個(gè)隊(duì)列);否則實(shí)例狀態(tài)被改為inactive。
- 掛起(pending):當(dāng)實(shí)例被添加到掛起隊(duì)列pending-list中后,狀態(tài)就會(huì)被改為pending,即掛起隊(duì)列中的所有元素的狀態(tài)都是Pending。掛起隊(duì)列中的元素都在等待線程類(lèi)將實(shí)例入隊(duì)(添加到元素自身持有的queue中去)。創(chuàng)建時(shí)沒(méi)有注冊(cè)到queue的Reference實(shí)例永遠(yuǎn)也不會(huì)變成此狀態(tài)。
- enqueued:當(dāng)實(shí)例被添加到其自身持有的queue(即其創(chuàng)建時(shí)注冊(cè)的queue)后,狀態(tài)被更改為enqueued。當(dāng)實(shí)例被從此隊(duì)列中移除后,狀態(tài)就變?yōu)閕nactive
- 不活躍(inactive):當(dāng)實(shí)例被更改成此狀態(tài)后,其狀態(tài)就不會(huì)再改變了。
實(shí)例在各個(gè)狀態(tài)下時(shí),其所持有的ReferenceQueue實(shí)例-queue變量和持有的Reference實(shí)例-next變量的值如下:
- active狀態(tài)時(shí):queue = 實(shí)例被創(chuàng)建時(shí)如果注冊(cè)了queue,則此queue就不會(huì)空。否則queue=ReferenceQueue.NULL; next = null.
- pending狀態(tài)時(shí):queue=創(chuàng)建時(shí)注冊(cè)到的queue,next=this。
- enqueued狀態(tài)時(shí):queue=ReferenceQueue.ENQUEUED(其實(shí)也是null);next=原來(lái)的隊(duì)頭(頭插法),如果原來(lái)的隊(duì)列為空,則next=this。
- inactive狀態(tài)時(shí):queue=ReferenceQueue.NULL,next=this。
在如上這種模式下,垃圾收集器僅需要通過(guò)檢查next字段就能確定實(shí)例是否需要特別的處理:如果next==null,那么實(shí)例處于active狀態(tài),如果next!=null,這垃圾收集器只需對(duì)實(shí)例進(jìn)行常規(guī)處理。 為了確保垃圾收集器能在不干擾對(duì)reference實(shí)例對(duì)象進(jìn)行enqueue()的應(yīng)用線程的正常運(yùn)行的情況下,能發(fā)現(xiàn)處于active狀態(tài)的實(shí)例,垃圾收集器應(yīng)該通過(guò)discovered字段鏈接這些處于active狀態(tài)的實(shí)例。 discovered字段也用于鏈接掛起列表中的Reference實(shí)例對(duì)象。
ReferenceQueue
Reference類(lèi)中定義了一個(gè)此類(lèi)的對(duì)象:
volatile ReferenceQueue<? super T> queue;
當(dāng)某Reference類(lèi)實(shí)例(或其子類(lèi)的實(shí)例)可能將不會(huì)再被使用,需要被垃圾收集器監(jiān)測(cè)以回收時(shí),應(yīng)將對(duì)象追加到此queue中。
垃圾收集器會(huì)不斷監(jiān)測(cè)此queue中的實(shí)例的狀態(tài),當(dāng)監(jiān)測(cè)到實(shí)例變更為某種狀態(tài)時(shí),會(huì)對(duì)對(duì)象進(jìn)行垃圾回收。
當(dāng)前對(duì)象r入隊(duì)后即queue.enqueue(),就會(huì)將自己的queue變量置空,即r.queue=null,以便垃圾收集器回收。
Lock
static private class Lock { } private static Lock lock = new Lock();
定義了Lock類(lèi)并創(chuàng)建了一個(gè)此類(lèi)的實(shí)例,用于作為同步垃圾收集線程的對(duì)象。
垃圾收集線程在每個(gè)收集周期開(kāi)始時(shí)必須先獲得此鎖,因此獲得此鎖的其它代碼應(yīng)盡快完成:盡量不要?jiǎng)?chuàng)建新的對(duì)象、盡量避免調(diào)用用戶代碼。
pending
pending是一個(gè)全局變量,每個(gè)JVM中只有一份。
private static Reference<Object> pending=null; // 是一個(gè)鏈表的頭結(jié)點(diǎn)
pending指向一個(gè)鏈表的頭結(jié)點(diǎn),當(dāng)某個(gè)處于特殊狀態(tài)的Reference實(shí)例需要插入此鏈表中時(shí),會(huì)采用頭插法的方式,將自己設(shè)置為pending,成為新的頭結(jié)點(diǎn)。
pending所指向的鏈表中的所有 Reference 實(shí)例都是處于掛起狀態(tài)、等待入隊(duì)的 Reference 實(shí)例。
當(dāng)active狀態(tài)的引用實(shí)例的referent的可達(dá)性處于某個(gè)特定狀態(tài)時(shí),垃圾收集線程會(huì)將此Reference實(shí)例添加到這個(gè)pending鏈表,并等待引用處理線程將元素從pending鏈表中移除,然后enqueue()到元素注冊(cè)的queue中去。
這個(gè)鏈表被lock對(duì)象保護(hù)。
這個(gè)鏈表用元素的discovered字段鏈接每個(gè)元素(相當(dāng)于鏈表節(jié)點(diǎn)中的next字段)。
ReferenceHandler
此線程用于將pending所指向的鏈表中的所有處于pending狀態(tài)的Reference實(shí)例,入隊(duì)到它們各自持有的queue中去。
此線程會(huì)調(diào)用boolean tryHandlePending(boolean waitFor)方法來(lái)處理pending狀態(tài)的引用對(duì)象。
此線程的優(yōu)先級(jí)被設(shè)置為最高。
如果可能還有其它pending狀態(tài)的引用實(shí)例,tryHandlePending會(huì)返回true。
如果沒(méi)有其它pending狀態(tài)的實(shí)例,并且希望應(yīng)用程序可以做更多的有意義的工作而不是這個(gè)線程一直自旋,一直占用CPU,則會(huì)返回false。
tryHandlePending方法的waitForNotify參數(shù)的意義:如果參數(shù)值為true,則線程會(huì)一直wait直到VM notify了它、或者線程被interrupted。
如果參數(shù)值為false,則當(dāng)沒(méi)有pending狀態(tài)的引用時(shí),線程就立即退出了。
如果處理了一個(gè)pending狀態(tài)的引用,則方法返回true。如果沒(méi)有要處理的對(duì)象,則一直wait,直到被notify或者被
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; // 'instanceof' might throw OutOfMemoryError sometimes // so do this before un-linking 'r' from the 'pending' chain... c = r instanceof Cleaner ? (Cleaner) r : null; // unlink 'r' from 'pending' chain pending = r.discovered; r.discovered = null; } else { // The waiting on the lock may cause an OutOfMemoryError // because it may try to allocate exception objects. if (waitForNotify) { lock.wait(); } // retry if waited return waitForNotify; } } } catch (OutOfMemoryError x) { // Give other threads CPU time so they hopefully drop some live references // and GC reclaims some space. // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above // persistently throws OOME for some time... Thread.yield(); // retry return true; } catch (InterruptedException x) { // retry return true; } // Fast path for cleaners if (c != null) { c.clean(); return true; } ReferenceQueue<? super Object> q = r.queue; if (q != ReferenceQueue.NULL) q.enqueue(r); return true; }
Reference類(lèi)中的靜態(tài)代碼塊
Reference中定義了一些靜態(tài)代碼塊,主要是啟動(dòng)一個(gè)線程,將處于pending狀態(tài)的引用類(lèi)對(duì)象入隊(duì),入隊(duì)后的Reference實(shí)例的狀態(tài)將變成Enqueued。
在Reference類(lèi)中,lock、pending、handler = new ReferenceHandler(…)、tryHandlePending(…)這些成員都是類(lèi)成員,因此,handler 線程是對(duì)全局的pending鏈表中的所有處于pending狀態(tài)的Re實(shí)例進(jìn)行處理。 queue、next、discovered則是實(shí)例變量。
static { ThreadGroup tg = Thread.currentThread().getThreadGroup(); for (ThreadGroup tgn = tg; tgn != null; tg = tgn, tgn = tg.getParent()); Thread handler = new ReferenceHandler(tg, "Reference Handler"); /* If there were a special system-only priority greater than * MAX_PRIORITY, it would be used here */ handler.setPriority(Thread.MAX_PRIORITY); handler.setDaemon(true); handler.start(); // provide access in SharedSecrets SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() { @Override public boolean tryHandlePendingReference() { return tryHandlePending(false); } }); }
WeakReference
弱引用對(duì)象,不會(huì)其引用的對(duì)象被JVM設(shè)置為可回收狀態(tài),然后被回收。弱引用通常用于實(shí)現(xiàn)規(guī)范化映射。
如果垃圾收集器在某個(gè)時(shí)間點(diǎn)確定某個(gè)對(duì)象的可達(dá)性是弱可達(dá)的(即這個(gè)對(duì)象可以通過(guò)一個(gè)弱引用鏈可達(dá),即使同時(shí)也有其它強(qiáng)引用鏈或者軟引用鏈可達(dá)此對(duì)象),那么GC就會(huì)清除所有引用這個(gè)對(duì)象的弱引用,還會(huì)通過(guò)可以到達(dá)這個(gè)對(duì)象的強(qiáng)引用鏈和軟引用鏈找到鏈上其它對(duì)象上的所有弱引用、并清除所有這些弱引用。
同時(shí),GC還會(huì)將所有之前被清除了弱引用的對(duì)象聲明為finalizable的。并且可能同時(shí)或者接著就會(huì)將那些弱引用實(shí)例本身添加到它們注冊(cè)到的ReferenceQueue隊(duì)列中去。
ReferenceQueue 類(lèi)什么用?
在Reference類(lèi)中,定義了一個(gè)ReferenceQueue類(lèi)型的成員變量,變量名為queue。 并定義了相應(yīng)的構(gòu)造函數(shù):
public Reference( T referent, ReferenceQueue<? super T> queue){ this.referent = referent; this.queue = (queue == null) ? ReferenceQueue.NULL : queue; }
當(dāng)構(gòu)造一個(gè)引用實(shí)例時(shí),如果初始化了成員變量queue的值,我稱(chēng)之為將引用實(shí)例與queue綁定了。 那么將引用實(shí)例與這個(gè)queue綁定,有什么用呢? 由于GC會(huì)對(duì)Reference類(lèi)及其子類(lèi)的實(shí)例進(jìn)行特殊方式的處理,比如對(duì)于weak引用實(shí)例,會(huì)在每次GC時(shí)都會(huì)將其發(fā)現(xiàn)的所有weak引用實(shí)例的referent 斷開(kāi)其引用。但是用戶可能需要對(duì)此做一些個(gè)性化的處理。因此,JVM設(shè)計(jì)出這樣的方式:GC在清理weak實(shí)例時(shí),會(huì)將weak實(shí)例入隊(duì)到其綁定的queue中,用戶就可以去queue中獲取這些被GC處理了的weak實(shí)例,然后再做一些個(gè)性化處理。
并不是所有的reference實(shí)例都必須綁定一個(gè)queue,如果用戶不需要對(duì)被GC的實(shí)例做特殊的處理,就不用設(shè)置。
一般在緩存map場(chǎng)景下,會(huì)定義一個(gè)ReferenceQueue,如WeakHashMap,WeakCache等。因?yàn)橥ǔ?shí)際的key封裝成一個(gè)WeakReference類(lèi)實(shí)例,存儲(chǔ)到緩存map的key中,目的是借助GC自動(dòng)及時(shí)釋放緩存內(nèi)存,防止map過(guò)大。但GC自動(dòng)將map中weak實(shí)例對(duì)實(shí)際key的referent置為null后,相應(yīng)的entry就失去了在map中存在的意義了,這時(shí)queue的作用就出來(lái)了:GC在清理weak實(shí)例時(shí),將此實(shí)例入隊(duì)到創(chuàng)建實(shí)例時(shí)綁定到的queue中,用戶主動(dòng)遍歷這個(gè)queue,將queue中元素對(duì)應(yīng)的map中的entry清理掉。否則map永遠(yuǎn)也不會(huì)釋放這些已經(jīng)失去意義的entry,這就會(huì)造成內(nèi)存泄漏。
WeakReference常用場(chǎng)景下的內(nèi)存泄漏問(wèn)題
以ThreadLocal.ThreadLocalMap為例,經(jīng)??吹饺缦抡f(shuō)法:ThreadLocalMap中, Entry extends WeakReference<ThreadLocal> ,Entry的key也即ThreadLocal實(shí)例本身會(huì)被賦值給WeakReference的referent,JVM執(zhí)行GC時(shí),只要遇到弱引用就會(huì)將其斷開(kāi),即設(shè)置 referent=null ,則Entry的key變null了,那么Entry的value就已經(jīng)沒(méi)有意義了,也應(yīng)該能被GC回收掉,否則就是內(nèi)存泄漏。但是如果我們不主動(dòng)調(diào)用 threadlocal.remove() ,不主動(dòng)設(shè)置vlaue=null,那么被value引用的對(duì)象就會(huì)一直到線程銷(xiāo)毀都無(wú)法被GC回收掉,這就是ThreadLocal會(huì)造成內(nèi)存泄漏的說(shuō)法。
但是,針對(duì)這種情況,ThreadLocal也不是什么都沒(méi)做。
在ThreadLocal實(shí)例每次執(zhí)行set(T value)方法時(shí)(首次創(chuàng)建線程的threadLocals對(duì)象時(shí)除外),最后都會(huì)執(zhí)行以下代碼
if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash();
cleanSomeSlots方法代碼如下:
private boolean cleanSomeSlots(int i, int n) { boolean removed = false; Entry[] tab = table; int len = tab.length; do { i = nextIndex(i, len); Entry e = tab[i]; if (e != null && e.get() == null) { // 檢查key是否為null n = len; removed = true; i = expungeStaleEntry(i); // 清理value } } while ( (n >>>= 1) != 0); return removed; }
可見(jiàn),除了第一次,其后每次向線程的threadLocals 中添加entry時(shí),都會(huì)清理在此之前被GC掉的的key對(duì)應(yīng)的entry。
也就是說(shuō),通常情況下,每個(gè)線程最多只會(huì)存在一個(gè)應(yīng)該被GC回收但未能被回收的泄漏的對(duì)象。
如果這個(gè)對(duì)象非常大,占用JVM內(nèi)存空間較多,那么就影響較大。
如果線程非常多,每個(gè)線程都有一個(gè)泄漏的對(duì)象,那么影響也較大。
到此這篇關(guān)于關(guān)于弱引用WeakReference所引用的對(duì)象的回收規(guī)則的文章就介紹到這了,更多相關(guān)弱引用WeakReference回收規(guī)則內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解java中面向?qū)ο笤O(shè)計(jì)模式類(lèi)與類(lèi)的關(guān)系
這篇文章主要介紹了java面向?qū)ο笤O(shè)計(jì)模式中類(lèi)與類(lèi)之間的關(guān)系,下面小編和大家一起來(lái)學(xué)習(xí)一下吧2019-05-05SpringBoot實(shí)現(xiàn)配置文件的替換
這篇文章主要介紹了SpringBoot實(shí)現(xiàn)配置文件的替換,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12java理論基礎(chǔ)Stream管道流狀態(tài)與并行操作
這篇文章主要為大家介紹了java理論基礎(chǔ)Stream管道流狀態(tài)與并行操作,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2022-03-03最新IntelliJ?IDEA?2022配置?Tomcat?8.5?的詳細(xì)步驟演示
這篇文章主要介紹了IntelliJ?IDEA?2022?詳細(xì)配置?Tomcat?8.5?步驟演示,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08Java Swing中的工具欄(JToolBar)和分割面版(JSplitPane)組件使用案例
這篇文章主要介紹了Java Swing中的工具欄(JToolBar)和分割面版(JSplitPane)組件使用案例,本文直接給出代碼實(shí)例和效果截圖,需要的朋友可以參考下2014-10-10SpringBoot?2.x整合Log4j2日志的詳細(xì)步驟
log4j2優(yōu)越的性能其原因在于log4j2使用了LMAX,一個(gè)無(wú)鎖的線程間通信庫(kù)代替了,logback和log4j之前的隊(duì)列,并發(fā)性能大大提升,下面這篇文章主要給大家介紹了關(guān)于SpringBoot?2.x整合Log4j2日志的相關(guān)資料,需要的朋友可以參考下2022-10-10