java基础(一):谈谈java内存管理与垃圾回收机制
看了很多java內(nèi)存管理的文章或者博客,寫的要么籠統(tǒng),要么劃分的不正確,且很多文章都千篇一律。例如部分地方將jvm籠統(tǒng)的分為堆、棧、程序計數(shù)器,這么分太過于籠統(tǒng),無法清晰的闡述java的內(nèi)存管理模型;部分地方將jvm分為堆、棧、程序計數(shù)器、常量池、方法區(qū),這么分,很全面,但是過于混亂,因?yàn)檫@些區(qū)域之間存在并列和包含關(guān)系,而最近再次刷《Java Thinking》這本書的時候,從新學(xué)習(xí)了關(guān)于內(nèi)存模型的內(nèi)容。基于上述原因,我決定來談?wù)刯vm虛擬機(jī)的內(nèi)存劃分。
至于垃圾回收機(jī)制,個人覺得應(yīng)該和內(nèi)存管理一同討論,所以在此,我也將內(nèi)存回收機(jī)制拿出來進(jìn)行一起討論。
本片博客的大致結(jié)構(gòu):1.內(nèi)存區(qū)域;2.內(nèi)存回收機(jī)制;3.垃圾回收器
1.內(nèi)存區(qū)域
首先看看官方的內(nèi)存模型圖片:圖片來自《Java虛擬機(jī)規(guī)范(第2版)》
1.1.程序計數(shù)器:
程序計數(shù)器是一個比較小的內(nèi)存區(qū)域,用于指示當(dāng)前線程所執(zhí)行的字節(jié)碼執(zhí)行到了第幾行,可以理解為是當(dāng)前線程的行號指示器。字節(jié)碼解釋器在工作時,會通過改變這個計數(shù)器的值來取下一條語句指令。由于Java虛擬機(jī)的多線程是通過線程輪流切換并分配處理器執(zhí)行時間的方式來實(shí)現(xiàn)的,在任何一個確定的時刻,一個處理器(對于多核處理器來說是一個內(nèi)核)只會執(zhí)行一條線程中的指令。因此,為了線程切換后能恢復(fù)到正確的執(zhí)行位置,每條線程都需要有一個獨(dú)立的程序計數(shù)器,各條線程之間的計數(shù)器互不影響,獨(dú)立存儲,我們稱這類內(nèi)存區(qū)域?yàn)椤熬€程私有”的內(nèi)存。 如果線程正在執(zhí)行的是一個Java方法,這個計數(shù)器記錄的是正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;如果正在執(zhí)行的是Natvie方法,這個計數(shù)器值則為空(Undefined),由于程序計數(shù)器只是記錄當(dāng)前指令地址,所以不存在內(nèi)存溢出的情況,因此,程序計數(shù)器也是所有JVM內(nèi)存區(qū)域中唯一一個沒有定義OutOfMemoryError的區(qū)域。
1.2.棧
棧分為虛擬機(jī)棧和本地方法棧,既然都是棧,那么就具有相同的特性:私有的,線程安全,棧中存儲了基本數(shù)據(jù)類型和對象的引用
1.2.1.java虛擬機(jī)棧
一個線程的每個方法在執(zhí)行的同時,都會創(chuàng)建一個棧幀(Statck Frame),棧幀中存儲的有局部變量表、操作站、動態(tài)鏈接、方法出口等,當(dāng)方法被調(diào)用時,棧幀在JVM棧中入棧,當(dāng)方法執(zhí)行完成時,棧幀出棧。實(shí)際上每個方法的調(diào)用相當(dāng)于是棧幀的入棧已經(jīng)出棧操作。局部變量表中存儲著方法的相關(guān)局部變量,包括各種基本數(shù)據(jù)類型,對象的引用,返回地址等。在局部變量表中,只有l(wèi)ong和double類型會占用2個局部變量空間(Slot,對于32位機(jī)器,一個Slot就是32個bit),其它都是1個Slot。需要注意的是,局部變量表是在編譯時就已經(jīng)確定好的,方法運(yùn)行所需要分配的空間在棧幀中是完全確定的,在方法的生命周期內(nèi)都不會改變。虛擬機(jī)棧中定義了兩種異常,如果線程調(diào)用的棧深度大于虛擬機(jī)允許的最大深度,則拋出StatckOverFlowError(棧溢出);不過多數(shù)Java虛擬機(jī)都允許動態(tài)擴(kuò)展虛擬機(jī)棧的大小(有少部分是固定長度的),所以線程可以一直申請棧,直到內(nèi)存不足,此時,會拋出OutOfMemoryError(內(nèi)存溢出)。
1.2.2.本地方法棧
本地方法棧在作用,運(yùn)行機(jī)制,異常類型等方面都與虛擬機(jī)棧相同,唯一的區(qū)別是:虛擬機(jī)棧是執(zhí)行Java方法的,而本地方法棧是用來執(zhí)行native方法的,如調(diào)用C++,C#編寫的方法。目前在很多虛擬機(jī)中(如Sun的JDK默認(rèn)的HotSpot虛擬機(jī)),會將本地方法棧與虛擬機(jī)棧放在一起使用。
1.3.堆
堆是線程共享的,存儲的是對象的實(shí)例,有的地方寫存儲的是對象的實(shí)例和數(shù)組,實(shí)際上數(shù)組是特殊的類,那么數(shù)組也屬于對象的實(shí)例。在JVM所管理的內(nèi)存中,堆區(qū)是最大的一塊,堆區(qū)也是Java GC發(fā)生的主要場所,在虛擬機(jī)啟動時創(chuàng)建,所以堆也成為GC堆,按照java垃圾回收的概念,堆又可以分為新生代和老年代,永久代(只有部分虛擬機(jī)中有永久代的概念,sun公司的HotSpot虛擬機(jī)就有,其它的一般沒有,而hotSpot應(yīng)用的比較廣泛),其中新生代又可以分為Eden,From Survivor,To Survivor,其中每一塊具體的作用在垃圾回收模塊會詳細(xì)介紹。原則上講,所有的對象都在堆區(qū)上分配內(nèi)存,但是隨著JIT編譯器的發(fā)展與逃逸分析技術(shù)的逐漸成熟,棧上分配、標(biāo)量替換優(yōu)化技術(shù)將會導(dǎo)致一些微妙的變化發(fā)生,所有的對象都分配在堆上也漸漸變得不是那么“絕對”了。一般的,根據(jù)Java虛擬機(jī)規(guī)范規(guī)定,堆內(nèi)存需要在邏輯上是連續(xù)的(在物理上不需要),在實(shí)現(xiàn)時,可以是固定大小的,也可以是可擴(kuò)展的,目前主流的虛擬機(jī)都是可擴(kuò)展的(通過-Xmx和-Xms控制)。如果在執(zhí)行垃圾回收之后,仍沒有足夠的內(nèi)存分配,也不能再擴(kuò)展,將會拋出OutOfMemoryError:Java heap space異。
1.4.方法區(qū)
方法區(qū)(Method Area)與Java堆一樣,是各個線程共享的內(nèi)存區(qū)域,它用于存儲已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量、即時編譯器編譯后的代碼等數(shù)據(jù)。雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的應(yīng)該是與Java堆區(qū)分開來。對于習(xí)慣在HotSpot虛擬機(jī)上開發(fā)和部署程序的開發(fā)者來說,很多人愿意把方法區(qū)稱為“永久代”(Permanent Generation),本質(zhì)上兩者并不等價,僅僅是因?yàn)镠otSpot虛擬機(jī)的設(shè)計團(tuán)隊(duì)選擇把GC分代收集擴(kuò)展至方法區(qū),或者說使用永久代來實(shí)現(xiàn)方法區(qū)而已。對于其他虛擬機(jī)(如BEA JRockit、IBM J9等)來說是不存在永久代的概念的。即使是HotSpot虛擬機(jī)本身,根據(jù)官方發(fā)布的路線圖信息,現(xiàn)在也有放棄永久代并“搬家”至Native Memory來實(shí)現(xiàn)方法區(qū)的規(guī)劃了。Java虛擬機(jī)規(guī)范對這個區(qū)域的限制非常寬松,除了和Java堆一樣不需要連續(xù)的內(nèi)存和可以選擇固定大小或者可擴(kuò)展外(通過設(shè)置permsize和MaxPermsize設(shè)置方法區(qū)的初始化大小和最大內(nèi)存),還可以選擇不實(shí)現(xiàn)垃圾收集。相對而言,垃圾收集行為在這個區(qū)域是比較少出現(xiàn)的,但并非數(shù)據(jù)進(jìn)入了方法區(qū)就如永久代的名字一樣“永久”存在了。這個區(qū)域的內(nèi)存回收目標(biāo)主要是針對常量池的回收和對類型的卸載,一般來說這個區(qū)域的回收“成績”比較難以令人滿意,尤其是類型的卸載,條件相當(dāng)苛刻,但是這部分區(qū)域的回收確實(shí)是有必要的。在Sun公司的BUG列表中,曾出現(xiàn)過的若干個嚴(yán)重的BUG就是由于低版本的HotSpot虛擬機(jī)對此區(qū)域未完全回收而導(dǎo)致內(nèi)存泄漏。 根據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時,將拋出OutOfMemoryError異常。
1.5.常量池
運(yùn)行時常量池(Runtime Constant Pool)是方法區(qū)的一部分。Class文件中除了有類的版本、字段、方法、接口等描述等信息外,還有一項(xiàng)信息是常量池(Constant Pool Table),用于存放編譯期生成的各種字面量和符號引用,這部分內(nèi)容將在類加載后存放到方法區(qū)的運(yùn)行時常量池中。 Java虛擬機(jī)對Class文件的每一部分(自然也包括常量池)的格式都有嚴(yán)格的規(guī)定,每一個字節(jié)用于存儲哪種數(shù)據(jù)都必須符合規(guī)范上的要求,這樣才會被虛擬機(jī)認(rèn)可、裝載和執(zhí)行。但對于運(yùn)行時常量池,Java虛擬機(jī)規(guī)范沒有做任何細(xì)節(jié)的要求,不同的提供商實(shí)現(xiàn)的虛擬機(jī)可以按照自己的需要來實(shí)現(xiàn)這個內(nèi)存區(qū)域。不過,一般來說,除了保存Class文件中描述的符號引用外,還會把翻譯出來的直接引用也存儲在運(yùn)行時常量池中。 運(yùn)行時常量池相對于Class文件常量池的另外一個重要特征是具備動態(tài)性,Java語言并不要求常量一定只能在編譯期產(chǎn)生,也就是并非預(yù)置入Class文件中常量池的內(nèi)容才能進(jìn)入方法區(qū)運(yùn)行時常量池,運(yùn)行期間也可能將新的常量放入池中,這種特性被開發(fā)人員利用得比較多的便是String類的intern()方法。 既然運(yùn)行時常量池是方法區(qū)的一部分,自然會受到方法區(qū)內(nèi)存的限制,當(dāng)常量池?zé)o法再申請到內(nèi)存時會拋出OutOfMemoryError異常。
2.垃圾回收算法
java 語言中一個顯著的特點(diǎn)就是引入了java回收機(jī)制,是c++程序員最頭疼的內(nèi)存管理的問題迎刃而解,它使得java程序員在編寫程序的時候不在考慮內(nèi)存管理。由于有個垃圾回收機(jī)制,java中的額對象不在有“作用域”的概念,只有對象的引用才有“作用域”。垃圾回收可以有效的防止內(nèi)存泄露,有效的使用空閑的內(nèi)存;java語言規(guī)范沒有明確的說明JVM 使用哪種垃圾回收算法,但是任何一種垃圾回收算法一般要做兩件基本事情:(1)發(fā)現(xiàn)無用的信息對象;(2)回收將無用對象占用的內(nèi)存空間。使該空間可被程序再次使用。
2.1.引用計數(shù)法(Reference Counting Collector)
引用計數(shù)是垃圾收集器中的早期策略。在這種方法中,堆中每個對象實(shí)例都有一個引用計數(shù)。當(dāng)一個對象被創(chuàng)建時,且將該對象實(shí)例分配給一個變量,該變量計數(shù)設(shè)置為1。當(dāng)任何其它變量被賦值為這個對象的引用時,計數(shù)加1(a = b,則b引用的對象實(shí)例的計數(shù)器+1),但當(dāng)一個對象實(shí)例的某個引用超過了生命周期或者被設(shè)置為一個新值時,對象實(shí)例的引用計數(shù)器減1。任何引用計數(shù)器為0的對象實(shí)例可以被當(dāng)作垃圾收集。當(dāng)一個對象實(shí)例被垃圾收集時,它引用的任何對象實(shí)例的引用計數(shù)器減1。
2.1.2優(yōu)缺點(diǎn)
優(yōu)點(diǎn):引用計數(shù)收集器可以很快的執(zhí)行,交織在程序運(yùn)行中。對程序需要不被長時間打斷的實(shí)時環(huán)境比較有利。
缺點(diǎn):無法檢測出循環(huán)引用。如父對象有一個對子對象的引用,子對象反過來引用父對象。這樣,他們的引用計數(shù)永遠(yuǎn)不可能為0.
2.2.tracing算法(Tracing Collector) 或 標(biāo)記-清除算法(mark and sweep)
該算法是從離散數(shù)學(xué)中的圖論引入的,程序把所有的引用關(guān)系看作一張圖,從一個節(jié)點(diǎn)GC ROOT開始,尋找對應(yīng)的引用節(jié)點(diǎn),找到這個節(jié)點(diǎn)以后,繼續(xù)尋找這個節(jié)點(diǎn)的引用節(jié)點(diǎn),當(dāng)所有的引用節(jié)點(diǎn)尋找完畢之后,剩余的節(jié)點(diǎn)則被認(rèn)為是沒有被引用到的節(jié)點(diǎn),即無用的節(jié)點(diǎn)。
java中可作為GC Root的對象有
1.虛擬機(jī)棧中引用的對象(本地變量表)
2.方法區(qū)中靜態(tài)屬性引用的對象
4.本地方法棧中引用的對象(Native對象)
標(biāo)記-清除算法采用從根集合進(jìn)行掃描,對存活的對象對象標(biāo)記,標(biāo)記完畢后,再掃描整個空間中未被標(biāo)記的對象,進(jìn)行回收。分為兩個階段:標(biāo)記階段和清除階段。標(biāo)記階段的任務(wù)是標(biāo)記出所有需要被回收的對象,清除階段就是回收被標(biāo)記的對象所占用的空間。具體過程如下圖所示。標(biāo)記-清除算法不需要進(jìn)行對象的移動,并且僅對不存活的對象進(jìn)行處理,在存活對象比較多的情況下極為高效,但由于標(biāo)記-清除算法直接回收不存活的對象,因此會造成內(nèi)存碎片。
2.3.compacting算法 或 標(biāo)記-整理算法
標(biāo)記-整理算法采用標(biāo)記-清除算法一樣的方式進(jìn)行對象的標(biāo)記,但在清除時不同,在回收不存活的對象占用的空間后,會將所有的存活對象往左端空閑空間移動,并更新對應(yīng)的指針。標(biāo)記-整理算法是在標(biāo)記-清除算法的基礎(chǔ)上,又進(jìn)行了對象的移動,因此成本更高,但是卻解決了內(nèi)存碎片的問題。在基于Compacting算法的收集器的實(shí)現(xiàn)中,一般增加句柄和句柄表。
2.4.copying算法(Compacting Collector)
該算法的提出是為了克服句柄的開銷和解決堆碎片的垃圾回收。它開始時把堆分成 一個對象 面和多個空閑面, 程序從對象面為對象分配空間,當(dāng)對象滿了,基于copying算法的垃圾 收集就從根集中掃描活動對象,并將每個 活動對象復(fù)制到空閑面(使得活動對象所占的內(nèi)存之間沒有空閑洞),這樣空閑面變成了對象面,原來的對象面變成了空閑面,程序會在新的對象面中分配內(nèi)存。一種典型的基于coping算法的垃圾回收是stop-and-copy算法,它將堆分成對象面和空閑區(qū)域面,在對象面與空閑區(qū)域面的切換過程中,程序暫停執(zhí)行。這種算法雖然實(shí)現(xiàn)簡單,運(yùn)行高效且不容易產(chǎn)生內(nèi)存碎片,但是卻對內(nèi)存空間的使用做出了高昂的代價,因?yàn)槟軌蚴褂玫膬?nèi)存縮減到原來的一半。很顯然,Copying算法的效率跟存活對象的數(shù)目多少有很大的關(guān)系,如果存活對象很多,那么Copying算法的效率將會大大降低。
2.5.generation算法(Generational Collector)
分代的垃圾回收策略,是基于這樣一個事實(shí):不同的對象的生命周期是不一樣的。因此,不同生命周期的對象可以采取不同的回收算法,以便提高回收效率。分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根據(jù)對象存活的生命周期將內(nèi)存劃分為若干個不同的區(qū)域。一般情況下將堆區(qū)劃分為老年代(Tenured Generation)和新生代(Young Generation)和持久代,老年代的特點(diǎn)是每次垃圾收集時只有少量對象需要被回收,而新生代的特點(diǎn)是每次垃圾回收時都有大量的對象需要被回收,那么就可以根據(jù)不同代的特點(diǎn)采取最適合的收集算法。
2.5.1.年輕代(Young Generation)
1.所有新生成的對象首先都是放在年輕代的。年輕代的目標(biāo)就是盡可能快速的收集掉那些生命周期短的對象。
2.新生代內(nèi)存按照8:1:1的比例分為一個eden區(qū)和兩個survivor(survivor0,survivor1)區(qū)。一個Eden區(qū),兩個 Survivor區(qū)(一般而言)。大部分對象在Eden區(qū)中生成。回收時先將eden區(qū)存活對象復(fù)制到一個survivor0區(qū),然后清空eden區(qū),當(dāng)這個survivor0區(qū)也存放滿了時,則將eden區(qū)和survivor0區(qū)存活對象復(fù)制到另一個survivor1區(qū),然后清空eden和這個survivor0區(qū),此時survivor0區(qū)是空的,然后將survivor0區(qū)和survivor1區(qū)交換,即保持survivor1區(qū)為空, 如此往復(fù)。
3.當(dāng)survivor1區(qū)不足以存放 eden和survivor0的存活對象時,就將存活對象直接存放到老年代。若是老年代也滿了就會觸發(fā)一次Full GC,也就是新生代、老年代都進(jìn)行回收
4.新生代發(fā)生的GC也叫做Minor GC,MinorGC發(fā)生頻率比較高(不一定等Eden區(qū)滿了才觸發(fā))
2.5.2.年老代(Old Generation)
1.在年輕代中經(jīng)歷了N次垃圾回收后仍然存活的對象,就會被放到年老代中。因此,可以認(rèn)為年老代中存放的都是一些生命周期較長的對象。
2.內(nèi)存比新生代也大很多(大概比例是1:2),當(dāng)老年代內(nèi)存滿時觸發(fā)Major GC即Full GC,Full GC發(fā)生頻率比較低,老年代對象存活時間比較長,存活率標(biāo)記高。
2.5.3.持久代(Permanent Generation)
用于存放靜態(tài)文件,如Java類、方法等。持久代對垃圾回收沒有顯著影響,但是有些應(yīng)用可能動態(tài)生成或者調(diào)用一些class,例如Hibernate 等,在這種時候需要設(shè)置一個比較大的持久代空間來存放這些運(yùn)行過程中新增的類。
目前大部分垃圾收集器對于新生代都采取Copying算法,因?yàn)樾律忻看卫厥斩家厥沾蟛糠謱ο?#xff0c;也就是說需要復(fù)制的操作次數(shù)較少,但是實(shí)際中并不是按照1:1的比例來劃分新生代的空間的,一般來說是將新生代劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間,當(dāng)進(jìn)行回收時,將Eden和Survivor中還存活的對象復(fù)制到另一塊Survivor空間中,然后清理掉Eden和剛才使用過的Survivor空間。
而由于老年代的特點(diǎn)是每次回收都只回收少量對象,一般使用的是Mark-Compact算法。
當(dāng)Eden區(qū)滿的時候,會觸發(fā)第一次Minor gc,把還活著的對象拷貝到Survivor From區(qū);當(dāng)Eden區(qū)再次出發(fā)Minor gc的時候,會掃描Eden區(qū)和From區(qū),對兩個區(qū)域進(jìn)行垃圾回收,經(jīng)過這次回收后還存活的對象,則直接復(fù)制到To區(qū)域,并將Eden區(qū)和From區(qū)清空。 當(dāng)后續(xù)Eden區(qū)又發(fā)生Minor gc的時候,會對Eden區(qū)和To區(qū)進(jìn)行垃圾回收,存活的對象復(fù)制到From區(qū),并將Eden區(qū)和To區(qū)清空 部分對象會在From區(qū)域和To區(qū)域中復(fù)制來復(fù)制去,如此交換15次(由JVM參數(shù)MaxTenuringThreshold決定,這個參數(shù)默認(rèn)是15),最終如果還存活,就存入老年代。
3.垃圾回收(了解)
新生代收集器使用的收集器:Serial、PraNew、Parallel Scavenge
老年代收集器使用的收集器:Serial Old、Parallel Old、CMS
Serial收集器(復(fù)制算法)
新生代單線程收集器,標(biāo)記和清理都是單線程,優(yōu)點(diǎn)是簡單高效。
Serial Old收集器(標(biāo)記-整理算法)
老年代單線程收集器,Serial收集器的老年代版本。
ParNew收集器(停止-復(fù)制算法)?
新生代收集器,可以認(rèn)為是Serial收集器的多線程版本,在多核CPU環(huán)境下有著比Serial更好的表現(xiàn)。
Parallel Scavenge收集器(停止-復(fù)制算法)
并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般為99%, 吞吐量= 用戶線程時間/(用戶線程時間+GC線程時間)。適合后臺應(yīng)用等對交互相應(yīng)要求不高的場景。
Parallel Old收集器(停止-復(fù)制算法)
Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量優(yōu)先
CMS(Concurrent Mark Sweep)收集器(標(biāo)記-清理算法)
高并發(fā)、低停頓,追求最短GC回收停頓時間,cpu占用比較高,響應(yīng)時間快,停頓時間短,多核cpu 追求高響應(yīng)時間的選擇
由于對象進(jìn)行了分代處理,因此垃圾回收區(qū)域、時間也不一樣。GC有兩種類型:Scavenge GC和Full GC。
Scavenge GC
一般情況下,當(dāng)新對象生成,并且在Eden申請空間失敗時,就會觸發(fā)Scavenge GC,對Eden區(qū)域進(jìn)行GC,清除非存活對象,并且把尚且存活的對象移動到Survivor區(qū)。然后整理Survivor的兩個區(qū)。這種方式的GC是對年輕代的Eden區(qū)進(jìn)行,不會影響到年老代。因?yàn)榇蟛糠謱ο蠖际菑腅den區(qū)開始的,同時Eden區(qū)不會分配的很大,所以Eden區(qū)的GC會頻繁進(jìn)行。因而,一般在這里需要使用速度快、效率高的算法,使Eden去能盡快空閑出來。
Full GC
對整個堆進(jìn)行整理,包括Young、Tenured和Perm。Full GC因?yàn)樾枰獙φ麄€堆進(jìn)行回收,所以比Scavenge GC要慢,因此應(yīng)該盡可能減少Full GC的次數(shù)。在對JVM調(diào)優(yōu)的過程中,很大一部分工作就是對于FullGC的調(diào)節(jié)。有如下原因可能導(dǎo)致Full GC:
1.年老代(Tenured)被寫滿
2.持久代(Perm)被寫滿
3.System.gc()被顯示調(diào)用
4.上一次GC之后Heap的各域分配策略動態(tài)變化
1.靜態(tài)集合類像HashMap、Vector等的使用最容易出現(xiàn)內(nèi)存泄露,這些靜態(tài)變量的生命周期和應(yīng)用程序一致,所有的對象Object也不能被釋放,因?yàn)樗麄円矊⒁恢北籚ector等應(yīng)用著。
2.各種連接,數(shù)據(jù)庫連接,網(wǎng)絡(luò)連接,IO連接等沒有顯示調(diào)用close關(guān)閉,不被GC回收導(dǎo)致內(nèi)存泄露。
3.監(jiān)聽器的使用,在釋放對象的同時沒有相應(yīng)刪除監(jiān)聽器的時候也可能導(dǎo)致內(nèi)存泄露。
www.cnblogs.com/andy-zcx/p/…
www.cnblogs.com/wabi8754756…
blog.csdn.net/yubujian_l/…
www.cnblogs.com/dz-boss/p/1…
總結(jié)
以上是生活随笔為你收集整理的java基础(一):谈谈java内存管理与垃圾回收机制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 算法笔记之回溯法(2)
- 下一篇: Luogu P2580 于是他错误的点名