日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java对象的生命周期及回收

發(fā)布時(shí)間:2025/4/14 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java对象的生命周期及回收 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

在網(wǎng)上看到一篇不錯的文章,記錄下來備忘。

要理解java對象的生命周期,我們需要要明白兩個問題,

1、java是怎么分配內(nèi)存的 ,2、java是怎么回收內(nèi)存的。

喜歡java的人,往往因?yàn)樗膬?nèi)存自動管理機(jī)制,不喜歡java的人,往往也是因?yàn)樗膬?nèi)存自動管理。我屬于前者,這幾年的coding經(jīng)驗(yàn)讓我認(rèn)識到,要寫好java程序,理解java的內(nèi)存管理機(jī)制是多么的重要。任何語言,內(nèi)存管理無外乎分配和回收,在C中我們可以用malloc動態(tài)申請內(nèi)存,調(diào)用free釋放申請的內(nèi)存;在C++中,我們可以用new操作符在堆中動態(tài)申請內(nèi)存,編寫析構(gòu)函數(shù)調(diào)用delete釋放申請的內(nèi)存;那么在java中究竟是內(nèi)存怎樣管理的呢?要弄清這個問題,我們首先要了解java內(nèi)存的分配機(jī)制,在java虛擬機(jī)規(guī)范里,JVM被分為7個內(nèi)存區(qū)域,但是規(guī)范這畢竟只是規(guī)范,就像我們編寫的接口一樣,雖然最終行為一致,但是個人的實(shí)現(xiàn)可能千差萬別,各個廠商的JVM實(shí)現(xiàn)也不盡相同,在這里,我們只針對sun的Hotspot虛擬機(jī)討論,該虛擬機(jī)也是目前應(yīng)用最廣泛的虛擬機(jī)。

虛擬器規(guī)范中的7個內(nèi)存區(qū)域分別是三個線程私有的和四個線程共享的內(nèi)存區(qū),線程私有的內(nèi)存區(qū)域與線程具有相同的生命周期,它們分別是: 指令計(jì)數(shù)器、 線程棧和本地線程棧,四個共享區(qū)是所有線程共享的,在JVM啟動時(shí)就會分配,分別是:方法區(qū)、 常量池、直接內(nèi)存區(qū)和堆(即我們通常所說的JVM的內(nèi)存分為堆和棧中的堆,后者就是前面的線程棧)。接下來我們逐一了解這幾個內(nèi)存區(qū)域。

1 指令計(jì)數(shù)器。我們都知道java的多線程是通過JVM切換時(shí)間片運(yùn)行的,因此每個線程在某個時(shí)刻可能在運(yùn)行也可能被掛起,那么當(dāng)線程掛起之后,JVM再次調(diào)度它時(shí)怎么知道該線程要運(yùn)行那條字節(jié)碼指令呢?這就需要一個與該線程相關(guān)的內(nèi)存區(qū)域記錄該線程下一條指令,而指令計(jì)數(shù)器就是實(shí)現(xiàn)這種功能的內(nèi)存區(qū)域。有多少線程在編譯時(shí)是不確定的,因此該區(qū)域也沒有辦法在編譯時(shí)分配,只能在創(chuàng)建線程時(shí)分配,所以說該區(qū)域是線程私有的,該區(qū)域只是指令的計(jì)數(shù),占用的空間非常少,所以虛擬機(jī)規(guī)范中沒有為該區(qū)域規(guī)定OutofMemoryError。

2 線程棧。先讓我看以下一段代碼:

Java代碼
  • class Test{
  • public static void main(String[] args) {
  • Thread th = new Thread();
  • th.start();
  • }
  • }
  • class Test{public static void main(String[] args) {Thread th = new Thread();th.start();} }

    在運(yùn)行以上代碼時(shí),JVM將分配一塊棧空間給線程th,用于保存方法內(nèi)的局部變量,方法的入口和出口等,這些局部變量包括基本類型和對象引用類型,這里可能有人會問,java的對象引用不是分配在堆上嗎?有這樣疑惑的人,可能是沒有理解java中引用和對象之間的區(qū)別,當(dāng)我們寫出以下代碼時(shí):

    Java代碼
  • public Object test(){
  • Object obj = new Object();
  • return obj;
  • }
  • public Object test(){Object obj = new Object();return obj; }

    其中的Object obj就是我們所說的引用類型,這樣的聲明本身是要占用4個字節(jié),而這4個字節(jié)在這里就是在棧空間里分配的,準(zhǔn)確的說是在線程棧中為test方法分配的棧幀中分配的,當(dāng)方法退出時(shí),將會隨棧幀的彈出而自動銷毀,而new Object()則是在堆中分配的,由GC在適當(dāng)?shù)臅r(shí)間收回其占用的空間。每個棧空間的默認(rèn)大小為0.5M,在1.7里調(diào)整為1M,每調(diào)用一次方法就會壓入一個棧幀,如果壓入的棧幀深度過大,即方法調(diào)用層次過深,就會拋出StackOverFlow,,SOF最常見的場景就是遞歸中,當(dāng)遞歸沒辦法退出時(shí),就會拋此異常,Hotspot提供了參數(shù)設(shè)置改區(qū)域的大小,使用-Xss:xxK,就可以修改默認(rèn)大小。 3 本地線程棧.顧名思義,該區(qū)域主要是給調(diào)用本地方法的線程分配的,該區(qū)域和線程棧的最大區(qū)別就是,在該線程的申請的內(nèi)存不受GC管理,需要調(diào)用者自己管理,JDK中的Math類的大部分方法都是本地方法,一個值得注意的問題是,在執(zhí)行本地方法時(shí),并不是運(yùn)行字節(jié)碼,所以之前所說的指令計(jì)數(shù)器是沒法記錄下一條字節(jié)碼指令的,當(dāng)執(zhí)行本地方法時(shí),指令計(jì)數(shù)器置為undefined。 接下來是四個線程共享區(qū)。 1 方法區(qū)。這塊區(qū)域是用來存放JVM裝載的class的類信息,包括:類的方法、靜態(tài)變量、類型信息(接口/父類),我們使用反射技術(shù)時(shí),所需的信息就是從這里獲取的。 2 常量池。當(dāng)我們編寫如下的代碼時(shí):

    Java代碼
  • class Test1{
  • private final int size=50;
  • }
  • class Test1{private final int size=50;}

    這個程序中size因?yàn)橛胒inal修飾,不能再修改它的值,所以就成為常量,而這常量將會存放在常量區(qū),這些常量在編譯時(shí)就知道占用空間的大小,但并不是說明該區(qū)域編譯就固定了,運(yùn)行期也可以修改常量池的大小,典型的場景是在使用String時(shí),你可以調(diào)用String的 intern(),JVM會判斷當(dāng)前所創(chuàng)建的String對象是否在常量池中,若有,則從常量區(qū)取,否則把該字符放入常量池并返回,這時(shí)就會修改常量池的大小,比如JDK中java.io.ObjectStreamField的一段代碼:

    Java代碼
  • ObjectStreamField(Field field, boolean unshared, boolean showType) {
  • this.field = field;
  • this.unshared = unshared;
  • name = field.getName();
  • Class ftype = field.getType();
  • type = (showType || ftype.isPrimitive()) ? ftype : Object.class;
  • signature = ObjectStreamClass.getClassSignature(ftype).intern();
  • }
  • ObjectStreamField(Field field, boolean unshared, boolean showType) {this.field = field;this.unshared = unshared;name = field.getName();Class ftype = field.getType();type = (showType || ftype.isPrimitive()) ? ftype : Object.class;signature = ObjectStreamClass.getClassSignature(ftype).intern(); }

    這段代碼將獲取的類的簽名放入常量池。HotSpot中并沒有單獨(dú)為該區(qū)域分配,而是合并到方法區(qū)中。 3 直接內(nèi)存區(qū)。直接內(nèi)存區(qū)并不是JVM可管理的內(nèi)存區(qū)。在JDK1.4中提供的NIO中,實(shí)現(xiàn)了高效的R/W操作,這種高效的R/W操作就是通過管道機(jī)制實(shí)現(xiàn)的,而管道機(jī)制實(shí)際上使用了本地內(nèi)存,這樣就避免了從本地源文件復(fù)制JVM內(nèi)存,再從JVM復(fù)制到目標(biāo)文件的過程,直接從源文件復(fù)制到目標(biāo)文件,JVM通過DirectByteBuffer操作直接內(nèi)存。 4 堆。主角總是最后出場,堆絕對是JVM中的一等公民,絕對的主角,我們通常所說的GC主要就是在這塊區(qū)域中進(jìn)行的,所有的java對象都在這里分配,這也是JVM中最大的內(nèi)存區(qū)域,被所有線程共享,成千上萬的對象在這里創(chuàng)建,也在這里被銷毀。 java內(nèi)存分配到這就算是一個完結(jié)了,接下來我們將討論java內(nèi)存的回收機(jī)制, 內(nèi)存回收主要包含以下幾個方面理解: 第一,局部變量占用內(nèi)存的回收,所謂局部變量,就是指在方法內(nèi)創(chuàng)建的變量,其中變量又分為基本類型和引用類型。如下代碼:

    Java代碼
  • public void test(){
  • int x=1;
  • char y='a';
  • long z=10L;
  • }
  • public void test(){int x=1;char y='a';long z=10L; }

    變量x y z即為局部變量,占用的空間將在test()所在的線程棧中分配,test()執(zhí)行完了后會自動從棧中彈出,釋放其占用的內(nèi)存,再來看一段代碼:

    Java代碼
  • public void test2(){
  • Date d = new Date();
  • System.out.println("Now is "+d);
  • }
  • public void test2(){Date d = new Date();System.out.println("Now is "+d); }

    我們都知道上述代碼會創(chuàng)建兩個對象,一個是Date d另一個是new Date。Date d叫做聲明了一個date類型的引用,引用就是一種類型,和int x一樣,它表明了這種類型要占用多少空間,在java中引用類型和int類型一樣占用4字節(jié)的空間,如果只聲明引用而不賦值,這4個字節(jié)將指向JVM中地址為0的空間,表示未初始化,對它的任何操作都會引發(fā)空指針異常。 如果進(jìn)行賦值如d = new Date()那么這個d就保存了new Date()這個對象的地址,通過之前的內(nèi)存分配策略,我知道new Date()是在jvm的heap中分配的,其占用的空間的回收我們將在后面著重分析,這里我們要知道的是這個Date d所占用的空間是在test2()所在的線程棧分配的,方法執(zhí)行完后同樣會被彈出棧,釋放其占用的空間。 第二.非局部變量的內(nèi)存回收,在上面的代碼中new Date()就和C++里的new創(chuàng)建的對象一樣,是在heap中分配,其占用的空間不會隨著方法的結(jié)束而自動釋放需要一定的機(jī)制去刪除,在C++中必須由程序員在適當(dāng)時(shí)候delete掉,在java中這部分內(nèi)存是由GC自動回收的,但是要進(jìn)行內(nèi)存回收必須解決兩問題:那些對象需要回收、怎么回收。判定那些對象需要回收,我們熟知的有以下方法: 一,引用計(jì)數(shù)法,這應(yīng)是絕大數(shù)的的java 程序員聽說的方法了,也是很多書上甚至很多老師講的方法,該方法是這樣描述的,為每個對象維護(hù)一個引用計(jì)數(shù)器,當(dāng)有引用時(shí)就加1,引用解除時(shí)就減1,那些長時(shí)間引用為0的對象就判定為回收對象,理論上這樣的判定是最準(zhǔn)確的,判定的效率也高,但是卻有一個致命的缺陷,請看以下代碼:

    Java代碼
  • package tmp;
  • import java.util.ArrayList;
  • import java.util.List;
  • public class Test {
  • private byte[] buffer;
  • private List ls;
  • public Test() {
  • this.buffer = new byte[4 * 1024 * 1024];
  • this.ls = new ArrayList();
  • }
  • private List getList() {
  • return ls;
  • }
  • public static void main(String[] args) {
  • Test t1 = new Test();
  • Test t2 = new Test();
  • t1.getList().add(t2);
  • t2.getList().add(t1);
  • t1 = t2 = null;
  • Test t3 = new Test();
  • System.out.println(t3);
  • }
  • }
  • package tmp;import java.util.ArrayList; import java.util.List;public class Test {private byte[] buffer;private List ls;public Test() {this.buffer = new byte[4 * 1024 * 1024];this.ls = new ArrayList();}private List getList() {return ls;}public static void main(String[] args) {Test t1 = new Test();Test t2 = new Test();t1.getList().add(t2);t2.getList().add(t1);t1 = t2 = null;Test t3 = new Test();System.out.println(t3);}}

    我們用以下參數(shù)運(yùn)行:-Xmx10M -Xms10M M 將jvm的大小設(shè)置為10M,不允許擴(kuò)展,按引用計(jì)數(shù)法,t1和t2相互引用,他們的引用計(jì)數(shù)都不可能為0,那么他們將永遠(yuǎn)不會回收,在我們的環(huán)境中JVM共10M,t1 t2占用8m,那么剩下的2M,是不足以創(chuàng)建t3的,理論上應(yīng)該拋出OOM。但是,程序正常運(yùn)行了,這說明JVM應(yīng)該是回收了t1和t2的我們加上-XX:+PrintGCDetails運(yùn)行,將打印GC的回收日記:

    [GC [DefNew: 252K->64K(960K), 0.0030166 secs][Tenured: 8265K->137K(9216K), 0.0109869 secs] 8444K->137K(10176K), [Perm : 2051K->2051K(12288K)], 0.0140892 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]

    [GC [DefNew: 252K->64K(960K), 0.0030166 secs][Tenured: 8265K->137K(9216K), 0.0109869 secs] 8444K->137K(10176K), [Perm : 2051K->2051K(12288K)], 0.0140892 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] com.mail.czp.Test@2ce908 Heapdef new generation total 960K, used 27K [0x029e0000, 0x02ae0000, 0x02ae0000)eden space 896K, 3% used [0x029e0000, 0x029e6c40, 0x02ac0000)from space 64K, 0% used [0x02ad0000, 0x02ad0000, 0x02ae0000)to space 64K, 0% used [0x02ac0000, 0x02ac0000, 0x02ad0000)tenured generation total 9216K, used 4233K [0x02ae0000, 0x033e0000, 0x033e0000)the space 9216K, 45% used [0x02ae0000, 0x02f02500, 0x02f02600, 0x033e0000)compacting perm gen total 12288K, used 2077K [0x033e0000, 0x03fe0000, 0x073e0000)the space 12288K, 16% used [0x033e0000, 0x035e74d8, 0x035e7600, 0x03fe0000) No shared spaces configured.

    轉(zhuǎn)載于:https://www.cnblogs.com/shhaoran/archive/2013/02/09/2924480.html

    總結(jié)

    以上是生活随笔為你收集整理的java对象的生命周期及回收的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。