1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        如何排查Java內(nèi)存泄漏?看完我給跪了!

        共 8589字,需瀏覽 18分鐘

         ·

        2021-07-29 04:02

        往期熱門文章:
        1、當(dāng) Docker 遇上 IDEA ,生產(chǎn)力徹底炸裂了
        2、如何把Spring Boot的Jar包做成exe?超詳細(xì)教程來了!
        3、徹底搞懂 Nginx 的五大應(yīng)用場景
        4、SpringBoot+Dubbo是如何搞定微服務(wù),成功應(yīng)對高并發(fā)的?
        5、為什么有些大公司技術(shù)弱爆了?
        作者:Jose Ferreirade Souza Filho
        譯者:Emma

        來源:www.toptal.com/java/hunting-memory-leaks-in-java

        沒有經(jīng)驗(yàn)的程序員經(jīng)常認(rèn)為Java的自動垃圾回收完全使他們免于擔(dān)心內(nèi)存管理。這是一個常見的誤解:雖然垃圾收集器做得很好,但即使是最好的程序員也完全有可能成為嚴(yán)重破壞內(nèi)存泄漏的犧牲品。讓我解釋一下。

        當(dāng)不必要地維護(hù)不再需要的對象引用時,會發(fā)生內(nèi)存泄漏。這些泄漏很糟糕。首先,當(dāng)程序消耗越來越多的資源時,它們會對計算機(jī)施加不必要的壓力。更糟糕的是,檢測這些泄漏可能很困難:靜態(tài)分析通常很難精確識別這些冗余引用,現(xiàn)有的泄漏檢測工具會跟蹤和報告有關(guān)單個對象的細(xì)粒度信息,產(chǎn)生難以解釋且缺乏精確度的結(jié)果。

        換句話說,泄漏要么太難以識別,要么使用太過具體而無用術(shù)語來識別。

        實(shí)際上有四類內(nèi)存問題具有相似和重疊的特征,但原因和解決方案各不相同:

        • Performance(性能):通常與過多的對象創(chuàng)建和刪除,垃圾收集的長時間延遲,過多的操作系統(tǒng)頁面交換等相關(guān)聯(lián)。

        • Resource constraints(資源約束):當(dāng)可用內(nèi)存很少或內(nèi)存過于分散而無法分配大對象時 - 這可能是本機(jī)的,或者更常見的是與Java堆相關(guān)。

        • Java heap leaks(java堆泄漏):經(jīng)典的內(nèi)存泄漏,Java對象在不釋放的情況下不斷創(chuàng)建。這通常是由潛在對象引用引起的。

        • Native memory leaks(本機(jī)內(nèi)存泄漏):與Java堆之外的任何不斷增長的內(nèi)存利用率相關(guān)聯(lián),例如由JNI代碼,驅(qū)動程序甚至JVM分配。

        在這個內(nèi)存管理教程中,我將專注于Java堆漏洞,并概述一種基于Java VisualVM報告檢測此類泄漏的方法,并利用可視化界面在運(yùn)行時分析基于Java技術(shù)的應(yīng)用程序。

        但在您可以預(yù)防和發(fā)現(xiàn)內(nèi)存泄漏之前,您應(yīng)該了解它們的發(fā)生方式和原因。(注意:如果你能很好地處理錯綜復(fù)雜的內(nèi)存泄漏,你可以跳過。)

        1. 內(nèi)存泄漏:基礎(chǔ)

        對于初學(xué)者來說,將內(nèi)存泄漏視為一種疾病,將Java的OutOfMemoryError(簡稱OOM)視為一種癥狀。但與任何疾病一樣,并非所有OOM都意味著內(nèi)存泄漏:由于生成大量局部變量或其他此類事件,OOM可能會發(fā)生。另一方面,并非所有內(nèi)存泄漏都必然表現(xiàn)為OOM,特別是在桌面應(yīng)用程序或客戶端應(yīng)用程序(沒有重新啟動時運(yùn)行很長時間)的情況下。

        將內(nèi)存泄漏視為疾病,將OutOfMemoryError視為癥狀。但并非所有OutOfMemoryErrors都意味著內(nèi)存泄漏,并非所有內(nèi)存泄漏都表現(xiàn)為OutOfMemoryErrors。

        為什么這些泄漏如此糟糕?除此之外,程序執(zhí)行期間泄漏的內(nèi)存塊通常會降低系統(tǒng)性能,因?yàn)榉峙涞词褂玫膬?nèi)存塊必須在系統(tǒng)耗盡空閑物理內(nèi)存時進(jìn)行換出。最終,程序甚至可能耗盡其可用的虛擬地址空間,從而導(dǎo)致OOM。

        2. 解密OutOfMemoryError

        如上所述,OOM是內(nèi)存泄漏的常見指示。實(shí)質(zhì)上,當(dāng)沒有足夠的空間來分配新對象時,會拋出錯誤。當(dāng)垃圾收集器找不到必要的空間,并且堆不能進(jìn)一步擴(kuò)展,會多次嘗試。因此,會出現(xiàn)錯誤以及堆棧跟蹤。

        診斷OOM的第一步是確定錯誤的實(shí)際含義。這聽起來很清楚,但答案并不總是那么清晰。例如:OOM是否是因?yàn)镴ava堆已滿而出現(xiàn),還是因?yàn)楸緳C(jī)堆已滿?為了幫助您回答這個問題,讓我們分析一些可能的錯誤消息:

        • java.lang.OutOfMemoryError: Java heap space

        • java.lang.OutOfMemoryError: PermGen space

        • java.lang.OutOfMemoryError: Requested array size exceeds VM limit

        • java.lang.OutOfMemoryError: request bytes for . Out of swap space?

        • java.lang.OutOfMemoryError: (Native method)

        2.1.“Java heap space”

        此錯誤消息不一定意味著內(nèi)存泄漏。實(shí)際上,問題可能與配置問題一樣簡單。

        例如,我負(fù)責(zé)分析一直產(chǎn)生這種類型的OutOfMemoryError的應(yīng)用程序。經(jīng)過一番調(diào)查后,我發(fā)現(xiàn)罪魁禍?zhǔn)资顷嚵袑?shí)例化,因?yàn)樾枰嗟膬?nèi)存;在這種情況下,并不是應(yīng)用程序的錯,而是應(yīng)用程序服務(wù)器依賴于默認(rèn)的堆太小了。我通過調(diào)整JVM的內(nèi)存參數(shù)解決了這個問題。

        在其他情況下,特別是對于長期存在的應(yīng)用程序,該消息可能表明我們無意中持有對象的引用,從而阻止垃圾收集器清理它們。這時Java語言等同于內(nèi)存泄漏。(注意:應(yīng)用程序調(diào)用的API也可能無意中持有對象引用。)

        這些“Java堆空間”O(jiān)OM的另一個潛在來源是使用finalizers。如果類具有finalize方法,則在垃圾收集時該類型的對象不會被回收。而是在垃圾收集之后,稍后對象將排隊等待最終確定。在Sun實(shí)現(xiàn)中,finalizers由守護(hù)線程執(zhí)行。如果finalizers線程無法跟上finalization隊列,那么Java堆可能會填滿并且可能拋出OOM。

        2.2. “PermGen space”

        此錯誤消息表明永久代已滿。永久代是存儲類和方法對象的堆的區(qū)域。如果應(yīng)用程序加載了大量類,則可能需要使用-XX:MaxPermSize選項(xiàng)增加永久代的大小。

        Interned java.lang.String對象也存儲在永久代中。java.lang.String類維護(hù)一個字符串池。調(diào)用實(shí)習(xí)方法時,該方法檢查池以查看是否存在等效字符串。如果是這樣,它由實(shí)習(xí)方法返回;如果沒有,則將字符串添加到池中。更準(zhǔn)確地說,java.lang.String.intern方法返回一個字符串的規(guī)范表示;結(jié)果是對該字符串顯示為文字時將返回的同一個類實(shí)例的引用。如果應(yīng)用程序?qū)嵗罅孔址?,則可能需要增加永久代的大小。

        注意:您可以使用jmap -permgen命令打印與永久生成相關(guān)的統(tǒng)計信息,包括有關(guān)內(nèi)部化String實(shí)例的信息。

        2.3.“Requested array size exceeds VM limit”

        此錯誤表示應(yīng)用程序(或該應(yīng)用程序使用的API)嘗試分配大于堆大小的數(shù)組。例如,如果應(yīng)用程序嘗試分配512MB的數(shù)組但最大堆大小為256MB,則將拋出此錯誤消息的OOM。在大多數(shù)情況下,問題是配置問題或應(yīng)用程序嘗試分配海量數(shù)組時導(dǎo)致的錯誤。

        2.4. “Request bytes for . Out of swap space?”

        此消息似乎是一個OOM。但是,當(dāng)本機(jī)堆的分配失敗并且本機(jī)堆可能將被耗盡時,HotSpot VM會拋出此異常。消息中包括失敗請求的大?。ㄒ宰止?jié)為單位)以及內(nèi)存請求的原因。在大多數(shù)情況下,是報告分配失敗的源模塊的名稱。

        如果拋出此類型的OOM,則可能需要在操作系統(tǒng)上使用故障排除實(shí)用程序來進(jìn)一步診斷問題。在某些情況下,問題甚至可能與應(yīng)用程序無關(guān)。例如,您可能會在以下情況下看到此錯誤:

        • 操作系統(tǒng)配置的交換空間不足。

        • 系統(tǒng)上的另一個進(jìn)程是消耗所有可用的內(nèi)存資源。

        由于本機(jī)泄漏,應(yīng)用程序也可能失?。ɡ?,如果某些應(yīng)用程序或庫代碼不斷分配內(nèi)存但無法將其釋放到操作系統(tǒng))。

        2.5. Native method

        如果您看到此錯誤消息并且堆棧跟蹤的頂部框架是本機(jī)方法,則該本機(jī)方法遇到分配失敗。此消息與上一個消息之間的區(qū)別在于,在JNI或本機(jī)方法中檢測到Java內(nèi)存分配失敗,而不是在Java VM代碼中檢測到。

        如果拋出此類型的OOM,您可能需要在操作系統(tǒng)上使用實(shí)用程序來進(jìn)一步診斷問題。

        2.6. Application Crash Without OOM

        有時,應(yīng)用程序可能會在從本機(jī)堆分配失敗后很快崩潰。如果您運(yùn)行的本機(jī)代碼不檢查內(nèi)存分配函數(shù)返回的錯誤,則會發(fā)生這種情況。

        例如,如果沒有可用內(nèi)存,malloc系統(tǒng)調(diào)用將返回NULL。如果未檢查malloc的返回,則應(yīng)用程序在嘗試訪問無效的內(nèi)存位置時可能會崩潰。根據(jù)具體情況,可能很難定位此類問題。

        在某些情況下,致命錯誤日志或崩潰轉(zhuǎn)儲的信息就足以診斷問題。如果確定崩潰的原因是某些內(nèi)存分配中缺少錯誤處理,那么您必須找到所述分配失敗的原因。與任何其他本機(jī)堆問題一樣,系統(tǒng)可能配置了但交換空間不足,另一個進(jìn)程可能正在消耗所有可用內(nèi)存資源等。

        3. 泄漏診斷

        在大多數(shù)情況下,診斷內(nèi)存泄漏需要非常詳細(xì)地了解相關(guān)應(yīng)用程序。警告:該過程可能很長并且是迭代的。

        我們尋找內(nèi)存泄漏的策略將相對簡單:

        1. 識別癥狀

        2. 啟用詳細(xì)垃圾回收

        3. 啟用分析

        4. 分析蹤跡

        3.1. 識別癥狀

        正如所討論的,在許多情況下,Java進(jìn)程最終會拋出一個OOM運(yùn)行時異常,這是一個明確的指示,表明您的內(nèi)存資源已經(jīng)耗盡。在這種情況下,您需要區(qū)分正常的內(nèi)存耗盡和泄漏。分析OOM的消息并嘗試根據(jù)上面提供的討論找到罪魁禍?zhǔn)住?/span>

        通常,如果Java應(yīng)用程序請求的存儲空間超過運(yùn)行時堆提供的存儲空間,則可能是由于設(shè)計不佳導(dǎo)致的。例如,如果應(yīng)用程序創(chuàng)建映像的多個副本或?qū)⑽募虞d到數(shù)組中,則當(dāng)映像或文件非常大時,它將耗盡存儲空間。這是正常的資源耗盡。該應(yīng)用程序按設(shè)計工作(雖然這種設(shè)計顯然是愚蠢的)。

        但是,如果應(yīng)用程序在處理相同類型的數(shù)據(jù)時穩(wěn)定地增加其內(nèi)存利用率,則可能會發(fā)生內(nèi)存泄漏。

        3.2. 啟用詳細(xì)垃圾收集

        斷言確實(shí)存在內(nèi)存泄漏的最快方法之一是啟用詳細(xì)垃圾回收。通??梢酝ㄟ^檢查verbosegc輸出中的模式來識別內(nèi)存約束問題。

        具體來說,-verbosegc參數(shù)允許您在每次垃圾收集(GC)過程開始時生成跟蹤。也就是說,當(dāng)內(nèi)存被垃圾收集時,摘要報告會打印到標(biāo)準(zhǔn)錯誤,讓您了解內(nèi)存的管理方式。

        這是使用-verbosegc選項(xiàng)生成的一些典型輸出:

        image

        此GC跟蹤文件中的每個塊(或節(jié))按遞增順序編號。要理解這種跟蹤,您應(yīng)該查看連續(xù)的分配失敗節(jié),并查找隨著時間的推移而減少的釋放內(nèi)存(字節(jié)和百分比),同時總內(nèi)存(此處,19725304)正在增加。這些是內(nèi)存耗盡的典型跡象。

        3.3. 啟用分析

        不同的JVM提供了生成跟蹤文件以反映堆活動的不同方法,這些方法通常包括有關(guān)對象類型和大小的詳細(xì)信息。這稱為分析堆。

        3.4. 分析路徑

        本文重點(diǎn)介紹Java VisualVM生成的跟蹤。跟蹤可以有不同的格式,因?yàn)樗鼈兛梢杂刹煌腏ava內(nèi)存泄漏檢測工具生成,但它們背后的想法總是相同的:在堆中找到不應(yīng)該存在的對象塊,并確定這些對象是否累積而不是釋放。特別感興趣的是每次在Java應(yīng)用程序中觸發(fā)某個事件時已知的臨時對象。應(yīng)該僅存少量,但存在許多對象實(shí)例,通常表示應(yīng)用程序出現(xiàn)錯誤。

        最后,解決內(nèi)存泄漏需要您徹底檢查代碼。了解對象泄漏的類型可能對此非常有用,并且可以大大加快調(diào)試速度。

        4. 垃圾收集如何在JVM中運(yùn)行?

        在我們開始分析具有內(nèi)存泄漏問題的應(yīng)用程序之前,讓我們首先看看垃圾收集在JVM中的工作原理。

        JVM使用一種稱為跟蹤收集器的垃圾收集器,它基本上通過暫停它周圍的世界來操作,標(biāo)記所有根對象(由運(yùn)行線程直接引用的對象),并遵循它們的引用,標(biāo)記它沿途看到的每個對象。

        Java基于分代假設(shè)-實(shí)現(xiàn)了一種稱為分代垃圾收集器的東西,該假設(shè)表明創(chuàng)建的大多數(shù)對象被快速丟棄,而未快速收集的對象可能會存在一段時間。

        基于此假設(shè),[Java將對象分為多代](http://www.oracle.com/technetwork/java/gc-tuning-5-138395.html#1.1. Generations|outline)。這是一個視覺解釋:

        image

        • Young Generation -這是對象的開始。它有兩個子代

          • Eden Space -對象從這里開始。大多數(shù)物體都是在Eden Space中創(chuàng)造和銷毀的。在這里,GC執(zhí)行Minor GCs,這是優(yōu)化的垃圾收集。執(zhí)行Minor GC時,對仍然需要的對象的任何引用都將遷移到其中一個survivors空間(S0或S1)。

          • Survivor Space (S0 and S1)-幸存Eden Space的對象最終來到這里。其中有兩個,在任何給定時間只有一個正在使用(除非我們有嚴(yán)重的內(nèi)存泄漏)。一個被指定為空,另一個被指定為活動,與每個GC循環(huán)交替。

        • Tenured Generation -也被稱為老年代(圖2中的舊空間),這個空間容納存活較長的對象,使用壽命更長(如果它們活得足夠長,則從Survivor空間移過來)。填充此空間時,GC會執(zhí)行完整GC,這會在性能方面降低成本。如果此空間無限制地增長,則JVM將拋出OutOfMemoryError - Java堆空間。

        • Permanent Generation -作為與終身代密切相關(guān)的第三代,永久代是特殊的,因?yàn)樗4嫣摂M機(jī)所需的數(shù)據(jù),以描述在Java語言級別上沒有等價的對象。例如,描述類和方法的對象存儲在永久代中。

        Java足夠聰明,可以為每一代應(yīng)用不同的垃圾收集方法。使用名為Parallel New Collector的跟蹤復(fù)制收集器處理年輕代。這個收集器阻止了這個世界,但由于年輕一代通常很小,所以暫停很短暫。

        有關(guān)JVM代及其工作原理的更多信息,請查閱Memory Management in the Java HotSpot? Virtual Machine 。

        5. 檢測內(nèi)存泄漏

        要查找內(nèi)存泄漏并消除它們,您需要合適的內(nèi)存泄漏工具。是時候使用Java VisualVM檢測并刪除此類泄漏。

        5.1. 使用Java VisualVM遠(yuǎn)程分析堆

        VisualVM是一種工具,它提供了一個可視化界面,用于查看有關(guān)基于Java技術(shù)的應(yīng)用程序運(yùn)行時的詳細(xì)信息。

        使用VisualVM,您可以查看與本地應(yīng)用程序和遠(yuǎn)程主機(jī)上運(yùn)行的應(yīng)用程序相關(guān)的數(shù)據(jù)。您還可以捕獲有關(guān)JVM軟件實(shí)例的數(shù)據(jù),并將數(shù)據(jù)保存到本地系統(tǒng)。

        為了從Java VisualVM的所有功能中受益,您應(yīng)該運(yùn)行Java平臺標(biāo)準(zhǔn)版(Java SE)版本6或更高版本。

        Related: Why You Need to Upgrade to Java 8 Already

        5.2. 為JVM啟用遠(yuǎn)程連接

        在生產(chǎn)環(huán)境中,通常很難訪問運(yùn)行代碼的實(shí)際機(jī)器。幸運(yùn)的是,我們可以遠(yuǎn)程分析我們的Java應(yīng)用程序。

        首先,我們需要在目標(biāo)機(jī)器上授予自己JVM訪問權(quán)限。為此,請使用以下內(nèi)容創(chuàng)建名為jstatd.all.policy的文件:

        1. grant codebase "file:${java.home}/../lib/tools.jar"{


        2. permission java.security.AllPermission;


        3. };

        創(chuàng)建文件后,我們需要使用jstatd - Virtual Machine jstat Daemon工具啟用與目標(biāo)VM的遠(yuǎn)程連接,如下所示:

        1. jstatd -p <PORT_NUMBER> -J-Djava.security.policy=<PATH_TO_POLICY_FILE>

        例如:

        1. jstatd -p 1234-J-Djava.security.policy=D:\jstatd.all.policy

        通過在目標(biāo)VM中啟動jstatd,我們能夠連接到目標(biāo)計算機(jī)并遠(yuǎn)程分析應(yīng)用程序的內(nèi)存泄漏問題。

        5.3. 連接到遠(yuǎn)程主機(jī)

        在客戶端計算機(jī)中,打開提示并鍵入jvisualvm以打開VisualVM工具。

        接下來,我們必須在VisualVM中添加遠(yuǎn)程主機(jī)。當(dāng)目標(biāo)JVM啟用以允許來自具有J2SE 6或更高版本的另一臺計算機(jī)的遠(yuǎn)程連接時,我們啟動Java VisualVM工具并連接到遠(yuǎn)程主機(jī)。如果與遠(yuǎn)程主機(jī)的連接成功,我們將看到在目標(biāo)JVM中運(yùn)行的Java應(yīng)用程序,如下所示:

        要在應(yīng)用程序上運(yùn)行內(nèi)存分析器,我們只需在側(cè)面板中雙擊其名稱即可。

        現(xiàn)在我們已經(jīng)設(shè)置了內(nèi)存分析器,讓我們研究一個內(nèi)存泄漏問題的應(yīng)用程序,我們稱之為MemLeak。

        6. MemLeak

        當(dāng)然,有很多方法可以在Java中創(chuàng)建內(nèi)存泄漏。為簡單起見,我們將一個類定義為HashMap中的鍵,但我們不會定義equals()和hashcode()方法。

        HashMap是Map接口的哈希表實(shí)現(xiàn),因此它定義了鍵和值的基本概念:每個值都與唯一鍵相關(guān),因此如果給定鍵值對的鍵已經(jīng)存在于HashMap,它的當(dāng)前值被替換。

        我們的密鑰類必須提供equals()和hashcode()方法的正確實(shí)現(xiàn)。沒有它們,就無法保證會生成一個好的密鑰。

        通過不定義equals()和hashcode()方法,我們一遍又一遍地向HashMap添加相同的鍵,而不是按原樣替換鍵,HashMap不斷增長,無法識別這些相同的鍵并拋出OutOfMemoryError 。

        MemLeak類:

        1. package com.post.memory.leak;


        2. import java.util.Map;


        3. publicclassMemLeak{

        4. publicfinalString key;


        5. publicMemLeak(String key) {

        6. this.key =key;

        7. }


        8. publicstaticvoid main(String args[]) {

        9. try{

        10. Map map = System.getProperties();

        11. for(;;) {

        12. map.put(newMemLeak("key"), "value");

        13. }

        14. } catch(Exception e) {

        15. e.printStackTrace();

        16. }

        17. }

        18. }

        注意:內(nèi)存泄漏不是由于第14行的無限循環(huán):無限循環(huán)可能導(dǎo)致資源耗盡,但不會導(dǎo)致內(nèi)存泄漏。如果我們已經(jīng)正確實(shí)現(xiàn)了equals()和hashcode()方法,那么即使使用無限循環(huán),代碼也能正常運(yùn)行,因?yàn)槲覀冊贖ashMap中只有一個元素。

        (對于那些感興趣的人,這里有一些(故意)產(chǎn)生泄漏的替代方法。)

        7. 使用Java VisualVM

        使用Java VisualVM,我們可以對Java Heap進(jìn)行內(nèi)存監(jiān)視,并確定其行為是否存在內(nèi)存泄漏。

        這是剛剛初始化后MemLeak的Java堆分析器的圖形表示(回想一下我們對各代的討論):

        image

        僅僅30秒之后,老年代幾乎已滿,表明即使使用Full GC,老年代也在不斷增長,這是內(nèi)存泄漏的明顯跡象。

        檢測此泄漏原因的一種方法如下圖所示(單擊放大),使用帶有heapdump的Java VisualVM生成。在這里,我們看到50%的Hashtable $ Entry對象在堆中,而第二行指向MemLeak類。因此,內(nèi)存泄漏是由MemLeak類中使用的哈希表引起的。

        image

        最后,在OutOfMemoryError之后觀察Java Heap,其中Young和Old代完全填滿。

        image

        8. 結(jié)束語

        內(nèi)存泄漏是最難解決的Java應(yīng)用程序問題之一,因?yàn)榘Y狀多種多樣且難以重現(xiàn)。在這里,我們概述了一種逐步發(fā)現(xiàn)內(nèi)存泄漏并確定其來源的方法。但最重要的是,仔細(xì)閱讀您的錯誤消息并注意堆棧跟蹤 - 并非所有泄漏都像它們出現(xiàn)的那樣簡單。

        9. 附錄

        與Java VisualVM一起,還有其他幾種可以執(zhí)行內(nèi)存泄漏檢測的工具。許多泄漏檢測器通過攔截對存儲器管理例程的調(diào)用在庫級別操作。例如,HPROF是一個與Java 2平臺標(biāo)準(zhǔn)版(J2SE)捆綁在一起的簡單命令行工具,用于堆和CPU分析??梢灾苯臃治鯤PROF的輸出,或?qū)⑵溆米鱆HAT等其他工具的輸入。當(dāng)我們使用Java 2 Enterprise Edition(J2EE)應(yīng)用程序時,有許多堆轉(zhuǎn)儲分析器解決方案更友好,例如IBM Heapdumps for Websphere應(yīng)用程序服務(wù)器。 

        最近熱文閱讀:

        1、當(dāng) Docker 遇上 IDEA ,生產(chǎn)力徹底炸裂了
        2、如何把Spring Boot的Jar包做成exe?超詳細(xì)教程來了!
        3、徹底搞懂 Nginx 的五大應(yīng)用場景
        4、推薦60個相見恨晚的神器工具
        5、為什么有些大公司技術(shù)弱爆了?
        6、這 40 道 Redis 面試題讓你不再慌(附答案)
        7、優(yōu)秀的代碼都是如何分層的?
        8、IDEA 中的熱部署神器!
        9、SpringBean默認(rèn)是單例的,高并發(fā)情況下,如何保證并發(fā)安全?
        10、知乎高贊:拼多多和國家電網(wǎng),選哪個?
        關(guān)注公眾號,你想要的Java都在這里

        瀏覽 17
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            国内精品久久久久久久久久 | 黄色特级一级片 | 久久无码电影 | 性插网站| 免费一级A毛片 | 国内精品久久久久 | 国产精品免费人成网站酒店 | 天堂资源中文在线 | 亚洲天堂无码在线 | 最近亚洲中文字幕在线 |