當前位置:首頁 » 編程語言 » java內存流

java內存流

發布時間: 2022-10-20 16:07:01

java以下代碼中,我用文件輸出流和內存輸出流同樣輸出output對象,為什麼結果不一樣

呃,時隔幾年,不知道你解決問題沒有。首先應該明白,System.out.println(OBJ)會自動調用OBJ.toString()方法。查閱官方文檔表示,ByteArrayInputStream類中重寫了toString( )方法,Converts the buffer's contents into a string decoding bytes using the platform's default character set.即 「使用平台的默認字元集將緩沖區的內容轉換為字元串解碼位元組。
但是 FileOutputStream類中並未重寫toString( ) 。使用的是從Object類中繼承下來的。所以你能看到 obj@xxx 格式的輸出。

② java就業方向有哪些

對於大多數同學而言,學習編程語言就是為了更好的就業。因為Java在電子商務、企業級開發應用、游戲編程等眾多領域行業發揮著重要作用,所以學習Java一直是一股熱潮。那麼,2020年,Java的職業方向有哪些呢?小編帶你解析。
用途廣泛的Java造就了Java工程師的輝煌,使其在軟體工程師的領域里獨占鰲頭!Java軟體工程師運用Java這個開發工具去完成軟體產品的軟體程序設計、開發、測試、維護升級等工作。隨著Internet的迅速發展,Web應用日益廣泛,Java語言也得到了迅速發展。
Java是目前世界上流行的計算機編程語言,是一種可以編寫跨平台應用軟體的面向對象的程序設計語言。這可以概括Java有著自己獨特的優勢:語言簡單、是一個面向對象、分布式應用並且安全、體系結構中立並且可移植,重要,它是一個動態語言。
計算機專業的大學生欲成為Java工程師,便捷的一條路就是參加以實戰項目為主要教學方法的Java職業技能培訓,從而有效地縮短同企業具體用人需求之間的差距。有關Java的未來職業發展有:1、成為管理人員,例如產品研發經理,技術經理,項目經理等繼續;2、技術工作之路,成為高級軟體工程師、需求工程師等。
Java軟體工程師一般月薪范圍在6000-10000元,遠遠超過了應屆畢業生月薪2500元的平均水平。通常來說,有一年工作經驗的Java高級軟體工程師的薪酬大致在年薪10—13萬左右。Java可以從事JSP網站開發、Java編程、Java游戲開發、Java桌面程序設計,以及其他與Java語言編程相關的工作,可進入電信、銀行、保險專業軟體開發公司等從事軟體設計和開發工作。
據權威統計機構統計——在所有軟體開發類人才的需求中,對Java工程師的需求達到全部需求量的60%~70%。面對如此好的就業前景,還等什麼

③ java 流占內存嗎

占內存啊
,流是個抽象的概念,是對
輸入輸出設備
的抽象,
Java程序
中,對於數據的輸入/輸出操作都是以「流」的方式進行。設備可以是文件,網路,內存等。
stream還有很多種,
具體的你可以看看相關的資料

④ java怎麼將生成的文件放入內存

這個要使用到內存流。BufferedOutputStream或者BufferedWriter。
文件的讀取和寫入都應該會了吧?普通的流讀寫都是直接從文件中讀取或者寫入到文件中的,而內存流則是把文件中的內容寫入到電腦內存或者是從內存中讀取出來。具體的話就是把輸出流替換成BufferedOutputStream或者BufferedWriter即可

⑤ 哪位能描述一下 java 中內存的分區情況和各類變數在內存中的存貯情況。

Java內存分配與管理是Java的核心技術之一,一般Java在內存分配時會涉及到以下區域:

◆寄存器:我們在程序中無法控制

◆棧:存放基本類型的數據和對象的引用,但對象本身不存放在棧中,而是存放在堆中

◆堆:存放用new產生的數據

◆靜態域:存放在對象中用static定義的靜態成員

◆常量池:存放常量

◆非RAM存儲:硬碟等永久存儲空間

Java內存分配中的棧

在函數中定義的一些基本類型的變數數據和對象的引用變數都在函數的棧內存中分配。

當在一段代碼塊定義一個變數時,Java就在棧中為這個變數分配內存空間,當該變數退出該作用域後,Java會自動釋放掉為該變數所分配的內存空間,該內存空間可以立即被另作他用。

Java內存分配中的堆

堆內存用來存放由new創建的對象和數組。在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。

在堆中產生了一個數組或對象後,還可以在棧中定義一個特殊的變數,讓棧中這個變數的取值等於數組或對象在堆內存中的首地址,棧中的這個變數就成了數組或對象的引用變數。引用變數就相當於是為數組或對象起的一個名稱,以後就可以在程序中使用棧中的引用變數來訪問堆中的數組或對象。引用變數就相當於是為數組或者對象起的一個名稱。

引用變數是普通的變數,定義時在棧中分配,引用變數在程序運行到其作用域之外後被釋放。而數組和對象本身在堆中分配,即使程序運行到使用new產生數組或者對象的語句所在的代碼塊之外,數組和對象本身占據的內存不會被釋放,數組和對象在沒有引用變數指向它的時候,才變為垃圾,不能在被使用,但仍然占據內存空間不放,在隨後的一個不確定的時間被垃圾回收器收走(釋放掉)。這也是Java比較占內存的原因。

實際上,棧中的變數指向堆內存中的變數,這就是Java中的指針!

常量池(constantpool)

常量池指的是在編譯期被確定,並被保存在已編譯的.class文件中的一些數據。除了包含代碼中所定義的各種基本類型(如int、long等等)和對象型(如String及數組)的常量值(final)還包含一些以文本形式出現的符號引用,比如:

◆類和介面的全限定名;

◆欄位的名稱和描述符;

◆方法和名稱和描述符。

虛擬機必須為每個被裝載的類型維護一個常量池。常量池就是該類型所用到常量的一個有序集和,包括直接常量(string,integer和floatingpoint常量)和對其他類型,欄位和方法的符號引用。

對於String常量,它的值是在常量池中的。而JVM中的常量池在內存當中是以表的形式存在的,對於String類型,有一張固定長度的CONSTANT_String_info表用來存儲文字字元串值,注意:該表只存儲文字字元串值,不存儲符號引用。說到這里,對常量池中的字元串值的存儲位置應該有一個比較明了的理解了。

在程序執行的時候,常量池會儲存在MethodArea,而不是堆中。

堆與棧

Java的堆是一個運行時數據區,類的(對象從中分配空間。這些對象通過new、newarray、anewarray和multianewarray等指令建立,它們不需要程序代碼來顯式的釋放。堆是由垃圾回收來負責的,堆的優勢是可以動態地分配內存大小,生存期也不必事先告訴編譯器,因為它是在運行時動態分配內存的,Java的垃圾收集器會自動收走這些不再使用的數據。但缺點是,由於要在運行時動態分配內存,存取速度較慢。

棧的優勢是,存取速度比堆要快,僅次於寄存器,棧數據可以共享。但缺點是,存在棧中的數據大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類型的變數數據(int,short,long,byte,float,double,boolean,char)和對象句柄(引用)。

棧有一個很重要的特殊性,就是存在棧中的數據可以共享。假設我們同時定義:

1. inta=3;

2. intb=3;

編譯器先處理inta=3;首先它會在棧中創建一個變數為a的引用,然後查找棧中是否有3這個值,如果沒找到,就將3存放進來,然後將a指向3。接著處理intb=3;在創建完b的引用變數後,因為在棧中已經有3這個值,便將b直接指向3。這樣,就出現了a與b同時均指向3的情況。

這時,如果再令a=4;那麼編譯器會重新搜索棧中是否有4值,如果沒有,則將4存放進來,並令a指向4;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。

要注意這種數據的共享與兩個對象的引用同時指向一個對象的這種共享是不同的,因為這種情況a的修改並不會影響到b,它是由編譯器完成的,它有利於節省空間。而一個對象引用變數修改了這個對象的內部狀態,會影響到另一個對象引用變數。

String是一個特殊的包裝類數據。可以用:

Stringstr=newString("abc");

Stringstr="abc";

兩種的形式來創建,第一種是用new()來新建對象的,它會在存放於堆中。每調用一次就會創建一個新的對象。而第二種是先在棧中創建一個對String類的對象引用變數str,然後通過符號引用去字元串常量池裡找有沒有"abc",如果沒有,則將"abc"存放進字元串常量池,並令str指向」abc」,如果已經有」abc」則直接令str指向「abc」。

比較類裡面的數值是否相等時,用equals()方法;當測試兩個包裝類的引用是否指向同一個對象時,用==,下面用例子說明上面的理論。

1.Stringstr1="abc";

2.Stringstr2="abc";

3.System.out.println(str1==str2);//true

可以看出str1和str2是指向同一個對象的。

1.Stringstr1=newString("abc");

2.Stringstr2=newString("abc");

3.System.out.println(str1==str2);//false

用new的方式是生成不同的對象。每一次生成一個。

因此用第二種方式創建多個」abc」字元串,在內存中其實只存在一個對象而已.這種寫法有利與節省內存空間.同時它可以在一定程度上提高程序的運行速度,因為JVM會自動根據棧中數據的實際情況來決定是否有必要創建新對象。而對於Stringstr=newString("abc");的代碼,則一概在堆中創建新對象,而不管其字元串值是否相等,是否有必要創建新對象,從而加重了程序的負擔。

另一方面,要注意:我們在使用諸如Stringstr="abc";的格式定義類時,總是想當然地認為,創建了String類的對象str。擔心陷阱!對象可能並沒有被創建!而可能只是指向一個先前已經創建的對象。只有通過new()方法才能保證每次都創建一個新的對象。

由於String類的immutable性質,當String變數需要經常變換其值時,應該考慮使用StringBuffer類,以提高程序效率。

1.首先String不屬於8種基本數據類型,String是一個對象。因為對象的默認值是null,所以String的默認值也是null;但它又是一種特殊的對象,有其它對象沒有的一些特性。

2.newString()和newString(」")都是申明一個新的空字元串,是空串不是null;

3.Stringstr=」kvill」;Stringstr=newString(」kvill」)的區別

示例:

1.Strings0="kvill";

2.Strings1="kvill";

3.Strings2="kv"+"ill";

4.System.out.println(s0==s1);

5.System.out.println(s0==s2);

結果為:

true

true

首先,我們要知結果為道Java會確保一個字元串常量只有一個拷貝。

因為例子中的s0和s1中的」kvill」都是字元串常量,它們在編譯期就被確定了,所以s0==s1為true;而」kv」和」ill」也都是字元串常量,當一個字元串由多個字元串常量連接而成時,它自己肯定也是字元串常量,所以s2也同樣在編譯期就被解析為一個字元串常量,所以s2也是常量池中」kvill」的一個引用。所以我們得出s0==s1==s2;用newString()創建的字元串不是常量,不能在編譯期就確定,所以newString()創建的字元串不放入常量池中,它們有自己的地址空間。

示例:

6.Strings0="kvill";

7.Strings1=newString("kvill");

8.Strings2="kv"+newString("ill");

9.System.out.println(s0==s1);

10.System.out.println(s0==s2);

11.System.out.println(s1==s2);

結果為:

false

false

false

例2中s0還是常量池中"kvill」的應用,s1因為無法在編譯期確定,所以是運行時創建的新對象」kvill」的引用,s2因為有後半部分newString(」ill」)所以也無法在編譯期確定,所以也是一個新創建對象」kvill」的應用;明白了這些也就知道為何得出此結果了。

4.String.intern():

再補充介紹一點:存在於.class文件中的常量池,在運行期被JVM裝載,並且可以擴充。String的intern()方法就是擴充常量池的一個方法;當一個String實例str調用intern()方法時,Java查找常量池中是否有相同Unicode的字元串常量,如果有,則返回其的引用,如果沒有,則在常量池中增加一個Unicode等於str的字元串並返回它的引用;看示例就清楚了

示例:

1.Strings0="kvill";

2.Strings1=newString("kvill");

3.Strings2=newString("kvill");

4.System.out.println(s0==s1);

5.System.out.println("**********");

6.s1.intern();

7.s2=s2.intern();//把常量池中"kvill"的引用賦給s2

8.System.out.println(s0==s1);

9.System.out.println(s0==s1.intern());

10.System.out.println(s0==s2);

結果為:

false

false//雖然執行了s1.intern(),但它的返回值沒有賦給s1

true//說明s1.intern()返回的是常量池中"kvill"的引用

true

最後我再破除一個錯誤的理解:有人說,「使用String.intern()方法則可以將一個String類的保存到一個全局String表中,如果具有相同值的Unicode字元串已經在這個表中,那麼該方法返回表中已有字元串的地址,如果在表中沒有相同值的字元串,則將自己的地址注冊到表中」如果我把他說的這個全局的String表理解為常量池的話,他的最後一句話,」如果在表中沒有相同值的字元串,則將自己的地址注冊到表中」是錯的:

示例:

1.Strings1=newString("kvill");

2.Strings2=s1.intern();

3.System.out.println(s1==s1.intern());

4.System.out.println(s1+""+s2);

5.System.out.println(s2==s1.intern());

結果:

1.false

2.kvillkvill

3.true

在這個類中我們沒有聲名一個」kvill」常量,所以常量池中一開始是沒有」kvill」的,當我們調用s1.intern()後就在常量池中新添加了一個」kvill」常量,原來的不在常量池中的」kvill」仍然存在,也就不是「將自己的地址注冊到常量池中」了。

s1==s1.intern()為false說明原來的」kvill」仍然存在;s2現在為常量池中」kvill」的地址,所以有s2==s1.intern()為true。

5.關於equals()和==:

這個對於String簡單來說就是比較兩字元串的Unicode序列是否相當,如果相等返回true;而==是比較兩字元串的地址是否相同,也就是是否是同一個字元串的引用。

6.關於String是不可變的

這一說又要說很多,大家只要知道String的實例一旦生成就不會再改變了,比如說:Stringstr=」kv」+」ill」+」「+」ans」;就是有4個字元串常量,首先」kv」和」ill」生成了」kvill」存在內存中,然後」kvill」又和」」生成「kvill「存在內存中,最後又和生成了」kvillans」;並把這個字元串的地址賦給了str,就是因為String的」不可變」產生了很多臨時變數,這也就是為什麼建議用StringBuffer的原因了,因為StringBuffer是可改變的。

下面是一些String相關的常見問題:

String中的final用法和理解

finalStringBuffera=newStringBuffer("111");

finalStringBufferb=newStringBuffer("222");

a=b;//此句編譯不通過

finalStringBuffera=newStringBuffer("111");

a.append("222");//編譯通過

可見,final只對引用的"值"(即內存地址)有效,它迫使引用只能指向初始指向的那個對象,改變它的指向會導致編譯期錯誤。至於它所指向的對象的變化,final是不負責的。

String常量池問題的幾個例子

下面是幾個常見例子的比較分析和理解:

Stringa="a1";

Stringb="a"+1;

System.out.println((a==b));//result=true

Stringa="atrue";

Stringb="a"+"true";

System.out.println((a==b));//result=true

Stringa="a3.4";

Stringb="a"+3.4;

System.out.println((a==b));//result=true

分析:JVM對於字元串常量的"+"號連接,將程序編譯期,JVM就將常量字元串的"+"連接優化為連接後的值,拿"a"+1來說,經編譯器優化後在class中就已經是a1。在編譯期其字元串常量的值就確定下來,故上面程序最終的結果都為true。

Stringa="ab";

Stringbb="b";

Stringb="a"+bb;

System.out.println((a==b));//result=false

分析:JVM對於字元串引用,由於在字元串的"+"連接中,有字元串引用存在,而引用的值在程序編譯期是無法確定的,即"a"+bb無法被編譯器優化,只有在程序運行期來動態分配並將連接後的新地址賦給b。所以上面程序的結果也就為false。

Stringa="ab";

finalStringbb="b";

Stringb="a"+bb;

System.out.println((a==b));//result=true

分析:和[3]中唯一不同的是bb字元串加了final修飾,對於final修飾的變數,它在編譯時被解析為常量值的一個本地拷貝存儲到自己的常量池中或嵌入到它的位元組碼流中。所以此時的"a"+bb和"a"+"b"效果是一樣的。故上面程序的結果為true。

Stringa="ab";

finalStringbb=getBB();

Stringb="a"+bb;

System.out.println((a==b));//result=false

privatestaticStringgetBB(){

return"b";

}

分析:JVM對於字元串引用bb,它的值在編譯期無法確定,只有在程序運行期調用方法後,將方法的返回值和"a"來動態連接並分配地址為b,故上面程序的結果為false。

通過上面4個例子可以得出得知:

Strings="a"+"b"+"c";

就等價於Strings="abc";

Stringa="a";

Stringb="b";

Stringc="c";

Strings=a+b+c;

這個就不一樣了,最終結果等於:

1.StringBuffertemp=newStringBuffer();

2.temp.append(a).append(b).append(c);

3.Strings=temp.toString();

由上面的分析結果,可就不難推斷出String採用連接運算符(+)效率低下原因分析,形如這樣的代碼:

publicclassTest{

publicstaticvoidmain(Stringargs[]){

Strings=null;

for(inti=0;i<100;i++){

s+="a";

}

}

}

每做一次+就產生個StringBuilder對象,然後append後就扔掉。下次循環再到達時重新產生個StringBuilder對象,然後append字元串,如此循環直至結束。如果我們直接採用StringBuilder對象進行append的話,我們可以節省N-1次創建和銷毀對象的時間。所以對於在循環中要進行字元串連接的應用,一般都是用StringBuffer或StringBulider對象來進行append操作。

String對象的intern方法理解和分析:

1.publicclassTest4{

2.privatestaticStringa="ab";

3.publicstaticvoidmain(String[]args){

4.Strings1="a";

5.Strings2="b";

6.Strings=s1+s2;

7.System.out.println(s==a);//false

8.System.out.println(s.intern()==a);//true

9.}

10.}

這里用到Java裡面是一個常量池的問題。對於s1+s2操作,其實是在堆裡面重新創建了一個新的對象,s保存的是這個新對象在堆空間的的內容,所以s與a的值是不相等的。而當調用s.intern()方法,卻可以返回s在常量池中的地址值,因為a的值存儲在常量池中,故s.intern和a的值相等。

總結

棧中用來存放一些原始數據類型的局部變數數據和對象的引用(String,數組.對象等等)但不存放對象內容

堆中存放使用new關鍵字創建的對象.

字元串是一個特殊包裝類,其引用是存放在棧里的,而對象內容必須根據創建方式不同定(常量池和堆).有的是編譯期就已經創建好,存放在字元串常量池中,而有的是運行時才被創建.使用new關鍵字,存放在堆中。

⑥ java io 流 有一種流是邊讀邊寫的 , 還一種 不管文件多大都讀到內存中 , 分別叫什麼

邊讀邊寫用多線程每個流都可以做到,管道流就是典型的流!

不管文件多大,讀到內存那是不可能的,你所說的應該是內存流
內存流典型的流(位元組數組流),特點內部封裝一個可變長度的數組,
特點就是處理速度快,可對外提供完整數據的數組,非常方便!
試想一下,你一個文件8G大,你能全都讀到內存中去?對么....

⑦ java在內存中建立個位元組數組如果向這個位元組數組中寫入數據用哪個是用輸出流嗎還是用輸入流

可以考慮使用ByteArrayInputStream & ByteArrayOutputStream
位元組數組位於內存

另外,「輸入流是從硬碟到內存的讀操作」 & 「輸出流是從內存到硬碟的寫操作」 這句嚴格說不正確。輸入流、輸出流的源和目標均是抽象概念,不一定是硬碟或者文件的

⑧ java內存泄漏怎麼處理

一、Java內存回收機制
不論哪種語言的內存分配方式,都需要返回所分配內存的真實地址,也就是返回一個指針到內存塊的首地址。Java中對象是採用new或者反射的方法創建的,這些對象的創建都是在堆(Heap)中分配的,所有對象的回收都是由Java虛擬機通過垃圾回收機制完成的。GC為了能夠正確釋放對象,會監控每個對象的運行狀況,對他們的申請、引用、被引用、賦值等狀況進行監控,Java會使用有向圖的方法進行管理內存,實時監控對象是否可以達到,如果不可到達,則就將其回收,這樣也可以消除引用循環的問題。在Java語言中,判斷一個內存空間是否符合垃圾收集標准有兩個:一個是給對象賦予了空值null,以下再沒有調用過,另一個是給對象賦予了新值,這樣重新分配了內存空間。
二、Java內存泄露引起原因
首先,什麼是內存泄露看經常聽人談起內存泄露,但要問什麼是內存泄露,沒幾個說得清楚。內存泄露是指無用對象(不再使用的對象)持續佔有內存或無用對象的內存得不到及時釋放,從而造成的內存空間的浪費稱為內存泄露。內存泄露有時不嚴重且不易察覺,這樣開發者就不知道存在內存泄露,但有時也會很嚴重,會提示你Out of memory。
那麼,Java內存泄露根本原因是什麼呢看長生命周期的對象持有短生命周期對象的引用就很可能發生內存泄露,盡管短生命周期對象已經不再需要,但是因為長生命周期對象持有它的引用而導致不能被回收,這就是java中內存泄露的發生場景。具體主要有如下幾大類:
1、靜態集合類引起內存泄露:
像HashMap、Vector等的使用最容易出現內存泄露,這些靜態變數的生命周期和應用程序一致,他們所引用的所有的對象Object也不能被釋放,因為他們也將一直被Vector等引用著。
例:
Static Vector v = new Vector(10);
for (int i = 1; i<100; i++)
{
Object o = new Object();
v.add(o);
o = null;
}//
在這個例子中,循環申請Object 對象,並將所申請的對象放入一個Vector 中,如果僅僅釋放引用本身(o=null),那麼Vector 仍然引用該對象,所以這個對象對GC 來說是不可回收的。因此,如果對象加入到Vector 後,還必須從Vector 中刪除,最簡單的方法就是將Vector對象設置為null。
2、當集合裡面的對象屬性被修改後,再調用remove()方法時不起作用。
例:
public static void main(String[] args)
{
Set<Person> set = new HashSet<Person>();
Person p1 = new Person("唐僧","pwd1",25);
Person p2 = new Person("孫悟空","pwd2",26);
Person p3 = new Person("豬八戒","pwd3",27);
set.add(p1);
set.add(p2);
set.add(p3);
System.out.println("總共有:"+set.size()+" 個元素!"); //結果:總共有:3 個元素!
p3.setAge(2); //修改p3的年齡,此時p3元素對應的hashcode值發生改變

set.remove(p3); //此時remove不掉,造成內存泄漏
set.add(p3); //重新添加,居然添加成功
System.out.println("總共有:"+set.size()+" 個元素!"); //結果:總共有:4 個元素!
for (Person person : set)
{
System.out.println(person);
}
}
3、監聽器
在java 編程中,我們都需要和監聽器打交道,通常一個應用當中會用到很多監聽器,我們會調用一個控制項的諸如addXXXListener()等方法來增加監聽器,但往往在釋放對象的時候卻沒有記住去刪除這些監聽器,從而增加了內存泄漏的機會。
4、各種連接
比如資料庫連接(dataSourse.getConnection()),網路連接(socket)和io連接,除非其顯式的調用了其close()方法將其連接關閉,否則是不會自動被GC 回收的。對於Resultset 和Statement 對象可以不進行顯式回收,但Connection 一定要顯式回收,因為Connection 在任何時候都無法自動回收,而Connection一旦回收,Resultset 和Statement 對象就會立即為NULL。但是如果使用連接池,情況就不一樣了,除了要顯式地關閉連接,還必須顯式地關閉Resultset Statement 對象(關閉其中一個,另外一個也會關閉),否則就會造成大量的Statement 對象無法釋放,從而引起內存泄漏。這種情況下一般都會在try裡面去的連接,在finally裡面釋放連接。
5、內部類和外部模塊等的引用
內部類的引用是比較容易遺忘的一種,而且一旦沒釋放可能導致一系列的後繼類對象沒有釋放。此外程序員還要小心外部模塊不經意的引用,例如程序員A 負責A 模塊,調用了B 模塊的一個方法如:
public void registerMsg(Object b);
這種調用就要非常小心了,傳入了一個對象,很可能模塊B就保持了對該對象的引用,這時候就需要注意模塊B 是否提供相應的操作去除引用。
6、單例模式
不正確使用單例模式是引起內存泄露的一個常見問題,單例對象在被初始化後將在JVM的整個生命周期中存在(以靜態變數的方式),如果單例對象持有外部對象的引用,那麼這個外部對象將不能被jvm正常回收,導致內存泄露,考慮下面的例子:
class A{
public A(){
B.getInstance().setA(this);
}
....
}
//B類採用單例模式
class B{
private A a;
private static B instance=new B();
public B(){}
public static B getInstance(){
return instance;
}
public void setA(A a){
this.a=a;
}
//getter...
}
顯然B採用singleton模式,它持有一個A對象的引用,而這個A類的對象將不能被回收。想像下如果A是個比較復雜的對象或者集合類型會發生什麼情況

⑨ 如何防止java中的內存泄漏

盡管java虛
擬機和垃圾回收機制治理著大部分的內存事務,但是在java軟體中還是可能存在內存泄漏的情況。的確,在大型工程中,內存泄漏是一個普遍問題。避免內存泄
漏的第一步,就是要了解他們發生的原因。這篇文章就是要介紹一些常見的缺陷,然後提供一些非常好的實踐例子來指導你寫出沒有內存泄漏的代碼。一旦你的程序
存在內存泄漏,要查明代碼中引起泄漏的原因是很困難的。同時這篇文章也要介紹一個新的工具來查找內存泄漏,然後指明發生的根本原因。這個工具輕易上手,可
以讓你找到產品級系統中的內存泄漏。

垃圾回收(GC)的角色

雖然垃圾回收關心著大部分的
問題,包括內存治理,使得程序員的任務顯得更加輕松,但是程序員還是可能犯些錯誤導致內存泄漏問題。GC(垃圾回收)通過遞歸對所有從「根」對象(堆棧中
的對象,靜態數據成員,JNI句柄等等)繼續下來的引用進行工作,然後標記所有可以訪問的活動著的對象。而這些對象變成了程序唯一能夠操縱的對象,其他的
對象都被釋放了。因為GC使得程序不能夠訪問那些被釋放的對象,所以這樣做是安全的。

內存治理可以說是自動的,但是這並沒有讓程
序員脫離內存治理問題。比方說,對於內存的分配(還有釋放)總是存在一定的開銷,盡管這些開銷對程序員來說是隱含的。一個程序假如創建了很多對象,那麼它
就要比完成相同任務而創建了較少對象的程序執行的速度慢(假如其他的條件都相同)。

文章更多想說的,導致內存泄漏主要的原因是,
先前申請了內存空間而忘記了釋放。假如程序中存在對無用對象的引用,那麼這些對象就會駐留內存,消耗內存,因為無法讓垃圾回收器驗證這些對象是否不再需
要。正如我們前面看到的,假如存在對象的引用,這個對象就被定義為「活動的」,同時不會被釋放。要確定對象所佔內存將被回收,程序員就要務必確認該對象不
再會被使用。典型的做法就是把對象數據成員設為null或者從集合中移除該對象。注重,當局部變數不需要時,不需明顯的設為null,因為一個方法執行完
畢時,這些引用會自動被清理。

從更高一個層次看,這就是所有存在內存管的語言對內存泄漏所考慮的事情,剩餘的對象引用將不再會被使用。

典型的泄漏

既然我們知道了在java中確實會存在內存泄漏,那麼就讓我們看一些典型的泄漏,並找出他們發生的原因。

全局集合

在大型應用程序中存在各種各樣的全局數據倉庫是很普遍的,比如一個JNDI-tree或者一個session table。在這些情況下,注重力就被放在了治理數據倉庫的大小上。當然是有一些適當的機制可以將倉庫中的無用數據移除。

可以有很多不同的解決形式,其中最常用的是一種周期運行的清除作業。這個作業會驗證倉庫中的數據然後清除一切不需要的數據。

另一個辦法是計算引用的數量。集合負責跟蹤集合中每個元素的引用者數量。這要求引用者通知集合什麼時候已經對元素處理完畢。當引用者的數目為零時,就可以移除集合中的相關元素。

高速緩存

高速緩存是一種用來快速查找已經執行過的操作結果的數據結構。因此,假如一個操作執行很慢的話,你可以先把普通輸入的數據放入高速緩存,然後過些時間再調用高速緩存中的數據。

高速緩存多少還有一點動態實現的意思,當數據操作完畢,又被送入高速緩存。一個典型的演算法如下所示:

1. 檢查結果是否在高速緩存中,存在則返回結果;

2. 假如結果不在,那麼計算結果;

3. 將結果放入高速緩存,以備將來的操作調用。

這個演算法的問題(或者說潛在的內存泄漏)在最後一步。假如操作是分別多次輸入,那麼存入高速緩存的內容將會非常大。很明顯這個方法不可取。

為了避免這種潛在的致命錯誤設計,程序就必須確定高速緩存在他所使用的內存中有一個上界。因此,更好的演算法是:

1. 檢查結果是否在高速緩存中,存在則返回結果;

2. 假如結果不在,那麼計算結果;

3. 假如高速緩存所佔空間過大,移除緩存中舊的結果;

4. 將結果放入高速緩存,以備將來的操作調用。

通過不斷的從緩存中移除舊的結果,我們可以假設,將來,最新輸入的數據可能被重用的幾率要遠遠大於舊的結果。這通常是一個不錯的設想。

這個新的演算法會確保高速緩存的容量在預先確定的范圍內。精確的范圍是很難計算的,因為緩存中的對象存在引用時將繼續有效。正確的劃分高速緩存的大小是一個復雜的任務,你必須權衡可使用內存大小和數據快速存取之間的矛盾。

另一個解決這個問題的途徑是使用java.lang.ref.SoftReference類來將對象放入高速緩存。這個方法可以保證當虛擬機用完內存或者需要更多堆的時候,可以釋放這些對象的引用。

類裝載器


Java類裝載器創建就存在很多導致內存泄漏的漏洞。由於類裝載器的復雜結構,使得很難得到內存泄漏的透視圖。這些困難不僅僅是由於類裝載器只與「普通
的」對象引用有關,同時也和對象內部的引用有關,比如數據變數,方法和各種類。這意味著只要存在對數據變數,方法,各種類和對象的類裝載器,那麼類裝載器
將駐留在JVM中。既然類裝載器可以同很多的類關聯,同時也可以和靜態數據變數關聯,那麼相當多的內存就可能發生泄漏。

定位內存泄漏


經常地,程序內存泄漏的最初跡象發生在出錯之後,在你的程序中得到一個OutOfMemoryError。這種典型的情況發生在產品環境中,而在那裡,
你希望內存泄漏盡可能的少,調試的可能性也達到最小。也許你的測試環境和產品的系統環境不盡相同,導致泄露的只會在產品中暴露。這種情況下,你需要一個低
負荷的工具來監聽和尋找內存泄漏。同時,你還需要把這個工具同你的系統聯系起來,而不需要重新啟動他或者機械化你的代碼。也許更重要的是,當你做分析的時
候,你需要能夠同工具分離而使得系統不會受到干擾。

一個OutOfMemoryError經常是內存泄漏的一個標志,有可能應用
程序的確用了太多的內存;這個時候,你既不能增加JVM的堆的數量,也不能改變你的程序而使得他減少內存使用。但是,在大多數情況下,一個
OutOfMemoryError是內存泄漏的標志。一個解決辦法就是繼續監聽GC的活動,看看隨時間的流逝,內存使用量是否會增加,假如有,程序中一定
存在內存泄漏。

具體輸出

有很多辦法來監聽垃圾回收器的活動。也許運用最廣泛的就是以:-Xverbose:gc選項運行JVM,然後觀察輸出結果一段時間。

[memory] 10.109-10.235: GC 65536K->16788K (65536K), 126.000 ms

箭頭後的值(在這個例子中 16788K)是垃圾回收後堆的使用量。

控制台

觀察這些無盡的GC具體統計輸出是一件非常單調乏味的事情。好在有一些工具來代替我們做這些事情。The JRockit Management Console可以用圖形的方式輸出堆的使用量。通過觀察圖像,我們可以很方便的觀察堆的使用量是否伴隨時間增長。

Figure 1. The JRockit Management Console

治理控制台甚至可以配置成在堆使用量出現問題(或者其他的事件發生)時向你發送郵件。這個顯然使得監控內存泄漏更加輕易。

內存泄漏探測工具


有很多專門的內存泄漏探測工具。其中The JRockit Memory Leak
Detector可以供來觀察內存泄漏也可以針對性地找到泄漏的原因。這個強大的工具被緊密地集成在JRockit
JVM中,可以提供最低可能的內存事務也可以輕松的訪問虛擬機的堆。

專門工具的優勢

一旦
你知道程序中存在內存泄漏,你需要更專業的工具來查明為什麼這里會有泄漏。而JVM是不可能告訴你的。現在有很多工具可以利用了。這些工具本質上主要通過
兩種方法來得到JVM的存儲系統信息的:JVMTI和位元組碼儀器。Java虛擬機工具介面(JVMTI)和他的原有形式JVMPI(壓型介面,PRofiling Interface)都是標准介面,作為外部工具同JVM進行通信,搜集JVM的信息。位元組碼儀器則是引用通過探針獲得工具所需的位元組信息的預處理技術。


通過這些技術來偵測內存泄漏存在兩個缺點,而這使得他們在產品級環境中的運用不夠理想。首先,根據兩者對內存的使用量和內存事務性能的降級是不可以忽略
的。從JVM獲得的堆的使用量信息需要在工具中導出,收集和處理。這意味著要分配內存。按照JVM的性能導出信息是需要開銷的,垃圾回收器在搜集信息的時
候是運行的非常緩慢的。另一個缺點就是,這些工具所需要的信息是關繫到JVM的。讓工具在JVM開始運行的時候和它關聯,而在分析的時候,分離工具而保持
JVM運行,這顯然是不可能的。

既然JRockit Memory Leak
Detector是被集成到JVM中的,那麼以上兩種缺點就不再存在。首先,大部分的處理和分析都是在JVM中完成的,所以就不再需要傳送或重建任何數
據。處理也可以建立在垃圾回收器的基礎上,即提高速度。再有,內存泄漏偵測器可以同一個運行的JVM關聯和分離,只要JVM在開始的時候伴隨著
–Xmanagement選項(通過遠程JMX介面答應監聽和治理JVM)。當工具分離以後,工具不會遺留任何東西在JVM中;JVM就可以全速運行代碼
就似乎工具關聯之前一樣。

⑩ Java 輸出流 內存溢出問題

把tomcat 的內存調大

熱點內容
不知密碼如何刪除簡訊 發布:2024-12-26 12:05:46 瀏覽:892
普通民眾怎麼存儲汽油 發布:2024-12-26 12:05:36 瀏覽:628
安卓手機已安裝的軟體如何備份 發布:2024-12-26 12:04:59 瀏覽:421
好玩兒的我的世界伺服器電腦 發布:2024-12-26 12:04:58 瀏覽:112
C表格源碼 發布:2024-12-26 11:56:18 瀏覽:680
emobile伺服器地址查詢 發布:2024-12-26 11:56:17 瀏覽:240
aspnet資料庫路徑 發布:2024-12-26 11:47:35 瀏覽:973
皮卡堂怎麼找到以前玩過的伺服器 發布:2024-12-26 11:45:59 瀏覽:123
瀏覽器如何變電腦版安卓 發布:2024-12-26 11:44:36 瀏覽:179
vivo微信怎麼加密碼鎖 發布:2024-12-26 11:34:14 瀏覽:405