開發(fā)過程需要注意的問題有下面這些:
公司主營業(yè)務(wù):成都網(wǎng)站建設(shè)、做網(wǎng)站、移動網(wǎng)站開發(fā)等業(yè)務(wù)。幫助企業(yè)客戶真正實現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競爭能力。創(chuàng)新互聯(lián)公司是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊。公司秉承以“開放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對我們的高要求,感謝他們從不同領(lǐng)域給我們帶來的挑戰(zhàn),讓我們激情的團(tuán)隊有機(jī)會用頭腦與智慧不斷的給客戶帶來驚喜。創(chuàng)新互聯(lián)公司推出深圳免費做網(wǎng)站回饋大家。
1. 服務(wù)器端必須提供輸入數(shù)據(jù)正確性的驗證,客戶端的Javascript驗證可以沒有。這是基于安全性的考慮,因為Javascript是很容易被繞過的,增加客戶端驗證只是為了減少服務(wù)器壓力、界面更加容易使用。
2. 適度使用Session,盡量不要在Session里放很大的集合對象,以免內(nèi)存消耗過大,因為很多用戶訪問的時候會產(chǎn)生很多的Session。參數(shù)傳遞應(yīng)該盡量通過Request。熟悉HTTP有助于更好的理解Session、Cookie、Request等的機(jī)制。
3. 在帶有分頁的查詢界面,盡量不要使用POST方法來傳遞參數(shù),POST傳遞的參數(shù)在地址欄里是看不到的,刷新后會有重新提交表單的提示。使用GET方法傳遞參數(shù)要注意URL的長度不能超過1K。
4. 分層應(yīng)該清晰,一般目前我們分為View(Jsp或FreeMarker加上Action)、Bean(Service)、DAO這么三層,即顯示層、業(yè)務(wù)層、數(shù)據(jù)層。記錄集ResultSet這種只能出現(xiàn)在DAO層中的對象不能出現(xiàn)在Bean(Service)層中,同樣HttpServletRequest這種只能出現(xiàn)在View層的對象也不應(yīng)出現(xiàn)在Bean(Service)層中。這并不是絕對的。
5. 盡量使用簡單SQL,避免兩表以及多表聯(lián)查。多表聯(lián)查會導(dǎo)致數(shù)據(jù)庫壓力大幅增加,而且不利于在內(nèi)存中對部分記錄進(jìn)行緩存,代碼的重用性也難以提高。
6. 避免在循環(huán)里執(zhí)行findXXById這樣的方法,不如執(zhí)行一個findXXByIds這樣的方法一次性把記錄取到Map里。大部分有實際對象對應(yīng)的表應(yīng)該提供這樣一個方法。
7. 如果使用最原始的jdbc編程的話需要注意資源的正確釋放,在循環(huán)里new出來的Statement或者ResultSet就要在循環(huán)里關(guān)閉。
8. 在編寫SQL進(jìn)行查詢的時候,需要能夠判斷這個SQL是否已經(jīng)使用了索引,避免全表掃描,必要的時候增加索引。
9. 在寫一個方法前,首先查看有沒有相同功能或者很類似功能的方法已經(jīng)有了,尤其是工具類方法,往往已經(jīng)寫過了,避免重復(fù)代碼的產(chǎn)生,發(fā)現(xiàn)重復(fù)代碼及時進(jìn)行處理。如果一段代碼被重復(fù)使用兩遍或以上,那么可以考慮專門寫個方法來放這段代碼,同樣多次使用的常量也應(yīng)該專門定義出來。
10. 在一個方法里并不一定只能有一個return,如果已經(jīng)有結(jié)果了盡早return,沒必要增加嵌套的層次,那樣會導(dǎo)致代碼可讀性不佳,但也不能return太多,代碼看起來比較舒服就可以了。
11. 不要滿足于能夠熟練的編寫DAO和Bean(Service)的代碼,相比較而言,后臺如果在成熟框架的支持下,編碼是沒有太大難度的,也不值得沾沾自喜,因為這是對Java研發(fā)工程師基本的要求。適當(dāng)?shù)呐囵B(yǎng)一下前臺的編碼能力,學(xué)會使用Dreamweaver。不要輕視界面,也別認(rèn)為這是界面設(shè)計的事情,界面對用戶來說就是軟件,學(xué)會編寫CSS和調(diào)整界面對你沒有壞處。
12. 非常明確Java和javascript作用的范圍,明確它們能做的事情。
13. 一般很奇怪的現(xiàn)象都是由一些低級錯誤引起的,如果你查了一段時間也沒有結(jié)果,那么讓別人來查吧。
14. 不要用可能被修改的字段來做主鍵,那樣會讓相關(guān)記錄的更新成為一個大麻煩。
15. 如果被迫使用Hibernate和jdbc混合操作數(shù)據(jù)庫的話,不要用Hibernate來做復(fù)雜查詢和統(tǒng)計。Hibernate用的不好的話,帶來的便利是非常有限的。
16. 數(shù)據(jù)庫中經(jīng)常被讀取,但是很少修改的話,應(yīng)該把這樣的數(shù)據(jù)讀到內(nèi)存中用OSCache之類的緩存起來,然后定期或者觸發(fā)的去更新,有助于減少讀數(shù)據(jù)庫次數(shù),提升性能。
17. 編碼的時候應(yīng)該注意部署環(huán)境帶來的影響,這種影響包括操作系統(tǒng)不同帶來路徑的差異;應(yīng)用服務(wù)器和數(shù)據(jù)庫服務(wù)器之間時間的差異;外網(wǎng)可能部署在多臺服務(wù)器上,放到Session里的對象因為需要復(fù)制所以要實現(xiàn)java.io.Serializable接口等。
18. 盡量不要在jsp上編寫太多代碼,保持jsp的整潔很重要,用Dreamweaver打開不至于一塌糊涂,根本看不出來這是個什么界面。
19. 目前我們的項目一般都使用Spring來管理數(shù)據(jù)庫事務(wù),而且一般都配置在Bean(Service)即業(yè)務(wù)層這一層,應(yīng)該注意要保持事務(wù)的完整性,不要把一些應(yīng)該放在一起的操作分散在Action這一層。相關(guān)的更新操作可以認(rèn)為是一個事務(wù),比如:增加一個家長,同時更新學(xué)生是否有家長的字段。
20. 在Spring的配置中,對于有些需要保持獨立事務(wù)的方法操作,比如生成主鍵等,應(yīng)該聲明該方法為獨立事務(wù)ROPAGATION_REQUIRES_NEW。Bean(Service)里如果拋出checked exception,事務(wù)默認(rèn)是不會回滾的,需要加以聲明,比如propkey="*"PROPAGATION_REQUIRED,-PassportException/prop。
21. 在一個Bean(Service)中引用其他Bean(Service)的時候盡量引用Bean(Service),而不是DAO。因為其他的Bean(Service)往往封裝DAO的操作后,又做了進(jìn)一步的完善,比如增加校驗等,所以應(yīng)該重用這些方法,而不必要去引用DAO的方法來重寫這些操作。
22. 至少在Bean(Service)的接口定義上增加注釋,方便他人引用你寫的方法。
23. 好好利用集合框架里的Map、List、Set。尤其是HashMap、ArrayList、HashSet用的最多,這些類是多條數(shù)據(jù)操作的基礎(chǔ),它們都不是線程安全的。
24. 現(xiàn)在跑的快的頁面,隨著數(shù)據(jù)量的增加,可能會變的很慢,所以應(yīng)該意識到頁面可能變慢的原因,而不是現(xiàn)在看起來很快。影響速度的大部分原因是對數(shù)據(jù)庫的壓力太大了,在java代碼執(zhí)行上花費很多時間的情況是不常見的。
25. 避免不必要的跳轉(zhuǎn),如果頁面執(zhí)行的足夠快,那么中間的載入進(jìn)度提示頁是不必要的,那樣會讓用戶覺得閃爍。
26. 注意頁面的文件大小,并不是每個用戶的帶寬都是非常理想的,文件小一點,速度快一點,總是感覺更好一點。
27. 不要去修改用戶的瀏覽器,比如隱藏他們的地址欄、菜單、右鍵菜單等,這可能會引起部分用戶的反感。盡量不要使用彈出窗口,可能會被攔截。
28. 網(wǎng)站的權(quán)限控制至少應(yīng)該保證有訪問權(quán)限的用戶才能訪問頁面,通過隱藏鏈接之類的方法是很不安全的,用戶看不見了并不表示安全了。權(quán)限的控制盡量使用框架里的攔截器這樣的機(jī)制,而不是把權(quán)限控制代碼寫的到處都是。過濾器不宜過多的使用,不僅因為過濾器的作用范圍很難控制,而且容易引起頁面執(zhí)行效果的混亂,錯誤不易排查。
29. 頁面里大部分情況使用相對路徑,保持Action路徑層次和頁面一致,這樣應(yīng)用就可以發(fā)布在各種目錄下。
用記事本寫完代碼后運(yùn)行方法如下:
1、用瀏覽器打開用記事本編寫的代碼
新建“文本文檔”后,鼠標(biāo)右鍵點擊該文本文檔,在菜單欄的“打開方式”選擇“用記事本打開”,也可以設(shè)置默認(rèn)打開方式為“記事本”;用記事本打開文本文檔后,直接在該文檔內(nèi)根據(jù)自己的需要輸入想要編輯的網(wǎng)頁代碼。
2、記事本寫java代碼怎么運(yùn)行
首先,需要安裝jdk并配置環(huán)境變量。然后,在命令行中,用javac命令編譯用記事本編寫的代碼。下一步,在命令行中,用java命令執(zhí)行編譯后的結(jié)果。
代碼是什么
代碼是程序員用開發(fā)工具所支持的語言寫出來的源文件,是一組由字符、符號或信號碼元以離散形式表示信息的明確的規(guī)則體系。代碼設(shè)計的原則包括唯一確定性、標(biāo)準(zhǔn)化和通用性、可擴(kuò)充性與穩(wěn)定性、便于識別與記憶、力求短小與格式統(tǒng)一以及容易修改等。
計算機(jī)源代碼最終目的是將人類可讀文本翻譯成為計算機(jī)可執(zhí)行的二進(jìn)制指令,這種過程叫編譯,它由通過編譯器完成。源代碼就是用匯編語言和高級語言寫出來的地代碼。目標(biāo)代碼是指源代碼經(jīng)過編譯程序產(chǎn)生的能被 cpu直接識別二進(jìn)制代碼。
可執(zhí)行代碼就是將目標(biāo)代碼連接后形成的可執(zhí)行文件,當(dāng)然也是二進(jìn)制的。
可供程序利用的資源(內(nèi)存、CPU時間、網(wǎng)絡(luò)帶寬等)是有限的,優(yōu)化的目的就是讓程序用盡可能少的資源完成預(yù)定的任務(wù)。優(yōu)化通常包含兩方面的內(nèi)容:減小代碼的體積,提高代碼的運(yùn)行效率。本文討論的主要是如何提高代碼的效率。
在Java程序中,性能問題的大部分原因并不在于Java語言,而是在于程序本身。養(yǎng)成好的代碼編寫習(xí)慣非常重要,比如正確地、巧妙地運(yùn)用java.lang.String類和java.util.Vector類,它能夠顯著地提高程序的性能。下面我們就來具體地分析一下這方面的問題。
1、 盡量指定類的final修飾符帶有final修飾符的類是不可派生的。在Java核心API中,有許多應(yīng)用final的例子,例如java.lang.String。為String類指定final防止了人們覆蓋length()方法。另外,如果指定一個類為final,則該類所有的方法都是final。Java編譯器會尋找機(jī)會內(nèi)聯(lián)(inline)所有的final方法(這和具體的編譯器實現(xiàn)有關(guān))。此舉能夠使性能平均提高50%
。
2、 盡量重用對象。特別是String 對象的使用中,出現(xiàn)字符串連接情況時應(yīng)用StringBuffer 代替。由于系統(tǒng)不僅要花時間生成對象,以后可能還需花時間對這些對象進(jìn)行垃圾回收和處理。因此,生成過多的對象將會給程序的性能帶來很大的影響。
3、 盡量使用局部變量,調(diào)用方法時傳遞的參數(shù)以及在調(diào)用中創(chuàng)建的臨時變量都保存在棧(Stack)中,速度較快。其他變量,如靜態(tài)變量、實例變量等,都在堆(Heap)中創(chuàng)建,速度較慢。另外,依賴于具體的編譯器/JVM,局部變量還可能得到進(jìn)一步優(yōu)化。請參見《盡可能使用堆棧變量》。
4、 不要重復(fù)初始化變量 默認(rèn)情況下,調(diào)用類的構(gòu)造函數(shù)時,
Java會把變量初始化成確定的值:所有的對象被設(shè)置成null,整數(shù)變量(byte、short、int、long)設(shè)置成0,float和double變量設(shè)置成0.0,邏輯值設(shè)置成false。當(dāng)一個類從另一個類派生時,這一點尤其應(yīng)該注意,因為用new關(guān)鍵詞創(chuàng)建一個對象時,構(gòu)造函數(shù)鏈中的所有構(gòu)造函數(shù)都會被自動調(diào)用。
5、 在JAVA + ORACLE 的應(yīng)用系統(tǒng)開發(fā)中,java中內(nèi)嵌的SQL語句盡量使用大寫的形式,以減輕ORACLE解析器的解析負(fù)擔(dān)。
6、 Java 編程過程中,進(jìn)行數(shù)據(jù)庫連接、I/O流操作時務(wù)必小心,在使用完畢后,即使關(guān)閉以釋放資源。因為對這些大對象的操作會造成系統(tǒng)大的開銷,稍有不慎,會導(dǎo)致嚴(yán)重的后果。
7、 由于JVM的有其自身的GC機(jī)制,不需要程序開發(fā)者的過多考慮,從一定程度上減輕了開發(fā)者負(fù)擔(dān),但同時也遺漏了隱患,過分的創(chuàng)建對象會消耗系統(tǒng)的大量內(nèi)存,嚴(yán)重時會導(dǎo)致內(nèi)存泄露,因此,保證過期對象的及時回收具有重要意義。JVM回收垃圾的條件是:對象不在被引用;然而,JVM的GC并非十分的機(jī)智,即使對象滿足了垃圾回收的條件也不一定會被立即回收。所以,建議我們在對象使用完畢,應(yīng)手動置成null。
8、 在使用同步機(jī)制時,應(yīng)盡量使用方法同步代替代碼塊同步。
9、 盡量減少對變量的重復(fù)計算
例如:for(int i = 0;i list.size; i ++) {
…
}
應(yīng)替換為:
for(int i = 0,int len = list.size();i len; i ++) {
…
}
10、盡量采用lazy loading 的策略,即在需要的時候才開始創(chuàng)建。
例如: String str = “aaa”;
if(i == 1) {
list.add(str);
}
應(yīng)替換為:
if(i == 1) {
String str = “aaa”;
list.add(str);
}
11、慎用異常
異常對性能不利。拋出異常首先要創(chuàng)建一個新的對象。Throwable接口的構(gòu)造函數(shù)調(diào)用名為fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法檢查堆棧,收集調(diào)用跟蹤信息。只要有異常被拋出,VM就必須調(diào)整調(diào)用堆棧,因為在處理過程中創(chuàng)建了一個新的對象。異常只能用于錯誤處理,不應(yīng)該用來控制程序流程。
12、不要在循環(huán)中使用:
Try {
} catch() {
}
應(yīng)把其放置在最外層。
13、StringBuffer 的使用:
StringBuffer表示了可變的、可寫的字符串。
有三個構(gòu)造方法 :
StringBuffer (); //默認(rèn)分配16個字符的空間
StringBuffer (int size); //分配size個字符的空間
StringBuffer (String str); //分配16個字符+str.length()個字符空間
你可以通過StringBuffer的構(gòu)造函數(shù)來設(shè)定它的初始化容量,這樣可以明顯地提升性能。這里提到的構(gòu)造函數(shù)是StringBuffer(int
length),length參數(shù)表示當(dāng)前的StringBuffer能保持的字符數(shù)量。你也可以使用ensureCapacity(int
minimumcapacity)方法在StringBuffer對象創(chuàng)建之后設(shè)置它的容量。首先我們看看StringBuffer的缺省行為,然后再找出一條更好的提升性能的途徑。
StringBuffer在內(nèi)部維護(hù)一個字符數(shù)組,當(dāng)你使用缺省的構(gòu)造函數(shù)來創(chuàng)建StringBuffer對象的時候,因為沒有設(shè)置初始化字符長度,StringBuffer的容量被初始化為16個字符,也就是說缺省容量就是16個字符。當(dāng)StringBuffer達(dá)到最大容量的時候,它會將自身容量增加到當(dāng)前的2倍再加2,也就是(2*舊值+2)。如果你使用缺省值,初始化之后接著往里面追加字符,在你追加到第16個字符的時候它會將容量增加到34(2*16+2),當(dāng)追加到34個字符的時候就會將容量增加到70(2*34+2)。無論何事只要StringBuffer到達(dá)它的最大容量它就不得不創(chuàng)建一個新的字符數(shù)組然后重新將舊字符和新字符都拷貝一遍――這也太昂貴了點。所以總是給StringBuffer設(shè)置一個合理的初始化容量值是錯不了的,這樣會帶來立竿見影的性能增益。
StringBuffer初始化過程的調(diào)整的作用由此可見一斑。所以,使用一個合適的容量值來初始化StringBuffer永遠(yuǎn)都是一個最佳的建議。
14、合理的使用Java類 java.util.Vector。
簡單地說,一個Vector就是一個java.lang.Object實例的數(shù)組。Vector與數(shù)組相似,它的元素可以通過整數(shù)形式的索引訪問。但是,Vector類型的對象在創(chuàng)建之后,對象的大小能夠根據(jù)元素的增加或者刪除而擴(kuò)展、縮小。請考慮下面這個向Vector加入元素的例子:
Object obj = new Object();
Vector v = new Vector(100000);
for(int I=0;
I100000; I++) { v.add(0,obj); }
除非有絕對充足的理由要求每次都把新元素插入到Vector的前面,否則上面的代碼對性能不利。在默認(rèn)構(gòu)造函數(shù)中,Vector的初始存儲能力是10個元素,如果新元素加入時存儲能力不足,則以后存儲能力每次加倍。Vector類就象StringBuffer類一樣,每次擴(kuò)展存儲能力時,所有現(xiàn)有的元素都要復(fù)制到新的存儲空間之中。下面的代碼片段要比前面的例子快幾個數(shù)量級:
Object obj = new Object();
Vector v = new Vector(100000);
for(int I=0; I100000; I++) { v.add(obj); }
同樣的規(guī)則也適用于Vector類的remove()方法。由于Vector中各個元素之間不能含有“空隙”,刪除除最后一個元素之外的任意其他元素都導(dǎo)致被刪除元素之后的元素向前移動。也就是說,從Vector刪除最后一個元素要比刪除第一個元素“開銷”低好幾倍。
假設(shè)要從前面的Vector刪除所有元素,我們可以使用這種代碼:
for(int I=0; I100000; I++)
{
v.remove(0);
}
但是,與下面的代碼相比,前面的代碼要慢幾個數(shù)量級:
for(int I=0; I100000; I++)
{
v.remove(v.size()-1);
}
從Vector類型的對象v刪除所有元素的最好方法是:
v.removeAllElements();
假設(shè)Vector類型的對象v包含字符串“Hello”??紤]下面的代碼,它要從這個Vector中刪除“Hello”字符串:
String s = "Hello";
int i = v.indexOf(s);
if(I != -1) v.remove(s);
這些代碼看起來沒什么錯誤,但它同樣對性能不利。在這段代碼中,indexOf()方法對v進(jìn)行順序搜索尋找字符串“Hello”,remove(s)方法也要進(jìn)行同樣的順序搜索。改進(jìn)之后的版本是:
String s = "Hello";
int i = v.indexOf(s);
if(I != -1) v.remove(i);
這個版本中我們直接在remove()方法中給出待刪除元素的精確索引位置,從而避免了第二次搜索。一個更好的版本是:
String s = "Hello"; v.remove(s);
最后,我們再來看一個有關(guān)Vector類的代碼片段:
for(int I=0; I++;I v.length)
如果v包含100,000個元素,這個代碼片段將調(diào)用v.size()方法100,000次。雖然size方法是一個簡單的方法,但它仍舊需要一次方法調(diào)用的開銷,至少JVM需要為它配置以及清除堆棧環(huán)境。在這里,for循環(huán)內(nèi)部的代碼不會以任何方式修改Vector類型對象v的大小,因此上面的代碼最好改寫成下面這種形式:
int size = v.size(); for(int I=0; I++;Isize)
雖然這是一個簡單的改動,但它仍舊贏得了性能。畢竟,每一個CPU周期都是寶貴的。
15、當(dāng)復(fù)制大量數(shù)據(jù)時,使用System.arraycopy()命令。
16、代碼重構(gòu):增強(qiáng)代碼的可讀性。
例如:
public class ShopCart {
private List carts ;
…
public void add (Object item) {
if(carts == null) {
carts = new ArrayList();
}
crts.add(item);
}
public void remove(Object item) {
if(carts. contains(item)) {
carts.remove(item);
}
}
public List getCarts() {
//返回只讀列表
return Collections.unmodifiableList(carts);
}
//不推薦這種方式
//this.getCarts().add(item);
}
17、不用new關(guān)鍵詞創(chuàng)建類的實例
用new關(guān)鍵詞創(chuàng)建類的實例時,構(gòu)造函數(shù)鏈中的所有構(gòu)造函數(shù)都會被自動調(diào)用。但如果一個對象實現(xiàn)了Cloneable接口,我們可以調(diào)用它的clone()方法。clone()方法不會調(diào)用任何類構(gòu)造函數(shù)。
在使用設(shè)計模式(Design Pattern)的場合,如果用Factory模式創(chuàng)建對象,則改用clone()方法創(chuàng)建新的對象實例非常簡單。例如,下面是Factory模式的一個典型實現(xiàn):
public static Credit getNewCredit() {
return new Credit();
}
改進(jìn)后的代碼使用clone()方法,如下所示:
private static Credit BaseCredit = new Credit();
public static Credit getNewCredit() {
return (Credit) BaseCredit.clone();
}
上面的思路對于數(shù)組處理同樣很有用。
18、乘法和除法
考慮下面的代碼:
for (val = 0; val 100000; val +=5) {
alterX = val * 8; myResult = val * 2;
}
用移位操作替代乘法操作可以極大地提高性能。下面是修改后的代碼:
for (val = 0; val 100000; val += 5) {
alterX = val 3; myResult = val 1;
}
修改后的代碼不再做乘以8的操作,而是改用等價的左移3位操作,每左移1位相當(dāng)于乘以2。相應(yīng)地,右移1位操作相當(dāng)于除以2。值得一提的是,雖然移位操作速度快,但可能使代碼比較難于理解,所以最好加上一些注釋。
19、在JSP頁面中關(guān)閉無用的會話。
一個常見的誤解是以為session在有客戶端訪問時就被創(chuàng)建,然而事實是直到某server端程序調(diào)用HttpServletRequest.getSession(true)這樣的語句時才被創(chuàng)建,注意如果JSP沒有顯示的使用 %@pagesession="false"% 關(guān)閉session,則JSP文件在編譯成Servlet時將會自動加上這樣一條語句HttpSession
session = HttpServletRequest.getSession(true);這也是JSP中隱含的session對象的來歷。由于session會消耗內(nèi)存資源,因此,如果不打算使用session,應(yīng)該在所有的JSP中關(guān)閉它。
對于那些無需跟蹤會話狀態(tài)的頁面,關(guān)閉自動創(chuàng)建的會話可以節(jié)省一些資源。使用如下page指令:%@ page session="false"%
20、JDBC與I/O
如果應(yīng)用程序需要訪問一個規(guī)模很大的數(shù)據(jù)集,則應(yīng)當(dāng)考慮使用塊提取方式。默認(rèn)情況下,JDBC每次提取32行數(shù)據(jù)。舉例來說,假設(shè)我們要遍歷一個5000行的記錄集,JDBC必須調(diào)用數(shù)據(jù)庫157次才能提取到全部數(shù)據(jù)。如果把塊大小改成512,則調(diào)用數(shù)據(jù)庫的次數(shù)將減少到10次。
[p][/p]21、Servlet與內(nèi)存使用
許多開發(fā)者隨意地把大量信息保存到用戶會話之中。一些時候,保存在會話中的對象沒有及時地被垃圾回收機(jī)制回收。從性能上看,典型的癥狀是用戶感到系統(tǒng)周期性地變慢,卻又不能把原因歸于任何一個具體的組件。如果監(jiān)視JVM的堆空間,它的表現(xiàn)是內(nèi)存占用不正常地大起大落。
解決這類內(nèi)存問題主要有二種辦法。第一種辦法是,在所有作用范圍為會話的Bean中實現(xiàn)HttpSessionBindingListener接口。這樣,只要實現(xiàn)valueUnbound()方法,就可以顯式地釋放Bean使用的資源。另外一種辦法就是盡快地把會話作廢。大多數(shù)應(yīng)用服務(wù)器都有設(shè)置會話作廢間隔時間的選項。另外,也可以用編程的方式調(diào)用會話的setMaxInactiveInterval()方法,該方法用來設(shè)定在作廢會話之前,Servlet容器允許的客戶請求的最大間隔時間,以秒計。
22、使用緩沖標(biāo)記
一些應(yīng)用服務(wù)器加入了面向JSP的緩沖標(biāo)記功能。例如,BEA的WebLogic Server從6.0版本開始支持這個功能,Open
Symphony工程也同樣支持這個功能。JSP緩沖標(biāo)記既能夠緩沖頁面片斷,也能夠緩沖整個頁面。當(dāng)JSP頁面執(zhí)行時,如果目標(biāo)片斷已經(jīng)在緩沖之中,則生成該片斷的代碼就不用再執(zhí)行。頁面級緩沖捕獲對指定URL的請求,并緩沖整個結(jié)果頁面。對于購物籃、目錄以及門戶網(wǎng)站的主頁來說,這個功能極其有用。對于這類應(yīng)用,頁面級緩沖能夠保存頁面執(zhí)行的結(jié)果,供后繼請求使用。
23、選擇合適的引用機(jī)制
在典型的JSP應(yīng)用系統(tǒng)中,頁頭、頁腳部分往往被抽取出來,然后根據(jù)需要引入頁頭、頁腳。當(dāng)前,在JSP頁面中引入外部資源的方法主要有兩種:include指令,以及include動作。
include指令:例如%@ include file="copyright.html"
%。該指令在編譯時引入指定的資源。在編譯之前,帶有include指令的頁面和指定的資源被合并成一個文件。被引用的外部資源在編譯時就確定,比運(yùn)行時才確定資源更高效。
include動作:例如jsp:include page="copyright.jsp"
/。該動作引入指定頁面執(zhí)行后生成的結(jié)果。由于它在運(yùn)行時完成,因此對輸出結(jié)果的控制更加靈活。但時,只有當(dāng)被引用的內(nèi)容頻繁地改變時,或者在對主頁面的請求沒有出現(xiàn)之前,被引用的頁面無法確定時,使用include動作才合算。
24、及時清除不再需要的會話
為了清除不再活動的會話,許多應(yīng)用服務(wù)器都有默認(rèn)的會話超時時間,一般為30分鐘。當(dāng)應(yīng)用服務(wù)器需要保存更多會話時,如果內(nèi)存容量不足,操作系統(tǒng)會把部分內(nèi)存數(shù)據(jù)轉(zhuǎn)移到磁盤,應(yīng)用服務(wù)器也可能根據(jù)“最近最頻繁使用”(Most
Recently
Used)算法把部分不活躍的會話轉(zhuǎn)儲到磁盤,甚至可能拋出“內(nèi)存不足”異常。在大規(guī)模系統(tǒng)中,串行化會話的代價是很昂貴的。當(dāng)會話不再需要時,應(yīng)當(dāng)及時調(diào)用HttpSession.invalidate()方法清除會話。HttpSession.invalidate()方法通??梢栽趹?yīng)用的退出頁面調(diào)用。
25、不要將數(shù)組聲明為:public static final 。
26、HashMap的遍歷效率討論
經(jīng)常遇到對HashMap中的key和value值對的遍歷操作,有如下兩種方法:MapString, String[] paraMap = new HashMapString, String[]();
................//第一個循環(huán)
SetString appFieldDefIds = paraMap.keySet();
for (String appFieldDefId : appFieldDefIds) {
String[] values = paraMap.get(appFieldDefId);
......
}
//第二個循環(huán)
for(EntryString, String[] entry : paraMap.entrySet()){
String appFieldDefId = entry.getKey();
String[] values = entry.getValue();
.......
}
第一種實現(xiàn)明顯的效率不如第二種實現(xiàn)。
分析如下 SetString appFieldDefIds = paraMap.keySet(); 是先從HashMap中取得keySet
代碼如下:
public SetK keySet() {
SetK ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}
private class KeySet extends AbstractSetK {
public IteratorK iterator() {
return newKeyIterator();
}
public int size() {
return size;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
public void clear() {
HashMap.this.clear();
}
}
其實就是返回一個私有類KeySet, 它是從AbstractSet繼承而來,實現(xiàn)了Set接口。
再來看看for/in循環(huán)的語法
for(declaration : expression_r)
statement
在執(zhí)行階段被翻譯成如下各式
for(IteratorE #i = (expression_r).iterator(); #i.hashNext();){
declaration = #i.next();
statement
}
因此在第一個for語句for (String appFieldDefId : appFieldDefIds) 中調(diào)用了HashMap.keySet().iterator() 而這個方法調(diào)用了newKeyIterator()
IteratorK newKeyIterator() {
return new KeyIterator();
}
private class KeyIterator extends HashIteratorK {
public K next() {
return nextEntry().getKey();
}
}
所以在for中還是調(diào)用了
在第二個循環(huán)for(EntryString, String[] entry : paraMap.entrySet())中使用的Iterator是如下的一個內(nèi)部類
private class EntryIterator extends HashIteratorMap.EntryK,V {
public Map.EntryK,V next() {
return nextEntry();
}
}
此時第一個循環(huán)得到key,第二個循環(huán)得到HashMap的Entry
效率就是從循環(huán)里面體現(xiàn)出來的第二個循環(huán)此致可以直接取key和value值
而第一個循環(huán)還是得再利用HashMap的get(Object key)來取value值
現(xiàn)在看看HashMap的get(Object key)方法
public V get(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length); //Entry[] table
EntryK,V e = table;
while (true) {
if (e == null)
return null;
if (e.hash == hash eq(k, e.key))
return e.value;
e = e.next;
}
}
其實就是再次利用Hash值取出相應(yīng)的Entry做比較得到結(jié)果,所以使用第一中循環(huán)相當(dāng)于兩次進(jìn)入HashMap的Entry中
而第二個循環(huán)取得Entry的值之后直接取key和value,效率比第一個循環(huán)高。其實按照Map的概念來看也應(yīng)該是用第二個循環(huán)好一點,它本來就是key和value的值對,將key和value分開操作在這里不是個好選擇。
如果執(zhí)行的順序不影響結(jié)果的話,可以不逐行執(zhí)行。
比如
a=1;
b=2;
c=3;這三條語句就是跟順序無關(guān)的,java會根據(jù)自己的需要進(jìn)行優(yōu)化來執(zhí)行。
jsp文件訪問時需要通過web容器進(jìn)行,先將jsp文件生成java文件,然后編譯成class文件,最后再執(zhí)行
%%中的內(nèi)容就是java代碼,也是java文件主方法的一步分代碼,也就是說最先執(zhí)行的就是這部分代碼
Java虛擬機(jī)(JVM)是可運(yùn)行Java代碼的假想計算機(jī)。
只要根據(jù)JVM規(guī)格描述將解釋器移植到特定的計算機(jī)上,就能保證經(jīng)過編譯的任何Java代碼能夠在該系統(tǒng)上運(yùn)行。
本文首先簡要介紹從Java文件的編譯到最終執(zhí)行的過程,隨后對JVM規(guī)格描述作一說明。
一.Java源文件的編譯、下載、解釋和執(zhí)行
Java應(yīng)用程序的開發(fā)周期包括編譯、下載、解釋和執(zhí)行幾個部分。
Java編譯程序?qū)ava源程序翻譯為JVM可執(zhí)行代碼?字節(jié)碼。
這一編譯過程同C/C++的編譯有些不同。
當(dāng)C編譯器編譯生成一個對象的代碼時,該代碼是為在某一特定硬件平臺運(yùn)行而產(chǎn)生的。
因此,在編譯過程中,編譯程序通過查表將所有對符號的引用轉(zhuǎn)換為特定的內(nèi)存偏移量,以保證程序運(yùn)行。
Java編譯器卻不將對變量和方法的引用編譯為數(shù)值引用,也不確定程序執(zhí)行過程中的內(nèi)存布局,而是將這些符號引用信息保留在字節(jié)碼中,由解釋器在運(yùn)行過程中創(chuàng)立內(nèi)存布局,然后再通過查表來確定一個方法所在的地址。
這樣就有效的保證了Java的可移植性和安全性。
運(yùn)行JVM字節(jié)碼的工作是由解釋器來完成的。
解釋執(zhí)行過程分三部進(jìn)行:代碼的裝入、代碼的校驗和代碼的執(zhí)行。
裝入代碼的工作由"類裝載器"(classloader)完成。
類裝載器負(fù)責(zé)裝入運(yùn)行一個程序需要的所有代碼,這也包括程序代碼中的類所繼承的類和被其調(diào)用的類。
當(dāng)類裝載器裝入一個類時,該類被放在自己的名字空間中。
除了通過符號引用自己名字空間以外的類,類之間沒有其他辦法可以影響其他類。
在本臺計算機(jī)上的所有類都在同一地址空間內(nèi),而所有從外部引進(jìn)的類,都有一個自己獨立的名字空間。
這使得本地類通過共享相同的名字空間獲得較高的運(yùn)行效率,同時又保證它們與從外部引進(jìn)的類不會相互影響。
當(dāng)裝入了運(yùn)行程序需要的所有類后,解釋器便可確定整個可執(zhí)行程序的內(nèi)存布局。
解釋器為符號引用同特定的地址空間建立對應(yīng)關(guān)系及查詢表。
通過在這一階段確定代碼的內(nèi)存布局,Java很好地解決了由超類改變而使子類崩潰的問題,同時也防止了代碼對地址的非法訪問。
隨后,被裝入的代碼由字節(jié)碼校驗器進(jìn)行檢查。
校驗器可發(fā)現(xiàn)操作數(shù)棧溢出,非法數(shù)據(jù)類型轉(zhuǎn)化等多種錯誤。
通過校驗后,代碼便開始執(zhí)行了。
Java字節(jié)碼的執(zhí)行有兩種方式:
1.即時編譯方式:解釋器先將字節(jié)碼編譯成機(jī)器碼,然后再執(zhí)行該機(jī)器碼。
2.解釋執(zhí)行方式:解釋器通過每次解釋并執(zhí)行一小段代碼來完成Java字節(jié)碼程序的所有操作。
通常采用的是第二種方法。
由于JVM規(guī)格描述具有足夠的靈活性,這使得將字節(jié)碼翻譯為機(jī)器代碼的工作
具有較高的效率。
對于那些對運(yùn)行速度要求較高的應(yīng)用程序,解釋器可將Java字節(jié)碼即時編譯為機(jī)器碼,從而很好地保證了Java代碼的可移植性和高性能。
二.JVM規(guī)格描述
JVM的設(shè)計目標(biāo)是提供一個基于抽象規(guī)格描述的計算機(jī)模型,為解釋程序開發(fā)人員提很好的靈活性,同時也確保Java代碼可在符合該規(guī)范的任何系統(tǒng)上運(yùn)行。
JVM對其實現(xiàn)的某些方面給出了具體的定義,特別是對Java可執(zhí)行代碼,即字節(jié)碼(Bytecode)的格式給出了明確的規(guī)格。
這一規(guī)格包括操作碼和操作數(shù)的語法和數(shù)值、標(biāo)識符的數(shù)值表示方式、以及Java類文件中的Java對象、常量緩沖池在JVM的存儲映象。
這些定義為JVM解釋器開發(fā)人員提供了所需的信息和開發(fā)環(huán)境。
Java的設(shè)計者希望給開發(fā)人員以隨心所欲使用Java的自由。
JVM定義了控制Java代碼解釋執(zhí)行和具體實現(xiàn)的五種規(guī)格,它們是:
JVM指令系統(tǒng)
JVM寄存器
JVM棧結(jié)構(gòu)
JVM碎片回收堆
JVM存儲區(qū)
2.1JVM指令系統(tǒng)
JVM指令系統(tǒng)同其他計算機(jī)的指令系統(tǒng)極其相似。
Java指令也是由操作碼和操作數(shù)兩部分組成。
操作碼為8位二進(jìn)制數(shù),操作數(shù)進(jìn)緊隨在操作碼的后面,其長度根據(jù)需要而不同。
操作碼用于指定一條指令操作的性質(zhì)(在這里我們采用匯編符號的形式進(jìn)行說明),如iload表示從存儲器中裝入一個整數(shù),anewarray表示為一個新數(shù)組分配空間,iand表示兩個整數(shù)的"與",ret用于流程控制,表示從對某一方法的調(diào)用中返回。
當(dāng)長度大于8位時,操作數(shù)被分為兩個以上字節(jié)存放。
JVM采用了"bigendian"的編碼方式來處理這種情況,即高位bits存放在低字節(jié)中。
這同Motorola及其他的RISCCPU采用的編碼方式是一致的,而與Intel采用的"littleendian"的編碼方式即低位bits存放在低位字節(jié)的方法不同。
Java指令系統(tǒng)是以Java語言的實現(xiàn)為目的設(shè)計的,其中包含了用于調(diào)用方法和監(jiān)視多先程系統(tǒng)的指令。
Java的8位操作碼的長度使得JVM最多有256種指令,目前已使用了160多種操作碼。
2.2JVM指令系統(tǒng)
所有的CPU均包含用于保存系統(tǒng)狀態(tài)和處理器所需信息的寄存器組。
如果虛擬機(jī)定義較多的寄存器,便可以從中得到更多的信息而不必對?;騼?nèi)存進(jìn)行訪問,這有利于提高運(yùn)行速度。
然而,如果虛擬機(jī)中的寄存器比實際CPU的寄存器多,在實現(xiàn)虛擬機(jī)時就會占用處理器大量的時間來用常規(guī)存儲器模擬寄存器,這反而會降低虛擬機(jī)的效率。
針對這種情況,JVM只設(shè)置了4個最為常用的寄存器。
它們是:
pc程序計數(shù)器
optop操作數(shù)棧頂指針
frame當(dāng)前執(zhí)行環(huán)境指針
vars指向當(dāng)前執(zhí)行環(huán)境中第一個局部變量的指針
所有寄存器均為32位。
pc用于記錄程序的執(zhí)行。
optop,frame和vars用于記錄指向Java棧區(qū)的指針。
2.3JVM棧結(jié)構(gòu)
作為基于棧結(jié)構(gòu)的計算機(jī),Java棧是JVM存儲信息的主要方法。
當(dāng)JVM得到一個Java字節(jié)碼應(yīng)用程序后,便為該代碼中一個類的每一個方法創(chuàng)建一個??蚣埽员4嬖摲椒ǖ臓顟B(tài)信息。
每個??蚣馨ㄒ韵氯愋畔ⅲ?/p>
局部變量
執(zhí)行環(huán)境
操作數(shù)棧
局部變量用于存儲一個類的方法中所用到的局部變量。
vars寄存器指向該變量表中的第一個局部變量。
執(zhí)行環(huán)境用于保存解釋器對Java字節(jié)碼進(jìn)行解釋過程中所需的信息。
它們是:上次調(diào)用的方法、局部變量指針和操作數(shù)棧的棧頂和棧底指針。
執(zhí)行環(huán)境是一個執(zhí)行一個方法的控制中心。
例如:如果解釋器要執(zhí)行iadd(整數(shù)加法),首先要從frame寄存器中找到當(dāng)前執(zhí)行環(huán)境,而后便從執(zhí)行環(huán)境中找到操作數(shù)棧,從棧頂彈出兩個整數(shù)進(jìn)行加法運(yùn)算,最后將結(jié)果壓入棧頂。
操作數(shù)棧用于存儲運(yùn)算所需操作數(shù)及運(yùn)算的結(jié)果。
2.4JVM碎片回收堆
Java類的實例所需的存儲空間是在堆上分配的。
解釋器具體承擔(dān)為類實例分配空間的工作。
解釋器在為一個實例分配完存儲空間后,便開始記錄對該實例所占用的內(nèi)存區(qū)域的使用。
一旦對象使用完畢,便將其回收到堆中。
在Java語言中,除了new語句外沒有其他方法為一對象申請和釋放內(nèi)存。
對內(nèi)存進(jìn)行釋放和回收的工作是由Java運(yùn)行系統(tǒng)承擔(dān)的。
這允許Java運(yùn)行系統(tǒng)的設(shè)計者自己決定碎片回收的方法。
在SUN公司開發(fā)的Java解釋器和HotJava環(huán)境中,碎片回收用后臺線程的方式來執(zhí)行。
這不但為運(yùn)行系統(tǒng)提供了良好的性能,而且使程序設(shè)計人員擺脫了自己控制內(nèi)存使用的風(fēng)險。
2.5JVM存儲區(qū)
JVM有兩類存儲區(qū):常量緩沖池和方法區(qū)。
常量緩沖池用于存儲類名稱、方法和字段名稱以及串常量。
方法區(qū)則用于存儲Java方法的字節(jié)碼。
對于這兩種存儲區(qū)域具體實現(xiàn)方式在JVM規(guī)格中沒有明確規(guī)定。
這使得Java應(yīng)用程序的存儲布局必須在運(yùn)行過程中確定,依賴于具體平臺的實現(xiàn)方式。
JVM是為Java字節(jié)碼定義的一種獨立于具體平臺的規(guī)格描述,是Java平 *** 立性的基礎(chǔ)。
目前的JVM還存在一些限制和不足,有待于進(jìn)一步的完善,但無論如何,JVM的思想是成功的。
對比分析:如果把Java原程序想象成我們的C++原程序,Java原程序編譯后生成的字節(jié)碼就相當(dāng)于C++原程序編譯后的80x86的機(jī)器碼(二進(jìn)制程序文件),JVM虛擬機(jī)相當(dāng)于80x86計算機(jī)系統(tǒng),Java解釋器相當(dāng)于80x86CPU。
在80x86CPU上運(yùn)行的是機(jī)器碼,在Java解釋器上運(yùn)行的是Java字節(jié)碼。
Java解釋器相當(dāng)于運(yùn)行Java字節(jié)碼的“CPU”,但該“CPU”不是通過硬件實現(xiàn)的,而是用軟件實現(xiàn)的。
Java解釋器實際上就是特定的平臺下的一個應(yīng)用程序。
只要實現(xiàn)了特定平臺下的解釋器程序,Java字節(jié)碼就能通過解釋器程序在該平臺下運(yùn)行,這是Java跨平臺的根本。
當(dāng)前,并不是在所有的平臺下都有相應(yīng)Java解釋器程序,這也是Java并不能在所有的平臺下都能運(yùn)行的原因,它只能在已實現(xiàn)了Java解釋器程序的平臺下運(yùn)行。