藍晶石

          BlogJava 首頁 新隨筆 聯系 聚合 管理
            10 Posts :: 0 Stories :: 5 Comments :: 0 Trackbacks

          2005年8月20日 #

          一. 什么是AJAX?

            這個名字代表了異步JavaScript+XMLHTTPRequest,并且意味著你可以在基于瀏覽器的JavaScript和服務器之間建立套接字通訊。其實AJAX并不是一種新技術,而是已經成功地用于現代瀏覽器中的若干成功技術的可能性組合。所有的AJAX應用程序實現了一種“豐富的”UI——這是通過JavaScript操作HTML文檔對象模型并且經由XMLHttpRequest實現的精確定位的數據檢索來實現的。典型的示例AJAX應用程序是Google Labs(http://labs.google.com)的Google Maps和Google Suggest。這些應用程序現場監視用戶輸入并且提供實時的頁面更新。最重要的是,在用戶通過地圖導航或輸入一個查找字符串的同時,這些事件不需要刷新頁面。

            事實上,支持這些令人感到驚訝的應用的技術已經出現一段時間了,盡管它們要求復雜的技能以及使用瀏覽器的技巧。一些專利產品就提供了相似的能力——如Macromedia Flash插件,Java Applets或.NET運行時——在達到實用上已經有一段時間了。把一種可與服務器通話的腳本組件引入到瀏覽器中的思想早在IE 5.0中就已經存在。Firefox和其它流行的瀏覽器也加入到瀏覽器大軍中并以一種內置對象形式支持XMLHTTPRequest。隨著跨平臺瀏覽器的出現,這些技術得到了認可并在2004年3月一家稱為Adaptive Path的公司中正式提出了AJAX。

            簡而言之,由于來自于Google的支持和安裝了一點可用的瀏覽器技術,加上為了一種"更好的用戶體驗",每個人都在把客戶端技術添加到Web應用程序上。

            二. AJAX與傳統應用程序的區別

            一個傳統Web應用程序模型實際上是一種基本的事件——用戶被迫提交表單以實現頁面交換。也就是說,表單提交和頁面傳送無法得到保證:還有更壞的情形——用戶需要再次點擊。這與AJAX截然不同-——數據跨過線路而不是完整的HTML頁面傳輸。這種數據交換是經由特定的瀏覽器對象:XMLHttpRequest實現的;再由適當的邏輯來處理每個數據請求的結果,頁面的特定區域而不是完整的頁面被更新。結果是更快的速度,更少的擁擠和更好的信息傳送控制。

            傳統型"click-refresh"Web應用程序強迫用戶中斷工作過程而等待頁面的重裝。通過引入AJAX技術,一個客戶端腳本能夠異步地與服務器通話,而用戶仍能保持輸入數據。除了對用戶透明之外,這樣的異步意味著服務器可以有更多時間來處理請求。

            傳統Web應用程序把所有的處理代理到服務器并且強迫服務器進行狀態管理。AJAX允許靈活劃分應用程序邏輯以及客戶和服務器之間的狀態管理。這就消除了一種"click-refresh"依賴性并且提供更好的服務器可伸縮性。當該狀態存儲在客戶端,你就不必跨越服務器來維持會話或保存/結束狀態-其使用期限是由客戶端來定義的。

            三. AJAX——分布式的MVC

            盡管AJAX應用程序依靠JavaScript來實現描述層,然而處理能力和知識庫仍然存在于服務器上。此時,AJAX應用程序大量的與J2EE服務器通訊——把數據輸入/輸出Web服務和servlets。具有基于AJAX的描述層的J2EE應用程序和標準J2EE應用程序之間的區別首先在于,MVC是通過線路分布的。通過使用AJAX,視圖是本地的,而模型和控制器是分布式的——這使得開發者能夠靈活地決定哪些部件會是基于客戶端的。具體地說,本地視圖通過巧妙地操作HTML DOM而生成圖形;控制器局部地處理用戶輸入并且根據開發者的判斷擴展到服務器的處理——經由HTTP請求(Web服務,XML/RPC或其它)實現;模型的遠程部分是根據客戶端需要而下載的以達到實時更新客戶端頁面;并且狀態是在客戶端收集的。

            在以后的AJAX文章中,我們將比較深入地討論這里的每一種組件并提供有關它們聯合在一起進行應用的示例。現在,先不多說,讓我們詳細地分析一個簡單的AJAX示例。

            四. 郵政區號校驗和查詢

            我們將創建一個包含三個INPUT字段(Zip,City和State)的HTML頁面。我們將保證,只要用戶輸入郵政區號的前三個數字,該頁面上的字段就會用第一個匹配的狀態值填充。一旦用戶輸入了所有五位郵政區號數,我們將立即決定和填充相應的城市。如果郵政區號無效(在服務器的數據庫沒有找到),那么我們將把郵政區號的邊界設置為紅色。這樣的可視化線索有助于用戶并且在現代瀏覽器中已經成為一種標準(作為一實例,當Firefox找到一個HTML頁面中的匹配關鍵字時,它會高亮與你在瀏覽器查找域輸入的內容一致的部分)。

            讓我們首先創建一個簡單的包含三個INPUT字段的HTML:zip,city和state。請注意,一旦一個字符輸入進郵政區號字段域中,即調用方法zipChanged()。JavaScript函數zipChanged()(見下)在當zip長度為3時調用函數updateState(),而在當zip長度為5時調用函數up-dateCity()。而updateCity()和updateState()把大部分的工作代理到另一個函數ask()。

          Zip:<input id="zipcode" type="text" maxlength="5" onKeyUp="zipChanged()"
          style="width:60"/>
          City: <input id="city" disabled maxlength="32" style="width:160"/>
          State:<input id="state" disabled maxlength="2" style="width:30"/>
          <script src="xmlhttp.js"></script>
          <script>
          var zipField = null;
          function zipChanged(){
          zipField = document.getElementById("zipcode")
          var zip = zipField.value;
          zip.length == 3?updateState(zip):zip.length == 5?updateCity(zip):"";
          }
          function updateState(zip) {
           var stateField = document.getElementById("state");
           ask("resolveZip.jsp?lookupType=state&zip="+zip, stateField, zipField);
          }
          function updateCity(zip) {
           var cityField = document.getElementById("city");
           ask("resolveZip.jsp? lookupType=city&zip="+zip, cityField, zipField);
          }
          </script>


              函數ask()與服務器進行通訊并分配一個回調函數來處理服務器的響應(見下列代碼)。后面,我們將分析具有雙重特點的resolveZip.jsp的內容-它根據zip字段中的字符數查找city或state信息。重要的是,ask()使用了具有異步特點的XmlHttpRequest,這樣填充state和city字段或著色zip字段邊界就可以不必減慢數據入口而得以實現。首先,我們調用request.open()-它用服務器打開套接字頻道,使用一個HTTP動詞(GET或POST)作為第一個參數并且以數據提供者的URL作為第二個參數。request.open()的最后一個參數被設置為true-它指示該請求的異步特性。注意,該請求還沒有被提交。隨著對request.send()的調用,開始提交-這可以為POST提供任何必要的有效載荷。在使用異步請求時,我們必須使用request.onreadystatechanged屬性來分配請求的回調函數。(如果請求是同步的話,我們應該能夠在調用request.send之后立即處理結果,但是我們也有可能阻斷用戶,直到該請求完成為止。)

          HTTPRequest = function () {
           var xmlhttp=null;
           try {
            xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
           } catch (_e) {
            try {
             xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
            } catch (_E) { }
           }
           if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
            try {
             xmlhttp = new XMLHttpRequest();
            } catch (e) {
             xmlhttp = false;
            }
           }
           return xmlhttp;
          }

          function ask(url, fieldToFill, lookupField) {
           var http = new HTTPRequest();
           http.open("GET", url, true);
           http.onreadystatechange = function (){ handleHttpResponse(http, fieldToFill,lookupField)};
           http.send(null);
          }

          function handleHttpResponse(http, fieldToFill, lookupField) {
           if (http.readyState == 4) {
            result = http.responseText;
            if ( -1 != result.search("null") ) {
             lookupField.style.borderColor = "red";
             fieldToFill.value = "";
            } else {
             lookupField.style.borderColor = "";
             fieldToFill.value = result;
            }
           }
          }

            為ask()所使用的HttpRequest()函數(見上)是一跨瀏覽器的XMLHTTPRequest的一個實例的構造器;稍后我們將分析它。到目前為止,請注意對于handleResponse()的調用是如何用一匿名函數包裝的-這個函數是function(){handleHttpResponse(http,fieldToFill, lookupField)}。

            該函數的代碼是動態創建的并且在每次我們給http.onreadstatechange屬性賦值時被編譯。結果,JavaScript創建一個指向上下文(所有的變量都可以存取正在結束的方法-ask())的指針。這樣以來,匿名函數和handleResponse()就能夠被保證充分存取所有的上下文宿主的變量,直至到匿名函數的參考被垃圾回收站收集為止。換句話說,無論何時我們的匿名函數被調用,它都能無縫地參考request,fieldToFill和lookupField變量,就象它們是全局的一樣。而且,每次ask()調用都將創建環境的一個獨立拷貝,并且此時這些變量中保存有該函數將結束時的值。

            現在,讓我們分析一下函數handleResponse()。既然它能夠在請求處理的不同狀態下激活,那么該函數將忽略所有的情形-除了該請求處理完成之外-這相應于request.readyState屬性等于4("Completed")。此時,該函數讀取服務器的響應文本。與它的名字所暗示的相反,XmlHttpRequest的輸入和輸出都不必限于XML格式。特別地,我們的resolveZip.jsp(見源碼中的列表1)返回普通文本。如果返回值為"unknown",那么該函數將假定郵政區號是無效的并且把查找字段(zip)邊界顏色置為紅色。否則,返回值被用于填充字段state或city,并且zip的邊界被賦予一種缺省顏色。

          XMLHttpRequest-傳輸對象

            讓我們返回到我們的XMLHTTPRequest的跨瀏覽器實現。最后一個列表包含一個HttpRequest()函數-它向上兼容于IE5.0和Mozilla 1.8/FireFox。為簡化起見,我們只創建一個微軟XMLHTTPRequest對象,而且如果創建失敗,我們假定它是Firefox/Mozilla。

            該函數的核心是XMLHTTPRequest-這是一個本機瀏覽器對象,它為包括HTTP協議的任何東西與服務器之間的通訊提供方便。它允許指定任何HTTP動詞,頭部和有效載荷,并且能夠以異步或同步方式工作。不需要下載也不需要安裝任何插件-盡管在IE的情形下,XMLHTTPRequest是一個集成到瀏覽器內部的ActiveX。因而,"Run ActiveX Control and Plugins"默認IE權限應該正好適合使用它。

            最重要的是,XMLHTTPRequest允許一個到服務器的RPC風格的編程查詢而不需要任何頁面刷新。它以一種可預測的,可控制的方式來實現此-提供了到HTTP協議的所有細節的完整存取-包括頭部和數據的任何定制格式。在以后的文章中,我們將向你展示其它一些業界協議-你可以在這些傳輸協議(如Web服務和XML-RPC)之上運行-它們極大地簡化大規模應用程序的開發和維護。

            五.服務器端邏輯

            最后,服務器端的resolveZip.jsp被從函數ask()中調用(見所附源碼中的列表1)。這個resolveZip.jsp在兩種由當前的郵政區號長度所區分的獨立的場所下被調用(見zipChanged()函數)。請求參數lookupType的值或者是state或者是city。為簡化起見,我們將假定,兩個文件state.properties和city.properties都位于服務器中C驅動器的根目錄下。resolveZip.jsp邏輯負責用適當的預裝載的文件返回查找值。

            我們的支持AJAX的頁面現在已經準備好了。

            六.遠程腳本技術-一種可選方法

            一些更舊的AJAX實現是基于所謂的遠程腳本技術。這種思想是,用戶的行為導致經由IFRAME對服務器進行查詢,而服務器用JavaScript作出響應,該腳本一旦到達客戶端立即被執行。這與XMLHttpRequest方法相比存在較大的區別,在后者情況下,服務器響應數據而客戶端解釋數據。其好處是這種解決方案支持更舊的瀏覽器。

            基于IFRAME示例的HTML部分(見所附源碼中的列表2)與我們在XMLHTTPRequest場合下所用的極相似,但是這次我們將引入另外一個IFRAME元素-controller:

          Zip:<input id="zipcode" type="text" maxlength="5" onKeyUp="zipChanged()"
          style="width:60" size="20"/>
          City: <input id="city" disabled maxlength="32" style="width:160" size="20"/>
          State:<input id="state" disabled maxlength="2" style="width:30" size="20"/>
          <iframe id="controller" style="visibility:hidden;width:0;height:0"></iframe>

            我們保持每次擊鍵都調用zipChanged()一次,但是這一次,從zipChanged()中被調用的函數ask()(見所附源碼中的列表3)負責設置IFRAME的src屬性,而不是調用一個XMLHTTPRequest:

          function ask(url, fieldToFill, lookupField){
           var controller = document.getElementById("controller");
           controller.src= url+"&field="+fieldToFill.id+"&zip="+lookupField.id;
          }

            服務器端邏輯由一個粗略的resolveZip.jsp(見所附源碼中的列表4)所描述。它與它的XMLHTTPRequest對應物相區別-它返回JavaScript語句,這些語句設置變量字段lookup和city的全局值,而且一旦它到達瀏覽器即從全局窗口的執行上下文中調用函數response()。

            函數response()是一修改版本的handleResponse()-這一函數可以免于處理未完成的請求(詳見本文所附源碼中的列表2)。

            七. 難題

            為簡化起見,讓我們"俯看"一下在我們的示例代碼中的一些重要的問題:

            1.事實-XMLHTTPRequest對象實例和回調函數調用在被使用以后并沒被破壞-在每次調用后這有可能導致內存泄漏。適當編寫的代碼應該破壞或重用對象池中的這些實例。而且,客戶端必須使用與服務器軟件相同的對象管理技術。

            2.在大多數情況下,錯誤往往得不到有效處理。例如,在方法ask()中對request.open()的調用可能引發一個異常,這是必須要捕獲和處理的,即使在瀏覽器中沒有設置JavaScript異常自動捕獲功能。而handleResponse()函數又是另外一個例子。它必須要為可能的服務器端和通訊錯誤而檢查headers和responseText值。在發生錯誤的情況下,它必須盡力恢復并/或者報告錯誤。正確開發的AJAX應用程序要盡可能避免"提交"松散的數據,因為往往存在線路斷開和其它低級通訊的問題-所以這些程序必須建立一個強壯的和自恢復的框架為此提供支持。

            3.當前服務器端框架提供相當多的功能-它們可以與一種自由刷新方法和諧相處。例如,讓我們考慮一個定制的在指定時間內的服務器端認證的問題。在這種情況下,我們必須攔截到XMLHTTPRequest調用的安全系統響應,顯示登錄屏幕,然后在用戶被認證后重新發出請求。

            所有的這些問題只是一些典型的用低級API工作的任何應用程序代碼,而且所有這些問題都能被解決。好消息是,解決這些問題所需要的技術十分相似于大多數Java開發技術,如Web服務,定制標簽和XML/XSLT。唯一的區別在于,現在這些技術以下列形式用于客戶端:

            ·Web服務-使用SOAP/REST/RPC等簡單通訊標準

            ·客戶端定制標簽-打包豐富的客戶端控件并集成AJAX功能

            ·數據操作-基于XML和基于XSLT技術

            八. 小結

            AJAX方法能夠向人們提供一種與桌面應用程序相同的豐富的互聯網體驗。但是,我們必須有選擇地使用AJAX技術,如當你仍在線購物時,你絕對不想讓你的信用卡通過后臺處理就悄悄地開始付款。AJAX會成為一種持續的動力嗎?我們當然希望這樣。在過去的五年時間內我們一直在努力開發AJAX應用程序并且能證明它是健全并且很有效的。然而,它要求一個開發者必須精通大量技術而不是在傳統的"click-refresh"Web應用程序中所使用的那些。

          posted @ 2005-12-29 17:10 kyanite 閱讀(219) | 評論 (0)編輯 收藏

          與任何技術一樣,使用Ajax在相當多的方面都可能范錯誤。我在這兒討論的問題目前都缺少解決方案,并將會隨著Ajax的成熟而解決或提高。隨著開發 Ajax應用經驗的不斷獲取,開發者社區中將會出現最好的實踐經驗與指導方針。

            1、XMLHttpRequest的有效性

            Ajax開發者面對的一個最大問題是當XMLHttpRequest不可用時如何反應。雖然大部分現代瀏覽器支持XMLHttpRequest,但還是有少量的用戶,他們的瀏覽器不能支持,或由于瀏覽器安全設置而阻止對XMLHttpRequest的使用。若你的Web應用發布于公司內部的 Intranet上,你很可能可以指定支持哪種瀏覽器,并可以確保XMLHttpRequest是可用的。若你在公共WEB上發布,則你必須意識到由于假定XMLHttpRequest是可用的,所有就阻止了老瀏覽器、手持設備瀏覽器等等用戶來使用你的系統。

            然而,你應該盡力保證應用系統“正常降級”使用,在系統中保留適用于不支持XMLHttpRequest的瀏覽器的功能。在購物車例子中,最好的方法是有一個Add to Cart按鈕,可以進行常規的提交處理,并刷新頁面來反映購物車狀態的變化。Ajax行衛可以在頁面被載入時通過JavaScript添加到頁面中,只在 XMLHttpRequest可用的情況下,為每個Add to Cart按鈕加上JavaScript處理函數。另一個方法是在用戶登錄時檢測XMLHttpRequest,再決定是提供Ajax版本還是常規基于 form提交的版本。

            2、可用性考慮

            圍繞著Ajax應用的大部分問題都是很普通的問題。例如,讓用戶知道他們的輸入已經被注冊并處理,是很重要的,因為在XMLHttpRequest處理過程中并不能提供通常的漏斗旋轉光標。一種方法是將“確認”按扭上的文本替換為“正在更新中…”,以避免用戶在等待響應時多次點擊按鈕。

            另一個問題是,用戶可能沒有注意到他們正在觀看的頁面已經被更新。可以通過使用各種視覺技巧來將用戶的眼光吸引到頁面的更新區域。還有一個問題是通過 Ajax更新頁面打斷了瀏覽器“退回前頁”按鈕的正常工作,地址欄中的URL不能反映頁面的全部狀態,并且不能使用書簽功能。參見Resource章節中列出的網站地址上的文章來了解更多Ajax應用關于可用性方面的問題。

            3、服務器負載

            使用Ajax界面代替傳統的基于form的界面可能戲劇性地增加傳遞到服務器的請求數量。例如,一個普通的Google搜索給服務器造成一次命中,并在用戶確認搜索表單時發生。然而,Google Suggest,將會試圖自動完成你的搜索詞,在用戶打字時將會往服務器發送多個請求。在開發一個Ajax應用時,要注意到你將會發送多少請求到用戶器端,以及服務器的負載指標。你可以通過在客戶端適當地緩存請求、與服務器響應來緩減負載壓力。你也應該在設計Ajax應用時盡量在客戶端處理更多的邏輯,而不用與服務器端通訊。

            4、處理異步

            一定要記住,沒有任何東西可以保證XMLHttpRequest將會按照它們被發送的順序來依次結束。實際上,你在設計系統時,腦子里應該始終假定它們不會按原來順序結束。在購物車例子中,使用了一個最后更新的時間戳來保證最新的數據不會被改寫。這個非常基本的方法可以在購物車場景中工作,但可能不能在其它情況下工作。在設計時刻就要考慮你該如何處理異步服務器響應。

            結論

            你現在應該對于Ajax的基本原則有了一個良好的了解,另外,你應該理解一些更高級的隨Ajax方法而來的設計問題。創建一個成功的Ajax應用需要一系列的方法—從JavaScript UI設計到服務器端架構—但是你現在應該已經具備了需要使用到的Ajax核心知識。

          posted @ 2005-12-29 16:01 kyanite 閱讀(172) | 評論 (0)編輯 收藏

          引言

          Java的堆是一個運行時數據區,類的實例(對象)從中分配空間。Java虛擬機(JVM)的堆中儲存著正在運行的應用程序所建立的所有對象,這些對象通過newnewarrayanewarraymultianewarray等指令建立,但是它們不需要程序代碼來顯式地釋放。一般來說,堆的是由垃圾回收 來負責的,盡管JVM規范并不要求特殊的垃圾回收技術,甚至根本就不需要垃圾回收,但是由于內存的有限性,JVM在實現的時候都有一個由垃圾回收所管理的堆。垃圾回收是一種動態存儲管理技術,它自動地釋放不再被程序引用的對象,按照特定的垃圾收集算法來實現資源自動回收的功能。

          垃圾收集的意義

          C++中,對象所占的內存在程序結束運行之前一直被占用,在明確釋放之前不能分配給其它對象;而在Java中,當沒有對象引用指向原先分配給某個對象的內存時,該內存便成為垃圾。JVM的一個系統級線程會自動釋放該內存塊。垃圾收集意味著程序不再需要的對象是"無用信息",這些信息將被丟棄。當一個對象不再被引用的時候,內存回收它占領的空間,以便空間被后來的新對象使用。事實上,除了釋放沒用的對象,垃圾收集也可以清除內存記錄碎片。由于創建對象和垃圾收集器釋放丟棄對象所占的內存空間,內存會出現碎片。碎片是分配給對象的內存塊之間的空閑內存洞。碎片整理將所占用的堆內存移到堆的一端,JVM將整理出的內存分配給新的對象。

          垃圾收集能自動釋放內存空間,減輕編程的負擔。這使Java 虛擬機具有一些優點。首先,它能使編程效率提高。在沒有垃圾收集機制的時候,可能要花許多時間來解決一個難懂的存儲器問題。在用Java語言編程的時候,靠垃圾收集機制可大大縮短時間。其次是它保護程序的完整性, 垃圾收集是Java語言安全性策略的一個重要部份。

          垃圾收集的一個潛在的缺點是它的開銷影響程序性能。Java虛擬機必須追蹤運行程序中有用的對象, 而且最終釋放沒用的對象。這一個過程需要花費處理器的時間。其次垃圾收集算法的不完備性,早先采用的某些垃圾收集算法就不能保證100%收集到所有的廢棄內存。當然隨著垃圾收集算法的不斷改進以及軟硬件運行效率的不斷提升,這些問題都可以迎刃而解。

          垃圾收集的算法分析

          Java語言規范沒有明確地說明JVM使用哪種垃圾回收算法,但是任何一種垃圾收集算法一般要做2件基本的事情:(1)發現無用信息對象;(2)回收被無用對象占用的內存空間,使該空間可被程序再次使用。

          大多數垃圾回收算法使用了根集(root set)這個概念;所謂根集就量正在執行的Java程序可以訪問的引用變量的集合(包括局部變量、參數、類變量),程序可以使用引用變量訪問對象的屬性和調用對象的方法。垃圾收集首選需要確定從根開始哪些是可達的和哪些是不可達的,從根集可達的對象都是活動對象,它們不能作為垃圾被回收,這也包括從根集間接可達的對象。而根集通過任意路徑不可達的對象符合垃圾收集的條件,應該被回收。下面介紹幾個常用的算法。

          1、  引用計數法(Reference Counting Collector)

          引用計數法是唯一沒有使用根集的垃圾回收的法,該算法使用引用計數器來區分存活對象和不再使用的對象。一般來說,堆中的每個對象對應一個引用計數器。當每一次創建一個對象并賦給一個變量時,引用計數器置為1。當對象被賦給任意變量時,引用計數器每次加1當對象出了作用域后(該對象丟棄不再使用),引用計數器減1,一旦引用計數器為0,對象就滿足了垃圾收集的條件。

          基于引用計數器的垃圾收集器運行較快,不會長時間中斷程序執行,適宜地必須 實時運行的程序。但引用計數器增加了程序執行的開銷,因為每次對象賦給新的變量,計數器加1,而每次現有對象出了作用域生,計數器減1

          2tracing算法(Tracing Collector)

          tracing算法是為了解決引用計數法的問題而提出,它使用了根集的概念。基于tracing算法的垃圾收集器從根集開始掃描,識別出哪些對象可達,哪些對象不可達,并用某種方式標記可達對象,例如對每個可達對象設置一個或多個位。在掃描識別過程中,基于tracing算法的垃圾收集也稱為標記和清除(mark-and-sweep)垃圾收集器.

          3compacting算法(Compacting Collector)

          為了解決堆碎片問題,基于tracing的垃圾回收吸收了Compacting算法的思想,在清除的過程中,算法將所有的對象移到堆的一端,堆的另一端就變成了一個相鄰的空閑內存區,收集器會對它移動的所有對象的所有引用進行更新,使得這些引用在新的位置能識別原來 的對象。在基于Compacting算法的收集器的實現中,一般增加句柄和句柄表。  

          4copying算法(Coping Collector)

          該算法的提出是為了克服句柄的開銷和解決堆碎片的垃圾回收。它開始時把堆分成 一個對象 面和多個空閑面, 程序從對象面為對象分配空間,當對象滿了,基于coping算法的垃圾 收集就從根集中掃描活動對象,并將每個 活動對象復制到空閑面(使得活動對象所占的內存之間沒有空閑洞),這樣空閑面變成了對象面,原來的對象面變成了空閑面,程序會在新的對象面中分配內存。

          一種典型的基于coping算法的垃圾回收是stop-and-copy算法,它將堆分成對象面和空閑區域面,在對象面與空閑區域面的切換過程中,程序暫停執行。

          5generation算法(Generational Collector)
            stop-and-copy垃圾收集器的一個缺陷是收集器必須復制所有的活動對象,這增加了程序等待時間,這是coping算法低效的原因。在程序設計中有這樣的規律:多數對象存在的時間比較短,少數的存在時間比較長。因此,generation算法將堆分成兩個或多個,每個子堆作為對象的一代(generation)。由于多數對象存在的時間比較短,隨著程序丟棄不使用的對象,垃圾收集器將從最年輕的子堆中收集這些對象。在分代式的垃圾收集器運行后,上次運行存活下來的對象移到下一最高代的子堆中,由于老一代的子堆不會經常被回收,因而節省了時間。

          6adaptive算法(Adaptive Collector)

          在特定的情況下,一些垃圾收集算法會優于其它算法。基于Adaptive算法的垃圾收集器就是監控當前堆的使用情況,并將選擇適當算法的垃圾收集器。

          透視Java垃圾回收

          1、命令行參數透視垃圾收集器的運行

          2、使用System.gc()可以不管JVM使用的是哪一種垃圾回收的算法,都可以請求Java的垃圾回收。在命令行中有一個參數-verbosegc可以查看Java使用的堆內存的情況,它的格式如下:

          java -verbosegc classfile

            可以看個例子:

          class TestGC
          {
           public static void main(String[] args)
           {
            new TestGC();
            System.gc();
            System.runFinalization();
           }
          }


            在這個例子中,一個新的對象被創建,由于它沒有使用,所以該對象迅速地變為可達,程序編譯后,執行命令: java -verbosegc TestGC 后結果為:

          [Full GC 168K->97K(1984K), 0.0253873 secs]

            機器的環境為,Windows 2000 + JDK1.3.1,箭頭前后的數據168K97K分別表示垃圾收集GC前后所有存活對象使用的內存容量,說明有168K-97K=71K的對象容量被回收,括號內的數據1984K為堆內存的總容量,收集所需要的時間是0.0253873秒(這個時間在每次執行的時候會有所不同)。

            2finalize方法透視垃圾收集器的運行

            在JVM垃圾收集器收集一個對象之前 ,一般要求程序調用適當的方法釋放資源,但在沒有明確釋放資源的情況下,Java提供了缺省機制來終止化該對象心釋放資源,這個方法就是finalize()。它的原型為:

          protected void finalize() throws Throwable

            在finalize()方法返回之后,對象消失,垃圾收集開始執行。原型中的throws Throwable表示它可以拋出任何類型的異常。

            之所以要使用finalize(),是由于有時需要采取與Java的普通方法不同的一種方法,通過分配內存來做一些具有C風格的事情。這主要可以通過"固有方法"來進行,它是從Java里調用非Java方法的一種方式。CC++是目前唯一獲得固有方法支持的語言。但由于它們能調用通過其他語言編寫的子程序,所以能夠有效地調用任何東西。在非Java代碼內部,也許能調用Cmalloc()系列函數,用它分配存儲空間。而且除非調用了free(),否則存儲空間不會得到釋放,從而造成內存"漏洞"的出現。當然,free()是一個CC++函數,所以我們需要在finalize()內部的一個固有方法中調用它。也就是說我們不能過多地使用finalize(),它并不是進行普通清除工作的理想場所。

            在普通的清除工作中,為清除一個對象,那個對象的用戶必須在希望進行清除的地點調用一個清除方法。這與C++"破壞器"的概念稍有抵觸。在C++中,所有對象都會破壞(清除)。或者換句話說,所有對象都"應該"破壞。若將C++對象創建成一個本地對象,比如在堆棧中創建(在Java中是不可能的),那么清除或破壞工作就會在"結束花括號"所代表的、創建這個對象的作用域的末尾進行。若對象是用new創建的(類似于Java),那么當程序員調用C++delete命令時(Java沒有這個命令),就會調用相應的破壞器。若程序員忘記了,那么永遠不會調用破壞器,我們最終得到的將是一個內存"漏洞",另外還包括對象的其他部分永遠不會得到清除。

            相反,Java不允許我們創建本地(局部)對象--無論如何都要使用new。但在Java中,沒有"delete"命令來釋放對象,因為垃圾收集器會幫助我們自動釋放存儲空間。所以如果站在比較簡化的立場,我們可以說正是由于存在垃圾收集機制,所以Java沒有破壞器。然而,隨著以后學習的深入,就會知道垃圾收集器的存在并不能完全消除對破壞器的需要,或者說不能消除對破壞器代表的那種機制的需要(而且絕對不能直接調用finalize(),所以應盡量避免用它)。若希望執行除釋放存儲空間之外的其他某種形式的清除工作,仍然必須調用Java中的一個方法。它等價于C++的破壞器,只是沒后者方便。

            下面這個例子向大家展示了垃圾收集所經歷的過程,并對前面的陳述進行了總結。

          class Chair {
           static boolean gcrun = false;
           static boolean f = false;
           static int created = 0;
           static int finalized = 0;
           int i;
           Chair() {
            i = ++created;
            if(created == 47)
             System.out.println("Created 47");
           }
           protected void finalize() {
            if(!gcrun) {
             gcrun = true;
             System.out.println("Beginning to finalize after " + created + " Chairs have been created");
            }
            if(i == 47) {
             System.out.println("Finalizing Chair #47, " +"Setting flag to stop Chair creation");
             f = true;
            }
            finalized++;
            if(finalized >= created)
             System.out.println("All " + finalized + " finalized");
           }
          }

          public class Garbage {
           public static void main(String[] args) {
            if(args.length == 0) {
             System.err.println("Usage: \n" + "java Garbage before\n or:\n" + "java Garbage after");
             return;
            }
            while(!Chair.f) {
             new Chair();
             new String("To take up space");
            }
            System.out.println("After all Chairs have been created:\n" + "total created = " + Chair.created +
          ", total finalized = " + Chair.finalized);
            if(args[0].equals("before")) {
              System.out.println("gc():");
              System.gc();
              System.out.println("runFinalization():");
              System.runFinalization();
            }
            System.out.println("bye!");
            if(args[0].equals("after"))
             System.runFinalizersOnExit(true);
           }
          }


            上面這個程序創建了許多Chair對象,而且在垃圾收集器開始運行后的某些時候,程序會停止創建Chair。由于垃圾收集器可能在任何時間運行,所以我們不能準確知道它在何時啟動。因此,程序用一個名為gcrun的標記來指出垃圾收集器是否已經開始運行。利用第二個標記fChair可告訴main()它應停止對象的生成。這兩個標記都是在finalize()內部設置的,它調用于垃圾收集期間。另兩個static變量--created以及finalized--分別用于跟蹤已創建的對象數量以及垃圾收集器已進行完收尾工作的對象數量。最后,每個Chair都有它自己的(非staticint i,所以能跟蹤了解它具體的編號是多少。編號為47Chair進行完收尾工作后,標記會設為true,最終結束Chair對象的創建過程。(關于這個例子的更具體的分析和說明請參看《Java編程思想》的第四章)

            關于垃圾收集的幾點補充

            經過上述的說明,可以發現垃圾回收有以下的幾個特點:

            (1)垃圾收集發生的不可預知性:由于實現了不同的垃圾收集算法和采用了不同的收集機制,所以它有可能是定時發生,有可能是當出現系統空閑CPU資源時發生,也有可能是和原始的垃圾收集一樣,等到內存消耗出現極限時發生,這與垃圾收集器的選擇和具體的設置都有關系。

            (2)垃圾收集的精確性:主要包括2 個方面:(a)垃圾收集器能夠精確標記活著的對象;(b)垃圾收集器能夠精確地定位對象之間的引用關系。前者是完全地回收所有廢棄對象的前提,否則就可能造成內存泄漏。而后者則是實現歸并和復制等算法的必要條件。所有不可達對象都能夠可靠地得到回收,所有對象都能夠重新分配,允許對象的復制和對象內存的縮并,這樣就有效地防止內存的支離破碎。(3)現在有許多種不同的垃圾收集器,每種有其算法且其表現各異,既有當垃圾收集開始時就停止應用程序的運行,又有當垃圾收集開始時也允許應用程序的線程運行,還有在同一時間垃圾收集多線程運行。

            (4)垃圾收集的實現和具體的JVM 以及JVM的內存模型有非常緊密的關系。不同的JVM 可能采用不同的垃圾收集,而JVM 的內存模型決定著該JVM可以采用哪些類型垃圾收集。現在,HotSpot 系列JVM中的內存系統都采用先進的面向對象的框架設計,這使得該系列JVM都可以采用最先進的垃圾收集。

            (5)隨著技術的發展,現代垃圾收集技術提供許多可選的垃圾收集器,而且在配置每種收集器的時候又可以設置不同的參數,這就使得根據不同的應用環境獲得最優的應用性能成為可能。

            針對以上特點,我們在使用的時候要注意:

            (1)不要試圖去假定垃圾收集發生的時間,這一切都是未知的。比如,方法中的一個臨時對象在方法調用完畢后就變成了無用對象,這個時候它的內存就可以被釋放。

            (2Java中提供了一些和垃圾收集打交道的類,而且提供了一種強行執行垃圾收集的方法--調用System.gc(),但這同樣是個不確定的方法。Java 中并不保證每次調用該方法就一定能夠啟動垃圾收集,它只不過會向JVM發出這樣一個申請,到底是否真正執行垃圾收集,一切都是個未知數。

            (3)挑選適合自己的垃圾收集器。一般來說,如果系統沒有特殊和苛刻的性能要求,可以采用JVM的缺省選項。否則可以考慮使用有針對性的垃圾收集器,比如增量收集器就比較適合實時性要求較高的系統之中。系統具有較高的配置,有比較多的閑置資源,可以考慮使用并行標記/清除收集器。

            (4)關鍵的也是難把握的問題是內存泄漏。良好的編程習慣和嚴謹的編程態度永遠是最重要的,不要讓自己的一個小錯誤導致內存出現大漏洞。

            (5)盡早釋放無用對象的引用。大多數程序員在使用臨時變量的時候,都是讓引用變量在退出活動域(scope)后,自動設置為null,暗示垃圾收集器來收集該對象,還必須注意該引用的對象是否被監聽,如果有,則要去掉監聽器,然后再賦空值。

            結束語

            一般來說,Java開發人員可以不重視JVM中堆內存的分配和垃圾處理收集,但是,充分理解Java的這一特性可以讓我們更有效地利用資源。同時要注意finalize()方法是Java的缺省機制,有時為確保對象資源的明確釋放,可以編寫自己的finalize方法。

          posted @ 2005-12-27 12:10 kyanite 閱讀(238) | 評論 (0)編輯 收藏

                近日,使用struts 1.1,發現討厭的中文亂碼問題,在form的傳送過程和入庫時候出現。就我在網絡上找的方法羅列如下:
          (Tomcat 5.0.28+struts 1.1+hibernate 2.1+sqlserver2k)
          1.直接轉編碼public static String isoToGB(String src){   
          String strRet=null;   
          try{    
            strRet = new String(src.getBytes("ISO_8859_1"),"GB2312");  
            }catch(Exception e)    {         
          }    return strRet;
          }通過一個函數轉編碼,我沒有成功,不知為何!

          2.過濾filter設置法

          package yourbean;

          import javax.servlet.*;
          import javax.servlet.http.*;
          import java.io.*;
          import java.util.*;

          public class servfilter extends HttpServlet implements Filter {  private FilterConfig filterConfig;  //Handle the passed-in FilterConfig  public void init(FilterConfig filterConfig) {    this.filterConfig = filterConfig;  }  //Process the request/response pair  public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) {    try {      request.setCharacterEncoding("GB2312");       ((HttpServletResponse)response).setHeader("Cache-control","no-cache");      response.setHeader("Pragma","No-cache"); response.setHeader("Cache-Control","no-cache"); response.setHeader("Expires","0");       ((HttpServletResponse)response).setHeader("Pragram","no-cache");      filterChain.doFilter(request, response);    }    catch(ServletException sx) {      filterConfig.getServletContext().log(sx.getMessage());    }    catch(IOException iox) {      filterConfig.getServletContext().log(iox.getMessage());    }  }  //Clean up resources  public void destroy() {  }}下面是一個web.xml文件你用jbuilder寫上面的bean的時候會生成一個<?xml version="1.0" encoding="ISO-8859-1"?>

          <!DOCTYPE web-app    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"    "http://java.sun.com/dtd/web-app_2_3.dtd">

          <web-app>  <display-name>Welcome to Tomcat</display-name>  <description>     Welcome to Tomcat  </description>  <filter>    <filter-name>servfilter</filter-name>    <filter-class>yourbean.servfilter</filter-class>  </filter>  <filter-mapping>    <filter-name>servfilter</filter-name>    <url-pattern>/*</url-pattern>  </filter-mapping></web-app>把上面的servfilter編譯放在你的web-inf/classes/yourbean/下web.xml放在web-inf/下和classes在一個目錄下在每個jsp頁面上加上<%@page contentType="text/html;charset=GBK"%>

          也不是很方便,而且在tomcat也沒有成功,繼續郁悶!

          3.我現在使用方法,推薦!!

          寫一個myActionServlet來并覆蓋ActionServlet中的process()方法。

            protected void process(HttpServletRequest request, HttpServletResponse response) throws java.io.IOException, javax.servlet.ServletException {    /**@todo Override this org.apache.struts.action.ActionServlet method*/    request.setCharacterEncoding("GB2312");//就加著一行一切都解決了    super.process(request, response);  }

          當然別忘了改一下web.xml里面的配置  <servlet>    <servlet-name>action</servlet-name>    <servlet-class>strutsdemo.myActionServlet</servlet-class>    <init-param>      <param-name>debug</param-name>      <param-value>2</param-value>    </init-param>    <init-param>      <param-name>config</param-name>      <param-value>/WEB-INF/struts-config.xml</param-value>    </init-param>    <load-on-startup>2</load-on-startup>  </servlet>

          改一下servlet-class標簽中的內容就可以!

          真的可以,一勞用yi!

          具體編碼的理論就不說了,google上已經夠多了。

          另外,如果不用struts的話,hibernate也可能碰到中文亂碼問題,只要在hibernate.cfg.xml配置中如下:

          <property name="hibernate.connection.url">   jdbc:microsoft:sqlserver://Localhost:1433;SelectMethod=cursor;characterEncoding=GBK;DatabaseName=myDatabase.  </property>

          characterEncoding=GBK!就可以了。
          posted @ 2005-08-20 13:52 kyanite 閱讀(685) | 評論 (0)編輯 收藏

          主站蜘蛛池模板: 新巴尔虎右旗| 双柏县| 吴江市| 蕲春县| 镇远县| 启东市| 汤阴县| 神池县| 平谷区| 武隆县| 巨鹿县| 崇阳县| 郧西县| 泾阳县| 沙湾县| 和顺县| 霍林郭勒市| 富川| 阜新| 白水县| 揭西县| 普宁市| 霍林郭勒市| 辉南县| 博野县| 荔波县| 吉安县| 乌兰浩特市| 丹凤县| 岳池县| 濉溪县| 固安县| 龙川县| 台东市| 舞钢市| 丹寨县| 黎平县| 略阳县| 曲沃县| 广丰县| 尤溪县|