Java從誕生以來(lái)以其在網(wǎng)絡(luò)應(yīng)用開(kāi)發(fā)上獨(dú)特的魅力以及“一次開(kāi)發(fā),,隨處運(yùn)行”的可移植性引起了人們極大的興趣,。Java與以往的高級(jí)語(yǔ)言如C/C++相比,在開(kāi)發(fā)方面具有很大的優(yōu)勢(shì),,其中以對(duì)象內(nèi)存管理機(jī)制中的垃圾處理機(jī)制(GC)最為突出,。
1 C/C++與Java對(duì)象內(nèi)存管理差別
C/C++將內(nèi)存劃分成四部分:數(shù)據(jù)區(qū)、代碼區(qū),、棧區(qū),、堆區(qū)。Java則把內(nèi)存劃分成三部分即代碼區(qū),、棧區(qū),、堆區(qū),代碼區(qū)主要用于存放程序的代碼,,棧區(qū)主要用于存放局部變量,、內(nèi)部變量等中間性變量,堆區(qū)主要用來(lái)存放對(duì)象,。
C/C++中的對(duì)象內(nèi)存管理是通過(guò)語(yǔ)句new()/delete()或malloc()/free()進(jìn)行申請(qǐng)和釋放的,。用new()或malloc()申請(qǐng)內(nèi)存后,若不使用delete()或free()進(jìn)行釋放,,則所申請(qǐng)的內(nèi)存一直被占用,,即使不使用也不能自動(dòng)釋放,,必須人為釋放,導(dǎo)致編程工作很繁瑣,。
Java中的對(duì)象內(nèi)存管理則改進(jìn)了對(duì)內(nèi)存的釋放過(guò)程,,使用new()或其他方法申請(qǐng)的內(nèi)存在不使用時(shí),可以自動(dòng)進(jìn)行垃圾處理,,釋放內(nèi)存,,從而節(jié)省內(nèi)存,使內(nèi)存的使用更加高效,、合理,。
Java中可以通過(guò)三種方法來(lái)銷毀對(duì)象實(shí)現(xiàn)內(nèi)存釋放,這三種方法被稱為Java銷毀對(duì)象的三把利劍:垃圾回收器;finalize方法;利用System.gc方法強(qiáng)制啟動(dòng)垃圾回收器,。
垃圾回收是一種動(dòng)態(tài)存儲(chǔ)管理技術(shù),,它自動(dòng)地釋放不再被程序引用的對(duì)象,按照特定的垃圾收集算法實(shí)現(xiàn)資源自動(dòng)回收的功能系統(tǒng),,會(huì)自動(dòng)進(jìn)行GC策略,。
2 GC概述
Java垃圾處理主要是針對(duì)堆的管理,對(duì)堆中不使用的空間進(jìn)行回收處理,。判斷一個(gè)對(duì)象的內(nèi)存空間是否無(wú)用的標(biāo)準(zhǔn)是:如果該對(duì)象不能再被程序中任何一個(gè)“活動(dòng)的部分”所引用,此時(shí)該對(duì)象的內(nèi)存空間已經(jīng)無(wú)用,。所謂“活動(dòng)的部分”,,是指程序中某部分參與程序的調(diào)用,正在執(zhí)行過(guò)程中,,尚未執(zhí)行完畢,。
具體以下兩例予以說(shuō)明:
實(shí)例1:
int [][]matrix=new int [2][3];
matrix=null;
此例中,第一句是用new語(yǔ)句在堆中為數(shù)組申請(qǐng)了一個(gè)空間,,然后用matrix來(lái)引用此空間的對(duì)象(這里數(shù)組可以理解為對(duì)象),,此時(shí)這個(gè)內(nèi)存空間就是有用的。第二句是給matrix賦空值,,matrix則不再引用此數(shù)組,。此時(shí),這個(gè)空間就是無(wú)用的,。
實(shí)例2:
int [][] m1=new int[2][3];
int [][] m2=new int[2][3];
m1=m2;
此例中,,第一句是用new語(yǔ)句在堆中為數(shù)組申請(qǐng)了一個(gè)空間,用m1引用這個(gè)數(shù)組,。第二句是用new語(yǔ)句在堆中為數(shù)組申請(qǐng)了一個(gè)空間,,用m2引用這個(gè)數(shù)組。第三句是改變引用對(duì)象,,把m2的引用賦給m1,,則此時(shí)m1也引用m2引用的對(duì)象數(shù)組,因此,m1原來(lái)引用的數(shù)組無(wú)人引用,,成為垃圾,。3 傳統(tǒng)收集器簡(jiǎn)介
Java依托于垃圾收集GC機(jī)制,可以自動(dòng)回收垃圾即釋放堆空間,,讓其他對(duì)象可以使用此部分空間,。而采用了某種 GC 算法的收集器(Collector)稱之為某某垃圾收集器(Garbage Collector)。目前 Java中采用的垃圾收集器一般包括:引用計(jì)數(shù)法(Reference Counting Collector),、 Tracing算法(Tracing Collector),、Compacting算法(Compacting Collector)、Coping算法(Coping Collector),、Generation算法(Generational Collector),、Adaptive算法(Adaptive Collector)。
3.1 引用計(jì)數(shù)法
引用計(jì)數(shù)法是唯一沒(méi)有使用根集的垃圾回收的方法,,該算法使用引用計(jì)數(shù)器來(lái)區(qū)分存活對(duì)象和不再使用的對(duì)象,。一般來(lái)說(shuō),堆中的每個(gè)對(duì)象對(duì)應(yīng)一個(gè)引用計(jì)數(shù)器,。當(dāng)每一次創(chuàng)建一個(gè)對(duì)象并賦給一個(gè)變量時(shí),,引用計(jì)數(shù)器置為1。當(dāng)對(duì)象被賦給任意變量時(shí),,引用計(jì)數(shù)器每次加1,,當(dāng)對(duì)象出了作用域后(該對(duì)象丟棄不再使用),引用計(jì)數(shù)器減1,,一旦引用計(jì)數(shù)器為0,,對(duì)象就滿足了垃圾收集的條件。
基于引用計(jì)數(shù)器的垃圾收集器運(yùn)行較快,,不會(huì)長(zhǎng)時(shí)間中斷程序執(zhí)行,,必須適宜地實(shí)時(shí)運(yùn)行的程序。但引用計(jì)數(shù)器增加了程序執(zhí)行的開(kāi)銷,,因?yàn)槊看螌?duì)象賦給新的變量,,計(jì)數(shù)器加1,而每次現(xiàn)有對(duì)象出了作用域,,計(jì)數(shù)器減1,。
3.2 Tracing算法
Tracing算法是為了解決引用計(jì)數(shù)法的問(wèn)題而提出,它使用了根集的概念,?;赥racing算法的垃圾收集器從根集開(kāi)始掃描,識(shí)別出哪些對(duì)象可達(dá),,哪些對(duì)象不可達(dá),,并用某種方式標(biāo)記可達(dá)對(duì)象,,例如對(duì)每個(gè)可達(dá)對(duì)象設(shè)置一個(gè)或多個(gè)位。在掃描識(shí)別過(guò)程中,,基于Tracing算法的垃圾收集也稱為標(biāo)記和清除(mark-and-sweep)垃圾收集器,。
3.3 Compacting算法
為了解決堆碎片問(wèn)題,基于tracing的垃圾回收吸收了Compacting算法的思想,,在清除的過(guò)程中,,算法將所有對(duì)象移到堆的一端,堆的另一端就變成了一個(gè)相鄰的空閑內(nèi)存區(qū),,收集器會(huì)對(duì)它移動(dòng)的所有對(duì)象的所有引用進(jìn)行更新,,使得這些引用在新的位置能識(shí)別原來(lái) 的對(duì)象。在基于Compacting算法的收集器的實(shí)現(xiàn)中,,一般增加句柄和句柄表,。
3.4 Coping算法
Coping算法的提出是為了克服句柄的開(kāi)銷和解決堆碎片的垃圾回收。它開(kāi)始時(shí)把堆分成一個(gè)對(duì)象面和多個(gè)空閑面,, 程序從對(duì)象面為對(duì)象分配空間,,當(dāng)對(duì)象滿了,基于Coping算法的垃圾收集就從根集中掃描活動(dòng)對(duì)象,,并將每個(gè)活動(dòng)對(duì)象復(fù)制到空閑面(使得活動(dòng)對(duì)象所占的內(nèi)存之間沒(méi)有空閑洞),,這樣空閑面變成了對(duì)象面,原來(lái)的對(duì)象面變成了空閑面,,程序會(huì)在新的對(duì)象面中分配內(nèi)存,。
一種典型的基于Coping算法的垃圾回收是stop-and-copy算法,它將堆分成對(duì)象面和空閑區(qū)域面,,在對(duì)象面與空閑區(qū)域面的切換過(guò)程中,程序暫停執(zhí)行,。
3.5 Generation算法
stop-and-copy垃圾收集器的一個(gè)缺陷是收集器必須復(fù)制所有的活動(dòng)對(duì)象,,這增加了程序等待時(shí)間,這是Coping算法低效的原因,。在程序設(shè)計(jì)中有這樣的規(guī)律:多數(shù)對(duì)象存在的時(shí)間比較短,,少數(shù)的存在時(shí)間比較長(zhǎng)。因此,,Generation算法將堆分成兩個(gè)或多個(gè),,每個(gè)子堆作為對(duì)象的一代(Generation)。由于多數(shù)對(duì)象存在的時(shí)間比較短,,隨著程序丟棄不使用的對(duì)象,,垃圾收集器將從最年輕的子堆中收集這些對(duì)象。在分代式的垃圾收集器運(yùn)行后,,上次運(yùn)行存活下來(lái)的對(duì)象移到下一最高代的子堆中,,由于老一代的子堆不會(huì)經(jīng)常被回收,,因而節(jié)省了時(shí)間。
3.6 Adaptive算法
在特定的情況下,,一些垃圾收集算法會(huì)優(yōu)于其他算法,。基于Adaptive算法的垃圾收集器就是監(jiān)控當(dāng)前堆的使用情況,,并將選擇適當(dāng)算法的垃圾收集器,。
4 GC新算法概述
基于上述對(duì)幾種收集器算法優(yōu)缺點(diǎn)的對(duì)比分析,提出一種既可以滿足程序?qū)?shí)時(shí)性的要求,,同時(shí)也能避免內(nèi)存泄漏的較完全的垃圾處理算法,。
4.1算法描述
4.1.1內(nèi)存劃分
(1)把一個(gè)堆內(nèi)存劃分成兩大塊,一塊是活躍區(qū),占堆大小的2/3,,活躍區(qū)劃分成大小相同的8個(gè)塊,,并且為每個(gè)塊設(shè)置一個(gè)計(jì)數(shù)器int cnt[x](x取1~8),用來(lái)記錄每塊內(nèi)存中動(dòng)態(tài)分配的被引用對(duì)象總數(shù),。另一塊是保留區(qū),占堆大小的1/3,,設(shè)置兩個(gè)常數(shù)min和max,分別用來(lái)表示保留區(qū)的初始大小和最大可增加到的大小,。(min的值小于堆大小的1/3,,max的值可以自行設(shè)置但最大值不超過(guò)堆大小的1/3)。
(2)為堆中的每個(gè)對(duì)象設(shè)置一個(gè)標(biāo)記位(標(biāo)記位放在一個(gè)專用數(shù)組cnt[x](x取1~8)中)以表示其是否被引用,,在對(duì)象被引用時(shí)計(jì)數(shù)器就開(kāi)始動(dòng)態(tài)地統(tǒng)計(jì)計(jì)數(shù),,記錄本塊中被引用的對(duì)象個(gè)數(shù)。
(3)通過(guò)比較計(jì)數(shù)器值的大小來(lái)判斷應(yīng)該掃描哪個(gè)塊,,而不是利用搜索所有對(duì)象的方法,。具體結(jié)構(gòu)如圖1。
(1)程序開(kāi)始運(yùn)行,,對(duì)象動(dòng)態(tài)地申請(qǐng)堆區(qū),,這時(shí),每個(gè)塊的計(jì)數(shù)器啟動(dòng),,根據(jù)引用對(duì)象的標(biāo)記位情況來(lái)改變計(jì)數(shù)器的值,,為1則計(jì)數(shù)器加1,8個(gè)計(jì)數(shù)器值放在數(shù)組里,,并比較8個(gè)計(jì)數(shù)器值的大小,,選取最大的計(jì)數(shù)器所在塊,進(jìn)行掃描,。
(2)掃描出的垃圾轉(zhuǎn)移到刪除區(qū),,等待被刪除。
(3)繼續(xù)比較計(jì)數(shù)器值,但已經(jīng)進(jìn)行掃描的塊不參加此后的比較,,待刪除的垃圾占的空間達(dá)到min值時(shí),,垃圾器開(kāi)始對(duì)活躍區(qū)壓縮內(nèi)存碎片,,并且在刪除區(qū)同時(shí)開(kāi)始進(jìn)行垃圾刪除申請(qǐng)。
(4)當(dāng)刪除區(qū)的空間達(dá)到了max值時(shí),,刪除區(qū)的垃圾還沒(méi)有被刪除,,這時(shí)停止活躍區(qū)的掃描,等待刪除區(qū)進(jìn)行垃圾刪除,。
4.2 實(shí)例分析
看下面一段程序:
int [][] m1=new int[2][3];
int [][] m2=new int[2][3];
m1=m2;
此例中,,第一句是用new語(yǔ)句在堆中為數(shù)組申請(qǐng)了一個(gè)空間,然后用matrix引用此空間的對(duì)象(這里數(shù)組可以理解為對(duì)象),,此時(shí)這個(gè)內(nèi)存空間就是有用的,。第二句是給matrix賦空值,matrix則不再引用此數(shù)組,。此時(shí),,這個(gè)空間就是無(wú)用的。
對(duì)于原來(lái)的算法,,m1引用的數(shù)組在堆中是隨機(jī)存放的,,若要查找垃圾,則會(huì)遍歷整個(gè)堆內(nèi)存,,先標(biāo)記,,然后再清理垃圾。設(shè)耗費(fèi)時(shí)間為Ta,。
m1引用的數(shù)組在堆中是隨機(jī)存放的,,所以假設(shè)其放在活躍區(qū)中的cnt[x]區(qū)(x取值為0~7之一),下面分兩種情況來(lái)考慮:
(1)最好的情況,,m1原來(lái)引用的數(shù)組放在cnt1中為數(shù)組cnt[x]中最大的數(shù),,則查找到這個(gè)垃圾的時(shí)間為: T8+cnt[x]。
由此可以節(jié)省7Ta/16的時(shí)間,。此新算法可以大大減少垃圾處理所需的時(shí)間,。
Java語(yǔ)言對(duì)垃圾的處理是利用Java的垃圾處理器自動(dòng)進(jìn)行的,JVM雖然沒(méi)有明確程序員必須了解垃圾處理器的過(guò)程和實(shí)質(zhì),,但是,一個(gè)優(yōu)秀的Java程序員應(yīng)該掌握和熟悉垃圾處理器的工作機(jī)制,,充分利用好內(nèi)存空間,,減少不必要的空間浪費(fèi),,從而使程序更好地運(yùn)行。