private void compressImageFile() { JFileChooser fileChooser = new JFileChooser("d://"); fileChooser.showOpenDialog(this); File file = fileChooser.getSelectedFile(); if (null != file && file.exists()) { Toolkit toolkit = Toolkit.getDefaultToolkit(); Image srcImage = toolkit.getImage(file.getAbsolutePath()); // 構(gòu)造Image對象 int wideth = -1; int height = -1; boolean flag = true; while (flag) { wideth = srcImage.getWidth(null); // 得到源圖寬 height = srcImage.getHeight(null); // 得到源圖長 System.out.println("wideth:" + wideth + " height:" + height); if (wideth > 0 && height > 0) { // 因?yàn)?Toolkit.getImage 是異步讀取,如果 // wideth 和 height // 都大于0,表明圖片已經(jīng)加載完畢 // imageCanvas.setImage(srcImage); flag = false; } else { try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } } } // 加載完畢,輸出 int w = 1024; float s = (float) wideth / 1024.0f; int h = (int) ((float) height / s); BufferedImage bufferedImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB); // bufferedImage.getGraphics().drawImage(srcImage, 0, 0, 1024, // 768, null); // 繪制縮小后的圖 boolean flag2 = false; while (!(flag2 = bufferedImage.getGraphics().drawImage(srcImage, 0, 0, w, h, this))) { try { Thread.sleep(10); } catch (Exception e) { e.printStackTrace(); } } try { File outputFile = new File("d://hhh.jpg"); if (!outputFile.exists()) { outputFile.createNewFile(); } FileOutputStream out = new FileOutputStream(outputFile); // 輸出到文件流 JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out); encoder.encode(bufferedImage); // 近JPEG編碼 out.close(); } catch (Exception e) { e.printStackTrace(); } } }
PS: 閱讀之后感觸很深,以前寫程序都沒考慮到這些細(xì)節(jié).好好學(xué)習(xí)一下,為了以后能寫出高質(zhì)量代碼的專業(yè)程序員而努力
出自:ibm:developerworks中國網(wǎng)站 歐陽辰周欣
Java的一個重要優(yōu)點(diǎn)就是通過垃圾收集器(Garbage Collection,GC)自動管理內(nèi)存的回收,程序員不需要通過調(diào)用函數(shù)來釋放內(nèi)存。因此,很多程序員認(rèn)為Java不存在內(nèi)存泄漏問題,或者認(rèn)為即使有內(nèi)存泄漏也不是程序的責(zé)任,而是GC或JVM的問題。其實(shí),這種想法是不正確的,因?yàn)镴ava也存在內(nèi)存泄露,但它的表現(xiàn)與C++不同。
隨著越來越多的服務(wù)器程序采用Java技術(shù),例如JSP,Servlet, EJB等,服務(wù)器程序往往長期運(yùn)行。另外,在很多嵌入式系統(tǒng)中,內(nèi)存的總量非常有限。內(nèi)存泄露問題也就變得十分關(guān)鍵,即使每次運(yùn)行少量泄漏,長期運(yùn)行之后,系統(tǒng)也是面臨崩潰的危險(xiǎn)。
為了判斷Java中是否有內(nèi)存泄露,我們首先必須了解Java是如何管理內(nèi)存的。Java的內(nèi)存管理就是對象的分配和釋放問題。在Java中,程序員需要通過關(guān)鍵字new為每個對象申請內(nèi)存空間 (基本類型除外),所有的對象都在堆 (Heap)中分配空間。另外,對象的釋放是由GC決定和執(zhí)行的。在Java中,內(nèi)存的分配是由程序完成的,而內(nèi)存的釋放是有GC完成的,這種收支兩條線的方法確實(shí)簡化了程序員的工作。但同時(shí),它也加重了JVM的工作。這也是Java程序運(yùn)行速度較慢的原因之一。因?yàn)椋珿C為了能夠正確釋放對象,GC必須監(jiān)控每一個對象的運(yùn)行狀態(tài),包括對象的申請、引用、被引用、賦值等,GC都需要進(jìn)行監(jiān)控。
監(jiān)視對象狀態(tài)是為了更加準(zhǔn)確地、及時(shí)地釋放對象,而釋放對象的根本原則就是該對象不再被引用。
為了更好理解GC的工作原理,我們可以將對象考慮為有向圖的頂點(diǎn),將引用關(guān)系考慮為圖的有向邊,有向邊從引用者指向被引對象。另外,每個線程對象可以作為一個圖的起始頂點(diǎn),例如大多程序從main進(jìn)程開始執(zhí)行,那么該圖就是以main進(jìn)程頂點(diǎn)開始的一棵根樹。在這個有向圖中,根頂點(diǎn)可達(dá)的對象都是有效對象,GC將不回收這些對象。如果某個對象 (連通子圖)與這個根頂點(diǎn)不可達(dá)(注意,該圖為有向圖),那么我們認(rèn)為這個(這些)對象不再被引用,可以被GC回收。
以下,我們舉一個例子說明如何用有向圖表示內(nèi)存管理。對于程序的每一個時(shí)刻,我們都有一個有向圖表示JVM的內(nèi)存分配情況。以下右圖,就是左邊程序運(yùn)行到第6行的示意圖。
Java使用有向圖的方式進(jìn)行內(nèi)存管理,可以消除引用循環(huán)的問題,例如有三個對象,相互引用,只要它們和根進(jìn)程不可達(dá)的,那么GC也是可以回收它們的。這種方式的優(yōu)點(diǎn)是管理內(nèi)存的精度很高,但是效率較低。另外一種常用的內(nèi)存管理技術(shù)是使用計(jì)數(shù)器,例如COM模型采用計(jì)數(shù)器方式管理構(gòu)件,它與有向圖相比,精度行低(很難處理循環(huán)引用的問題),但執(zhí)行效率很高。
下面,我們就可以描述什么是內(nèi)存泄漏。在Java中,內(nèi)存泄漏就是存在一些被分配的對象,這些對象有下面兩個特點(diǎn),首先,這些對象是可達(dá)的,即在有向圖中,存在通路可以與其相連;其次,這些對象是無用的,即程序以后不會再使用這些對象。如果對象滿足這兩個條件,這些對象就可以判定為Java中的內(nèi)存泄漏,這些對象不會被GC所回收,然而它卻占用內(nèi)存。
在C++中,內(nèi)存泄漏的范圍更大一些。有些對象被分配了內(nèi)存空間,然后卻不可達(dá),由于C++中沒有GC,這些內(nèi)存將永遠(yuǎn)收不回來。在Java中,這些不可達(dá)的對象都由GC負(fù)責(zé)回收,因此程序員不需要考慮這部分的內(nèi)存泄露。
通過分析,我們得知,對于C++,程序員需要自己管理邊和頂點(diǎn),而對于Java程序員只需要管理邊就可以了(不需要管理頂點(diǎn)的釋放)。通過這種方式,Java提高了編程的效率。
因此,通過以上分析,我們知道在Java中也有內(nèi)存泄漏,但范圍比C++要小一些。因?yàn)镴ava從語言上保證,任何對象都是可達(dá)的,所有的不可達(dá)對象都由GC管理。
對于程序員來說,GC基本是透明的,不可見的。雖然,我們只有幾個函數(shù)可以訪問GC,例如運(yùn)行GC的函數(shù)System.gc(),但是根據(jù)Java語言規(guī)范定義, 該函數(shù)不保證JVM的垃圾收集器一定會執(zhí)行。因?yàn)椋煌腏VM實(shí)現(xiàn)者可能使用不同的算法管理GC。通常,GC的線程的優(yōu)先級別較低。JVM調(diào)用GC的策略也有很多種,有的是內(nèi)存使用到達(dá)一定程度時(shí),GC才開始工作,也有定時(shí)執(zhí)行的,有的是平緩執(zhí)行GC,有的是中斷式執(zhí)行GC。但通常來說,我們不需要關(guān)心這些。除非在一些特定的場合,GC的執(zhí)行影響應(yīng)用程序的性能,例如對于基于Web的實(shí)時(shí)系統(tǒng),如網(wǎng)絡(luò)游戲等,用戶不希望GC突然中斷應(yīng)用程序執(zhí)行而進(jìn)行垃圾回收,那么我們需要調(diào)整GC的參數(shù),讓GC能夠通過平緩的方式釋放內(nèi)存,例如將垃圾回收分解為一系列的小步驟執(zhí)行,Sun提供的HotSpot JVM就支持這一特性。
下面給出了一個簡單的內(nèi)存泄露的例子。在這個例子中,我們循環(huán)申請Object對象,并將所申請的對象放入一個Vector中,如果我們僅僅釋放引用本身,那么Vector仍然引用該對象,所以這個對象對GC來說是不可回收的。因此,如果對象加入到Vector后,還必須從Vector中刪除,最簡單的方法就是將Vector對象設(shè)置為null。
Vector v=new Vector(10);
for (int i=1;i<100; i++)
{
Object o=new Object();
v.add(o);
o=null;
}
//此時(shí),所有的Object對象都沒有被釋放,因?yàn)樽兞縱引用這些對象。
最后一個重要的問題,就是如何檢測Java的內(nèi)存泄漏。目前,我們通常使用一些工具來檢查Java程序的內(nèi)存泄漏問題。市場上已有幾種專業(yè)檢查Java內(nèi)存泄漏的工具,它們的基本工作原理大同小異,都是通過監(jiān)測Java程序運(yùn)行時(shí),所有對象的申請、釋放等動作,將內(nèi)存管理的所有信息進(jìn)行統(tǒng)計(jì)、分析、可視化。開發(fā)人員將根據(jù)這些信息判斷程序是否有內(nèi)存泄漏問題。這些工具包括Optimizeit Profiler,JProbe Profiler,JinSight , Rational 公司的Purify等。
下面,我們將簡單介紹Optimizeit的基本功能和工作原理。
Optimizeit Profiler版本4.11支持Application,Applet,Servlet和Romote Application四類應(yīng)用,并且可以支持大多數(shù)類型的JVM,包括SUN JDK系列,IBM的JDK系列,和Jbuilder的JVM等。并且,該軟件是由Java編寫,因此它支持多種操作系統(tǒng)。Optimizeit系列還包括Thread Debugger和Code Coverage兩個工具,分別用于監(jiān)測運(yùn)行時(shí)的線程狀態(tài)和代碼覆蓋面。
當(dāng)設(shè)置好所有的參數(shù)了,我們就可以在OptimizeIt環(huán)境下運(yùn)行被測程序,在程序運(yùn)行過程中,Optimizeit可以監(jiān)視內(nèi)存的使用曲線(如下圖),包括JVM申請的堆(heap)的大小,和實(shí)際使用的內(nèi)存大小。另外,在運(yùn)行過程中,我們可以隨時(shí)暫停程序的運(yùn)行,甚至強(qiáng)行調(diào)用GC,讓GC進(jìn)行內(nèi)存回收。通過內(nèi)存使用曲線,我們可以整體了解程序使用內(nèi)存的情況。這種監(jiān)測對于長期運(yùn)行的應(yīng)用程序非常有必要,也很容易發(fā)現(xiàn)內(nèi)存泄露。
在運(yùn)行過程中,我們還可以從不同視角觀查內(nèi)存的使用情況,Optimizeit提供了四種方式:
在運(yùn)行過程中,我們可以隨時(shí)觀察內(nèi)存的使用情況,通過這種方式,我們可以很快找到那些長期不被釋放,并且不再使用的對象。我們通過檢查這些對象的生存周期,確認(rèn)其是否為內(nèi)存泄露。在實(shí)踐當(dāng)中,尋找內(nèi)存泄露是一件非常麻煩的事情,它需要程序員對整個程序的代碼比較清楚,并且需要豐富的調(diào)試經(jīng)驗(yàn),但是這個過程對于很多關(guān)鍵的Java程序都是十分重要的。
綜上所述,Java也存在內(nèi)存泄露問題,其原因主要是一些對象雖然不再被使用,但它們?nèi)匀槐灰谩榱私鉀Q這些問題,我們可以通過軟件工具來檢查內(nèi)存泄露,檢查的主要原理就是暴露出所有堆中的對象,讓程序員尋找那些無用但仍被引用的對象。
文章:
Jim Patrick, Handling memory leaks in Java programs,
http://www-106.ibm.com/developerWorks/library/j-leaks/index.html
Ed Lycklama, Does Java Technology Have Memory Leaks?
http://www.klgroup.com/javaone
Sun, The Java HotSpot Virtual Machine, Technical White Paper
軟件:
Sitraka Software's Jprobe http://www.sitraka.com
Boland Software's Optimizeit http://optimizeit
IBM alphaWorks' Jinsight http://www.alphaworks.ibm.com/tech/jinsight
關(guān)于作者
歐陽辰,北京大學(xué)計(jì)算機(jī)碩士畢業(yè),98年起開始研究基于java的軟件開發(fā)、測試,參與開發(fā)、測試過多個基于Java的應(yīng)用程序和Web服務(wù)項(xiàng)目。聯(lián)系方式yeekee@sina.com
周欣,北京大學(xué)計(jì)算機(jī)系在讀博士生,主要研究方向:程序理解、逆向工程及軟件度量,聯(lián)系方式 zhouxin@sei.pku.edu.cn
線程 是 java 的一大特性,它可以是給定的指令序列、給定的方法中定義的變量或者一些共享數(shù)據(jù)(類一級的變量)。 在 java 中每個 線程 有自己的堆棧和程序計(jì)數(shù)器(pc),其中堆棧是用來跟蹤 線程 的上下文(上下文是當(dāng) 線程 執(zhí)行到某處時(shí),當(dāng)前的局部變量的值),而程序計(jì)數(shù)器則用來跟蹤當(dāng)前 線程 正在執(zhí)行的指令。
在通常情況下,一個 線程 不能訪問另外一個 線程 的堆棧變量,而且這個 線程 必須處于如下狀態(tài)之一:
1.排隊(duì)狀態(tài)(ready),在用戶創(chuàng)建了一個 線程 以后,這個 線程 不會立即運(yùn)行。當(dāng) 線程 中的方法start()被調(diào)用時(shí),這個 線程 就會進(jìn)行排隊(duì)狀態(tài),等待調(diào)度程序?qū)⑺D(zhuǎn)入運(yùn)行狀態(tài)(running)。當(dāng)一個進(jìn)程被執(zhí)行后它也可以進(jìn)行排隊(duì)狀態(tài)。如果調(diào)度程序允許的話,通過調(diào)用方法yield()就可以將進(jìn)程放入排隊(duì)狀態(tài)。
2.運(yùn)行狀態(tài)(running),當(dāng)調(diào)度程序?qū)pu的運(yùn)行時(shí)間分配給一個 線程 ,這個 線程 就進(jìn)入了運(yùn)行狀態(tài)開始運(yùn)行。
3.等待狀態(tài)(waiting),很多原因都可以導(dǎo)致 線程 處于等待狀態(tài),例如 線程 執(zhí)行過程中被暫停,或者是等待i/o請求的完成而進(jìn)入等待狀態(tài)。
在 java 中不同的 線程 具有不同的優(yōu)先級,高優(yōu)先級的 線程 可以安排在低優(yōu)先級 線程 之前完成。如果多個 線程 具有相同的優(yōu)先級, java 會在不同的 線程 之間切換運(yùn)行。一個應(yīng)用程序可以通過使用 線程 中的方法setpriority()來設(shè)置 線程 的優(yōu)先級,使用方法getpriority()來獲得一個 線程 的優(yōu)先級。
線程 的生命周期
一個 線程 的的生命周期可以分成兩階段:生存(alive)周期和死亡(dead)周期,其中生存周期又包括運(yùn)行狀態(tài)(running)和等待狀態(tài)(waiting)。當(dāng)創(chuàng)建一個新 線程 后,這個 線程 就進(jìn)入了排隊(duì)狀態(tài)(ready),當(dāng) 線程 中的方法start()被調(diào)用時(shí), 線程 就進(jìn)入生存周期,這時(shí)它的方法isalive()始終返回真值,直至 線程 進(jìn)入死亡狀態(tài)。
線程 的實(shí)現(xiàn)
有兩種方法可以實(shí)現(xiàn) 線程 ,一種是擴(kuò)展 java .lang.thread類,另一種是通過 java .lang.runnable接口。
地址:http://blog.csdn.net/jerryao/archive/2006/07/04/874101.aspx
一、堆棧(stack)和堆(heap)?
(1)內(nèi)存分配的策略
按照編譯原理的觀點(diǎn),程序運(yùn)行時(shí)的內(nèi)存分配有三種策略,分別是靜態(tài)的,棧式的,和堆式的.
靜態(tài)存儲分配是指在編譯時(shí)就能確定每個數(shù)據(jù)目標(biāo)在運(yùn)行時(shí)刻的存儲空間需求,因而在編譯時(shí)就可以給他們分配固定的內(nèi)存空間.這種分配策略要求程序代碼中不允許有可變數(shù)據(jù)結(jié)構(gòu)(比如可變數(shù)組)的存在,也不允許有嵌套或者遞歸的結(jié)構(gòu)出現(xiàn),因?yàn)樗鼈兌紩?dǎo)致編譯程序無法計(jì)算準(zhǔn)確的存儲空間需求.
棧式存儲分配也可稱為動態(tài)存儲分配,是由一個類似于堆棧的運(yùn)行棧來實(shí)現(xiàn)的.和靜態(tài)存儲分配相反,在棧式存儲方案中,程序?qū)?shù)據(jù)區(qū)的需求在編譯時(shí)是完全未知的,只有到運(yùn)行的時(shí)候才能夠知道,但是規(guī)定在運(yùn)行中進(jìn)入一個程序模塊時(shí),必須知道該程序模塊所需的數(shù)據(jù)區(qū)大小才能夠?yàn)槠浞峙鋬?nèi)存.和我們在數(shù)據(jù)結(jié)構(gòu)所熟知的棧一樣,棧式存儲分配按照先進(jìn)后出的原則進(jìn)行分配。
靜態(tài)存儲分配要求在編譯時(shí)能知道所有變量的存儲要求,棧式存儲分配要求在過程的入口處必須知道所有的存儲要求,而堆式存儲分配則專門負(fù)責(zé)在編譯時(shí)或運(yùn)行時(shí)模塊入口處都無法確定存儲要求的數(shù)據(jù)結(jié)構(gòu)的內(nèi)存分配,比如可變長度串和對象實(shí)例.堆由大片的可利用塊或空閑塊組成,堆中的內(nèi)存可以按照任意順序分配和釋放.
(2)堆和棧的比較
上面的定義從編譯原理的教材中總結(jié)而來,除靜態(tài)存儲分配之外,都顯得很呆板和難以理解,下面撇開靜態(tài)存儲分配,集中比較堆和棧:
從堆和棧的功能和作用來通俗的比較, 堆主要用來存放對象的,棧主要是用來執(zhí)行程序的 .而這種不同又主要是由于堆和棧的特點(diǎn)決定的:
在編程中,例如C/C++中,所有的方法調(diào)用都是通過棧來進(jìn)行的,所有的局部變量,形式參數(shù)都是從棧中分配內(nèi)存空間的。實(shí)際上也不是什么分配,只是從棧頂向上用就行,就好像工廠中的傳送帶(conveyor belt)一樣,Stack Pointer會自動指引你到放東西的位置,你所要做的只是把東西放下來就行.退出函數(shù)的時(shí)候,修改棧指針就可以把棧中的內(nèi)容銷毀.這樣的模式速度最快,當(dāng)然要用來運(yùn)行程序了.需要注意的是,在分配的時(shí)候,比如為一個即將要調(diào)用的程序模塊分配數(shù)據(jù)區(qū)時(shí),應(yīng)事先知道這個數(shù)據(jù)區(qū)的大小,也就說是雖然分配是在程序運(yùn)行時(shí)進(jìn)行的,但是分配的大小多少是確定的,不變的,而這個"大小多少"是在編譯時(shí)確定的,不是在運(yùn)行時(shí).
堆是應(yīng)用程序在運(yùn)行的時(shí)候請求操作系統(tǒng)分配給自己內(nèi)存,由于從操作系統(tǒng)管理的內(nèi)存分配,所以在分配和銷毀時(shí)都要占用時(shí)間,因此用堆的效率非常低.但是堆的優(yōu)點(diǎn)在于,編譯器不必知道要從堆里分配多少存儲空間,也不必知道存儲的數(shù)據(jù)要在堆里停留多長的時(shí)間,因此,用堆保存數(shù)據(jù)時(shí)會得到更大的靈活性。事實(shí)上,面向?qū)ο蟮亩鄳B(tài)性,堆內(nèi)存分配是必不可少的,因?yàn)槎鄳B(tài)變量所需的存儲空間只有在運(yùn)行時(shí)創(chuàng)建了對象之后才能確定.在C++中,要求創(chuàng)建一個對象時(shí),只需用new命令編制相關(guān)的代碼即可。執(zhí)行這些代碼時(shí),會在堆里自動進(jìn)行數(shù)據(jù)的保存.當(dāng)然,為達(dá)到這種靈活性,必然會付出一定的代價(jià):在堆里分配存儲空間時(shí)會花掉更長的時(shí)間!這也正是導(dǎo)致我們剛才所說的效率低的原因,看來列寧同志說的好,人的優(yōu)點(diǎn)往往也是人的缺點(diǎn),人的缺點(diǎn)往往也是人的優(yōu)點(diǎn)(暈~).
(3)JVM中的堆和棧
JVM是基于堆棧的虛擬機(jī).JVM為每個新創(chuàng)建的線程都分配一個堆棧.也就是說,對于一個Java程序來說,它的運(yùn)行就是通過對堆棧的操作來完成的。堆棧以幀為單位保存線程的狀態(tài)。JVM對堆棧只進(jìn)行兩種操作:以幀為單位的壓棧和出棧操作。
我們知道,某個線程正在執(zhí)行的方法稱為此線程的當(dāng)前方法.我們可能不知道,當(dāng)前方法使用的幀稱為當(dāng)前幀。當(dāng)線程激活一個Java方法,JVM就會在線程的Java堆棧里新壓入一個幀。這個幀自然成為了當(dāng)前幀.在此方法執(zhí)行期間,這個幀將用來保存參數(shù),局部變量,中間計(jì)算過程和其他數(shù)據(jù).這個幀在這里和編譯原理中的活動紀(jì)錄的概念是差不多的.
從Java的這種分配機(jī)制來看,堆棧又可以這樣理解:堆棧(Stack)是操作系統(tǒng)在建立某個進(jìn)程時(shí)或者線程(在支持多線程的操作系統(tǒng)中是線程)為這個線程建立的存儲區(qū)域,該區(qū)域具有先進(jìn)后出的特性。
每一個Java應(yīng)用都唯一對應(yīng)一個JVM實(shí)例,每一個實(shí)例唯一對應(yīng)一個堆。應(yīng)用程序在運(yùn)行中所創(chuàng)建的所有類實(shí)例或數(shù)組都放在這個堆中,并由應(yīng)用所有的線程共享.跟C/C++不同,Java中分配堆內(nèi)存是自動初始化的。Java中所有對象的存儲空間都是在堆中分配的,但是這個對象的引用卻是在堆棧中分配,也就是說在建立一個對象時(shí)從兩個地方都分配內(nèi)存,在堆中分配的內(nèi)存實(shí)際建立這個對象,而在堆棧中分配的內(nèi)存只是一個指向這個堆對象的指針(引用)而已。
地址:http://blog.csdn.net/jerryao/archive/2006/07/04/874101.aspx
一、堆棧(stack)和堆(heap)?
(1)內(nèi)存分配的策略
按照編譯原理的觀點(diǎn),程序運(yùn)行時(shí)的內(nèi)存分配有三種策略,分別是靜態(tài)的,棧式的,和堆式的.
靜態(tài)存儲分配是指在編譯時(shí)就能確定每個數(shù)據(jù)目標(biāo)在運(yùn)行時(shí)刻的存儲空間需求,因而在編譯時(shí)就可以給他們分配固定的內(nèi)存空間.這種分配策略要求程序代碼中不允許有可變數(shù)據(jù)結(jié)構(gòu)(比如可變數(shù)組)的存在,也不允許有嵌套或者遞歸的結(jié)構(gòu)出現(xiàn),因?yàn)樗鼈兌紩?dǎo)致編譯程序無法計(jì)算準(zhǔn)確的存儲空間需求.
棧式存儲分配也可稱為動態(tài)存儲分配,是由一個類似于堆棧的運(yùn)行棧來實(shí)現(xiàn)的.和靜態(tài)存儲分配相反,在棧式存儲方案中,程序?qū)?shù)據(jù)區(qū)的需求在編譯時(shí)是完全未知的,只有到運(yùn)行的時(shí)候才能夠知道,但是規(guī)定在運(yùn)行中進(jìn)入一個程序模塊時(shí),必須知道該程序模塊所需的數(shù)據(jù)區(qū)大小才能夠?yàn)槠浞峙鋬?nèi)存.和我們在數(shù)據(jù)結(jié)構(gòu)所熟知的棧一樣,棧式存儲分配按照先進(jìn)后出的原則進(jìn)行分配。
靜態(tài)存儲分配要求在編譯時(shí)能知道所有變量的存儲要求,棧式存儲分配要求在過程的入口處必須知道所有的存儲要求,而堆式存儲分配則專門負(fù)責(zé)在編譯時(shí)或運(yùn)行時(shí)模塊入口處都無法確定存儲要求的數(shù)據(jù)結(jié)構(gòu)的內(nèi)存分配,比如可變長度串和對象實(shí)例.堆由大片的可利用塊或空閑塊組成,堆中的內(nèi)存可以按照任意順序分配和釋放.
(2)堆和棧的比較
上面的定義從編譯原理的教材中總結(jié)而來,除靜態(tài)存儲分配之外,都顯得很呆板和難以理解,下面撇開靜態(tài)存儲分配,集中比較堆和棧:
從堆和棧的功能和作用來通俗的比較, 堆主要用來存放對象的,棧主要是用來執(zhí)行程序的 .而這種不同又主要是由于堆和棧的特點(diǎn)決定的:
在編程中,例如C/C++中,所有的方法調(diào)用都是通過棧來進(jìn)行的,所有的局部變量,形式參數(shù)都是從棧中分配內(nèi)存空間的。實(shí)際上也不是什么分配,只是從棧頂向上用就行,就好像工廠中的傳送帶(conveyor belt)一樣,Stack Pointer會自動指引你到放東西的位置,你所要做的只是把東西放下來就行.退出函數(shù)的時(shí)候,修改棧指針就可以把棧中的內(nèi)容銷毀.這樣的模式速度最快,當(dāng)然要用來運(yùn)行程序了.需要注意的是,在分配的時(shí)候,比如為一個即將要調(diào)用的程序模塊分配數(shù)據(jù)區(qū)時(shí),應(yīng)事先知道這個數(shù)據(jù)區(qū)的大小,也就說是雖然分配是在程序運(yùn)行時(shí)進(jìn)行的,但是分配的大小多少是確定的,不變的,而這個"大小多少"是在編譯時(shí)確定的,不是在運(yùn)行時(shí).
堆是應(yīng)用程序在運(yùn)行的時(shí)候請求操作系統(tǒng)分配給自己內(nèi)存,由于從操作系統(tǒng)管理的內(nèi)存分配,所以在分配和銷毀時(shí)都要占用時(shí)間,因此用堆的效率非常低.但是堆的優(yōu)點(diǎn)在于,編譯器不必知道要從堆里分配多少存儲空間,也不必知道存儲的數(shù)據(jù)要在堆里停留多長的時(shí)間,因此,用堆保存數(shù)據(jù)時(shí)會得到更大的靈活性。事實(shí)上,面向?qū)ο蟮亩鄳B(tài)性,堆內(nèi)存分配是必不可少的,因?yàn)槎鄳B(tài)變量所需的存儲空間只有在運(yùn)行時(shí)創(chuàng)建了對象之后才能確定.在C++中,要求創(chuàng)建一個對象時(shí),只需用new命令編制相關(guān)的代碼即可。執(zhí)行這些代碼時(shí),會在堆里自動進(jìn)行數(shù)據(jù)的保存.當(dāng)然,為達(dá)到這種靈活性,必然會付出一定的代價(jià):在堆里分配存儲空間時(shí)會花掉更長的時(shí)間!這也正是導(dǎo)致我們剛才所說的效率低的原因,看來列寧同志說的好,人的優(yōu)點(diǎn)往往也是人的缺點(diǎn),人的缺點(diǎn)往往也是人的優(yōu)點(diǎn)(暈~).
(3)JVM中的堆和棧
JVM是基于堆棧的虛擬機(jī).JVM為每個新創(chuàng)建的線程都分配一個堆棧.也就是說,對于一個Java程序來說,它的運(yùn)行就是通過對堆棧的操作來完成的。堆棧以幀為單位保存線程的狀態(tài)。JVM對堆棧只進(jìn)行兩種操作:以幀為單位的壓棧和出棧操作。
我們知道,某個線程正在執(zhí)行的方法稱為此線程的當(dāng)前方法.我們可能不知道,當(dāng)前方法使用的幀稱為當(dāng)前幀。當(dāng)線程激活一個Java方法,JVM就會在線程的Java堆棧里新壓入一個幀。這個幀自然成為了當(dāng)前幀.在此方法執(zhí)行期間,這個幀將用來保存參數(shù),局部變量,中間計(jì)算過程和其他數(shù)據(jù).這個幀在這里和編譯原理中的活動紀(jì)錄的概念是差不多的.
從Java的這種分配機(jī)制來看,堆棧又可以這樣理解:堆棧(Stack)是操作系統(tǒng)在建立某個進(jìn)程時(shí)或者線程(在支持多線程的操作系統(tǒng)中是線程)為這個線程建立的存儲區(qū)域,該區(qū)域具有先進(jìn)后出的特性。
每一個Java應(yīng)用都唯一對應(yīng)一個JVM實(shí)例,每一個實(shí)例唯一對應(yīng)一個堆。應(yīng)用程序在運(yùn)行中所創(chuàng)建的所有類實(shí)例或數(shù)組都放在這個堆中,并由應(yīng)用所有的線程共享.跟C/C++不同,Java中分配堆內(nèi)存是自動初始化的。Java中所有對象的存儲空間都是在堆中分配的,但是這個對象的引用卻是在堆棧中分配,也就是說在建立一個對象時(shí)從兩個地方都分配內(nèi)存,在堆中分配的內(nèi)存實(shí)際建立這個對象,而在堆棧中分配的內(nèi)存只是一個指向這個堆對象的指針(引用)而已。
進(jìn)程通常被定義為一個正在運(yùn)行的程序的實(shí)例,它由兩個部分組成:
* 一個組成部分是操作系統(tǒng)用來管理進(jìn)程的內(nèi)核對象。內(nèi)核對象也是系統(tǒng)用來存放關(guān)于進(jìn)程的統(tǒng)計(jì)信息的地方。
* 另一個組成部分是地址空間,它包含所有可執(zhí)行模塊或DLL模塊的代碼和數(shù)據(jù)。它還包含動態(tài)內(nèi)存分配的空間。如線程堆棧和堆棧分配空間。
進(jìn)程是不活潑的。若要使進(jìn)程完成某項(xiàng)操作,它必須擁有一個在它的環(huán)境中運(yùn)行的線程,該線程負(fù)責(zé)執(zhí)行包含在進(jìn)程的地址空間中的代碼。實(shí)際上,單個進(jìn)程可能包含若干個線程,所有這些線程都“同時(shí)”執(zhí)行進(jìn)程地址空間中的代碼。為此,每個線程都有它自己的一組CPU寄存器和它自己的堆棧。每個進(jìn)程至少擁有一個線程,來執(zhí)行進(jìn)程的地址空間中的代碼。如果沒有線程來執(zhí)行進(jìn)程的地址空間中的代碼,那么進(jìn)程就沒有存在的理由了,系統(tǒng)就將自動撤消該進(jìn)程和它的地址空間。
若要使所有這些線程都能運(yùn)行,操作系統(tǒng)就要為每個線程安排一定的CPU時(shí)間。它通過以一種循環(huán)方式為線程提供時(shí)間片(稱為量程),從而造成一種假象,仿佛所有線程都是同時(shí)運(yùn)行的一樣。
當(dāng)創(chuàng)建一個進(jìn)程時(shí),系統(tǒng)會自動創(chuàng)建它的第一個線程,稱為主線程。然后,該線程可以創(chuàng)建其他的線程,而這些線程又能創(chuàng)建更多的線程。
---------------------------------------------------------------摘自 《Windows 核心編程》
進(jìn)程有三大部分:代碼段、數(shù)據(jù)段、PCB(進(jìn)程控制段)。
操作系統(tǒng)正是通過PCB來管理這多個進(jìn)程。在這樣的系統(tǒng)里,進(jìn)程是操作系統(tǒng)獨(dú)立調(diào)度和分派的基本單位,又是一個可擁有資源的獨(dú)立單位。
線程:系統(tǒng)調(diào)度和分派的基本單位。
進(jìn)程和線程有如下不同:
進(jìn)程可以擁有資源,線程共享進(jìn)程擁有的資源
進(jìn)程間的切換必須保存PCB
---------------------------------------------------------------
微軟官方對進(jìn)程和線程的定義:
進(jìn)程:用最簡潔的話來說,進(jìn)程就是一個正在執(zhí)行的程序,一個或多個線程在進(jìn)程中運(yùn)行,線程是操作系統(tǒng)分配CPU運(yùn)算時(shí)間的最小單位。每一個進(jìn)程都提供了運(yùn)行一個程序所必需的資源,一個進(jìn)程具有4GB的虛擬地址空間(Windows NT Server Enterprise Edition及Windows 2000 Advanced Server中低3GB虛擬地址空間供進(jìn)程使用,高1GB供操作系統(tǒng)的內(nèi)核代碼使用。Windows NT/2000中低2GB供進(jìn)程使用,高2GB供操作系統(tǒng)內(nèi)核代碼使用。Windows9X:0——64K只讀空間用來裝入Microsoft DOS信息,64K——4M裝入DOS的兼容代碼,4M——2GB的私有空間供進(jìn)程使用,2GB——3GB的共享空間裝入各種DLL代碼,3GB——4GB為共享的系統(tǒng)內(nèi)核代碼空間,其中共享的2GB——4GB的空間是99%的“內(nèi)存無效頁錯誤”、“General Protect Error(GPE)”及藍(lán)屏的罪魁禍?zhǔn)住#蓤?zhí)行代碼,數(shù)據(jù),對象句柄,環(huán)境變量,優(yōu)先權(quán)以及設(shè)置最大化最小化的功能。每一個進(jìn)程都從一個主線程開始執(zhí)行,但可以在它所擁有的線程中創(chuàng)建額外的線程。一個進(jìn)程的所有線程共享進(jìn)程的虛擬地址空間和系統(tǒng)資源,一個線程的資源包括線程的機(jī)器寄存器設(shè)置,內(nèi)核堆棧,線程環(huán)境變量和進(jìn)程虛擬地址中的用戶堆棧。
---------------------------------------------------------------
兩者的區(qū)別。根據(jù)定義,進(jìn)程為一個數(shù)據(jù)結(jié)構(gòu)及能在其上進(jìn)行的一次操作,它有兩個基本特征,一個是進(jìn)程是可用有資源的獨(dú)立單位,第二個是進(jìn)程同時(shí)又是一個可以獨(dú)立調(diào)度和分派的基本單位,這兩個基本屬性使之能夠獨(dú)立運(yùn)行,也能夠并發(fā)運(yùn)行。但是在并發(fā)運(yùn)行的時(shí)候,系統(tǒng)還需要執(zhí)行一系列操作:
1、需要創(chuàng)建進(jìn)程,并為之分配其所必需的資源。
2、撤銷進(jìn)程,對資源進(jìn)行回收。
3、進(jìn)程切換,它需要保留當(dāng)前進(jìn)程的CPU環(huán)境和設(shè)置新選中進(jìn)程的CPU環(huán)境,為此需要花費(fèi)不少處理時(shí)間。正因?yàn)檫M(jìn)程擁有資源,所以在并發(fā)執(zhí)行進(jìn)程的時(shí)候,在創(chuàng)建、撤銷和切換種,系統(tǒng)需要付出較大的開銷,因此,系統(tǒng)中設(shè)置的進(jìn)程不能太多,進(jìn)程切換的頻率也不能過高,這就限制了并發(fā)程度的提高。為了解決這一問題,于是產(chǎn)生并引入了線程概念。
線程是進(jìn)程中的一個實(shí)體,它的基本思想是將程序的執(zhí)行和資源分開,只擁有一點(diǎn)必不可少的資源。一個進(jìn)程可用有多個線程,但它可以和同屬于同一進(jìn)程的其他線程共享進(jìn)程所擁有的所有的資源,同一進(jìn)程中的線程之間可以并發(fā)執(zhí)行。這樣的話,并發(fā)程度可以獲得顯著的提高。線程也具有許多進(jìn)程所具有的特征,因此被稱為輕型進(jìn)程。
---------------------------------------------------------------網(wǎng)上收集
以前在公司都用SQL Server ,對Oracle的操作不是很熟悉.由于工作需要,我要連接同事的Oracle數(shù)據(jù)庫.于是上網(wǎng)找了方法..
網(wǎng)上方法很多,據(jù)說有兩種方法可以連接遠(yuǎn)程數(shù)據(jù)庫,一種是使用服務(wù)管理里Oracle Net Manager的的圖形界面操作,這種我嘗試了一下,沒有成功.. 于是 我嘗試第二種. 第二種方法是 直接修改Oracle數(shù)據(jù)庫的文件.我覺得這方法比較簡單.于是在這里記一下.
操作步驟:
1)
打開目錄D:\oracle安裝目錄\ora92\network\admin中的文件tnsnames.ora。
2) 添加代碼:
WAREHOUSE =
(DESCRIPTION =
(ADDRESS_LIST =
(ADDRESS = (PROTOCOL = TCP)(HOST=10.1.10.158)(PORT = 1521))
)
(CONNECT_DATA =
(SID = orcl )
)
)
修改說明:
WAREHOUSE:這名字是你用來連接的名字,隨便取.
10.1.10.158:遠(yuǎn)程計(jì)算機(jī)的IP地址.
orcl : 遠(yuǎn)程計(jì)算機(jī)數(shù)據(jù)庫名.
保存之后就可以了.
PS:在admin 文件夾中還看到其他的名字,感覺這名字很熟悉,找個時(shí)間也看看這些個文件間的關(guān)聯(lián).應(yīng)該是一件有意思的事情
JDBC高級應(yīng)用
轉(zhuǎn)自 http://www.blogcn.com/user69/galiasun/index.html
本來想繼續(xù)談JDBC的高級連結(jié)方式,事務(wù)模式.但發(fā)現(xiàn)關(guān)于大對象存儲有很多人在問,所以
先來插入一節(jié)關(guān)于大對象存儲的內(nèi)容,然后再接著原來的思路寫下去.
JDBC的大對象存儲聽起來復(fù)雜,其實(shí)如果你明白了原理以后,就非常簡單,網(wǎng)上有關(guān)這方面的
教材很少,而SUN的文檔中,我從1.2開始看到一在仍然是錯誤的,不知道寫文檔的人長腦子沒
有,就那幾行代碼你試試不就知道了,這么多次重抄下來還是錯誤的.
大對象分類:一般來說,大對象分為:大的文本對象,比如一個很長的文本(請你要注意什么是
文本文件,什么是二進(jìn)制文件)文件,或者是你定義的一個長字符串,比如你定義了:
String s = "我們要去吃飯了......................然后睡覺!";
從吃飯到睡覺中間省略了實(shí)際的10000000000000字,雖然你不會真的定義這么稱的String,但
有時(shí)會從什么地方得到這樣的String,要寫到數(shù)據(jù)庫中.
另一種就是大的二進(jìn)制對象,象執(zhí)行文件,圖象文件等,注意,word,excel,ppt這些"帶格式"的文
檔都應(yīng)該以二進(jìn)制對象存儲.
一般來說,數(shù)據(jù)庫如果支持大對象存儲,會有這幾種類型的SQL數(shù)據(jù)類型:
BLOB,CLOCB,NLOB,也有的數(shù)據(jù)數(shù)只有一種BLOB,基本上是這樣的:BLOB用來存放二進(jìn)制文件,而
CLOB用來存放文本文件,NLOB是對多字節(jié)文本文件支持.假如你的文本文件是純英文的,放在
BLOB中當(dāng)然可以,也就是說它是以byte格式存儲的,而多字節(jié)是以CHAR格式存儲的.
同樣對于這幾種類型的文檔,有幾種相對應(yīng)的存取方式:
setter:
利用PreparedStatement的setXXX方法,
setAsciiStream()方法用于寫入一般的文本流.setBinaryStream()方法用于寫入二進(jìn)制流
而setUnicodeStream()用于寫好UNICODE編碼的文本,與此相對應(yīng)的ResultSet中三個getter方法
用于取回:getAsciiStream(),getBinaryStream(),getBinaryStream().
對于文件本身,要把它作為一個流,只要new InputStream(new FileInputStream("文件路徑")
就可以了,但對于大的String對象,你不會寫入文件再轉(zhuǎn)換成輸入流吧?
new StringBufferInputStream(String s),記住了.
JDBC2以后提供了java.sql.BLOB對象,我不建議大家使用它,一是很麻類,二是容易出錯,要先插
入一個空的BLOB對象,然后再填充它,實(shí)在沒有必要,直接setXXX就行了,我試過,至少mysql,
oracle,sql server是可以直接set的.
好了,我們先看一個例子如何寫入文件到數(shù)據(jù)庫:
數(shù)據(jù)結(jié)構(gòu):
create table test(
name varchar(200),
content BLOB
);
File f = new File("a.exe";//先生成File對象是為了取得流的長度.FileInputStram可以直接
//傳入文件路徑
InputStream in = new InputStream(new FileInputStream(f));
PreparedStatement ps = conn.prepareStatement("insert into test (?,?)";
ps.setString(1,"a.exe");
ps.setBinaryStream(2,in,(int)f.length());
ps.executeUpdate();
f的長度一定要做從long到int的轉(zhuǎn)換,SUN的文檔中好幾版都沒有改過來.就這么簡單,當(dāng)然,不同的
數(shù)據(jù)庫存本身要設(shè)置它允許的最大長度,MYSQL默認(rèn)只能傳1M的文件,要修改參數(shù)原能存更大的文件.
如果要從數(shù)庫中取得文件:
PreparedStatement ps = conn.prepareStatement("select * from test where name=?");
ps.setString(1,"a.exe";
ResultSet rs = ps.executeQuery();
if(rs.next()){
InputStream in = rs.getBinaryStream("content";
}
得到in對象后,你可以進(jìn)行任何處理,寫向文件和寫向頁面只是out對象不同而已:
寫向文件:
DateOutputStream out = new DateOutputStream(new FileOutputStream("b.exe");
寫向頁面:
response.reset();
response.setContType("類型";
ServletOutputSreamt out = response.getOutputSream();
得到out對象后,就可以輸出了:
byte[] buf = new byte[1024];
int len = 0;
while((len = in.read(buf)) >0)
out.write(buf,0,len);
in.close();
out.close();
對于向頁面輸入,要設(shè)置什么樣的ContType,要看你想如何輸出,如果你想讓對方下載,就設(shè)為
"application/octet-stream",這樣即使是文本,圖象都會下載而不會在瀏覽器中打開.如果你要想
在瀏覽器中打開,就要設(shè)置相應(yīng)的類型,還要在容器的配置文件中設(shè)置支持這種文檔類型的輸出,但
對于很多格式的文件,到底要輸出什么類型,其實(shí)就是HTTP的MIME集,比如圖片:image/gif,當(dāng)然你如
果你的文件擴(kuò)展名(ext)不確定,你也不要用if(ext.equals("gif")......這樣來判斷,我教你一個
技巧,我之所以說是技巧,是我沒有在別的地方發(fā)現(xiàn)有人用這種方法,對我來說我是絕對不會把別人的
方法拿來說是我的技巧的:
構(gòu)造一個file類型的URL,我們知道URL目前JAVA可以支持HTTP,FTP,MAILTO,FILE,LDAP等,從FILE類型
的URL就可以得到它的MIME:
URL u = new URL("file://a.exe";
String mime = u.openConnection().getContentType();
這樣你就可以直接response.setContType(mime);而不用一個一個類型判斷了.
好了,大對象存儲就說到這兒,不同的數(shù)據(jù)仍然和些特殊的規(guī)定,不在此一一列舉了.