国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 學(xué)院 > 開(kāi)發(fā)設(shè)計(jì) > 正文

字節(jié)碼防止內(nèi)存錯(cuò)誤及提高代碼質(zhì)量

2019-11-18 11:31:43
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友
   翻譯:Cherami

開(kāi)始使用javaP

大多Java程序員知道他們的程序通常不會(huì)被編譯為本機(jī)代碼而是被編譯為由java虛擬機(jī)(JVM)執(zhí)行的字節(jié)碼格式。然而,很少有java程序員曾經(jīng)看過(guò)字節(jié)碼因?yàn)樗麄兊墓ぞ卟还膭?lì)他們?nèi)タ础4蠖郕ava 調(diào)試工具不答應(yīng)單步執(zhí)行字節(jié)碼,它們要么顯示源代碼行,要么什么也不顯示。
幸運(yùn)的是JDK提供了javap,一個(gè)命令行工具,它使得查看字節(jié)碼很輕易。讓我們看一個(gè)范例:

public class ByteCodeDemo {
public static void main(String[] args) {
System.out.}
}

在編譯這個(gè)類(lèi)后,你可以用十六進(jìn)制編輯器打開(kāi).class文件然后參照虛擬機(jī)規(guī)范翻譯字節(jié)碼。幸運(yùn)的是有更簡(jiǎn)單的方法。JDK包含一個(gè)命令行的反匯編器:javap,它可以轉(zhuǎn)換字節(jié)碼為一種可讀的助記符形式,可以像下面這樣通過(guò)傳遞'-c'參數(shù)給javap得到字節(jié)碼列表:

javap -c ByteCodeDemo

你應(yīng)該會(huì)看到輸出類(lèi)似這樣:

public class ByteCodeDemo extends java.lang.Object {
public ByteCodeDemo();
public static void main(java.lang.String[]);
}
Method ByteCodeDemo()
0 aload_0
1 invokespecial #1 <Method java.lang.Object()>
4 return
Method void main(java.lang.String[])
0 getstatic #2 <Field java.io.PrintStream out>
3 ldc #3 <String "Hello world">
5 invokevirtual #4 <Method void println(java.lang.String)>
8 return

僅僅從這個(gè)短小的列表你可以學(xué)到很多字節(jié)碼的知識(shí)。從main方法的第一個(gè)指令開(kāi)始:

0 getstatic #2 <Field java.io.PrintStream out>

開(kāi)始的整數(shù)是方法中的指令的偏移值,因此第一個(gè)指令以0開(kāi)始。緊隨偏移量是指令的助記符(mnemonic)。在這個(gè)范例中,'getstatic' 指令將一個(gè)靜態(tài)成員壓入一個(gè)稱(chēng)為操作數(shù)堆棧的數(shù)據(jù)結(jié)構(gòu),后續(xù)的指令可以引用這個(gè)數(shù)據(jù)結(jié)構(gòu)中的成員。getstatic 指令后是要壓入的成員。在這個(gè)例子中,要壓入的成員是"#2 <Field java.io.PrintStream out>" 。假如你直接檢查字節(jié)碼,你會(huì)看到成員信息沒(méi)有直接嵌入指令而是像所有由java類(lèi)使用的常量那樣存儲(chǔ)在一個(gè)共享池中。將成員信息存儲(chǔ)在一個(gè)常量池中可以減小字節(jié)碼指令的大小,因?yàn)橹噶钪恍枰鎯?chǔ)常量池中的一個(gè)索引而不是整個(gè)常量。在這個(gè)例子中,成員信息位于常量池中的#2處。常量池中的項(xiàng)目的順序是和編譯器相關(guān)的,因此在你的環(huán)境中看到的可能不是'#2' 。

分析完第一個(gè)指令后很輕易猜到其它指令的意思。'ldc' (load constant) 指令將常量"Hello, World."壓入操作數(shù)棧。'invokevirtual'指令調(diào)用println方法,它從操作數(shù)棧彈出它的兩個(gè)參數(shù)。不要忘記一個(gè)像println這樣的實(shí)例方法有兩個(gè)參數(shù):上面的字符串,加上隱含的'this'引用。
photoshop教程 數(shù)據(jù)結(jié)構(gòu) 五筆輸入法專(zhuān)題 QQ病毒專(zhuān)題 共享上網(wǎng)專(zhuān)題 Google工具和服務(wù)專(zhuān)題 字節(jié)碼如何預(yù)防內(nèi)存錯(cuò)誤

Java語(yǔ)言經(jīng)常被吹捧為開(kāi)發(fā)互聯(lián)網(wǎng)軟件的"安全的"語(yǔ)言。表面上和c++如此相似的代碼如何體現(xiàn)安全呢?它引入的一個(gè)重要的安全概念是防止內(nèi)存相關(guān)的錯(cuò)誤。計(jì)算機(jī)罪犯利用內(nèi)存錯(cuò)誤在其它情況下安全的程序中插入自己的惡意的代碼。Java字節(jié)碼是第一個(gè)可以預(yù)防這種攻擊的,像下面的范例展示的:

public float add(float f, int n) {
return f + n;
}

假如你將這個(gè)方法加入上面的范例中,重新編譯它,然后運(yùn)行javap,你將看到的字節(jié)碼類(lèi)似這個(gè):

Method float add(float, int)
0 fload_1
1 iload_2
2 i2f
3 fadd
4 freturn

在方法的開(kāi)始,虛擬機(jī)將方法的參數(shù)放入一個(gè)稱(chēng)為局部變量表的數(shù)據(jù)結(jié)構(gòu)中。將像名字暗示的那樣,局部變量表也包含了你聲明的任何局部變量。在這個(gè)例子中,方法以三個(gè)局部變量表的項(xiàng)開(kāi)始,這些都是add方法的參數(shù),位置0保存this引用,而位置1和2分別保存float和int參數(shù)。

為了實(shí)際的操作這些變量,它們必須被加載(壓入)到操作數(shù)棧。第一個(gè)指令fload_1將位置1處的float壓入操作數(shù)棧,第二個(gè)指令iload_2將位置2處的int壓入操作數(shù)棧。這些指令的一個(gè)引起注重的事情是指令中的'i'和'f'前綴,這說(shuō)明Java字節(jié)碼指令是強(qiáng)類(lèi)型的。假如參數(shù)的類(lèi)型和字節(jié)碼的類(lèi)型不匹配,VM將該字節(jié)碼作為不安全的而加以拒絕。更好的是,字節(jié)碼被設(shè)計(jì)為只需在類(lèi)被加載時(shí)執(zhí)行一次這樣的類(lèi)型安全檢查。

這個(gè)類(lèi)型安全是如何加強(qiáng)安全的?假如一個(gè)攻擊者能夠欺騙虛擬機(jī)將一個(gè)int作為一個(gè)float或者相反,它就可以很輕易的以一個(gè)預(yù)期的的方法破壞計(jì)算。假如這些計(jì)算涉及銀行結(jié)余,那么隱含的安全性是很明顯的。更危險(xiǎn)的是欺騙VM將一個(gè)int作為一個(gè)Object引用。在大多情況下,這將導(dǎo)致VM崩潰,但是攻擊者只需要找到一個(gè)漏洞。不要忘記攻擊者不會(huì)手工搜索這個(gè)漏洞--寫(xiě)出一個(gè)程序產(chǎn)生數(shù)以?xún)|計(jì)的錯(cuò)誤字節(jié)碼的排列是相當(dāng)輕易的,這些排列試圖找到危害VM的幸運(yùn)的那個(gè)。

字節(jié)碼的另一個(gè)內(nèi)存安全防護(hù)是數(shù)組操作。'aastore' 和 'aaload' 字節(jié)碼操作Java數(shù)組并且它們總是檢查數(shù)組邊界。假如調(diào)用程序越過(guò)了數(shù)組尾,這些字節(jié)碼將拋出一個(gè)ArrayIndexOutOfBoundsException。也許所有最重要的檢查都使用分支指令,例如,以if開(kāi)始的字節(jié)碼。在字節(jié)碼中,分支指令只能轉(zhuǎn)移到同一方法中的其它指令。在方法外可以傳遞的唯一控制是使它返回:拋出一個(gè)異常或者執(zhí)行一個(gè)'invoke'指令。這不僅關(guān)閉了很多攻擊,同時(shí)也防止由于搖蕩引用(dangling reference)或者堆棧沖突而引發(fā)的令人厭惡的錯(cuò)誤。假如你曾經(jīng)使用系統(tǒng)調(diào)試器打開(kāi)你的程序并定位到代碼中的一個(gè)隨機(jī)的位置,那么你會(huì)很熟悉這些錯(cuò)誤。

所有這些檢查中需要記住的重要的一點(diǎn)是它們是由虛擬機(jī)在字節(jié)碼級(jí)進(jìn)行的而不是僅僅由編譯器在源代碼級(jí)進(jìn)行的。一個(gè)例如c++這樣的語(yǔ)言的編譯器可能在編譯時(shí)預(yù)防上面討論的某些內(nèi)存錯(cuò)誤,但是這些保護(hù)只是在源代碼級(jí)應(yīng)用。操作系統(tǒng)將很樂(lè)意加載執(zhí)行任何機(jī)器碼,無(wú)論這些代碼是由精細(xì)的c++編譯器產(chǎn)生的還是心懷惡意的攻擊者產(chǎn)生的。簡(jiǎn)單的講,C++僅僅是在源代碼級(jí)上面向?qū)ο蠖鳭ava的面向?qū)ο蟮奶匦詳U(kuò)展到編譯過(guò)的代碼級(jí)。

分析字節(jié)碼提升代碼質(zhì)量

Java字節(jié)碼的內(nèi)存和安全保護(hù)無(wú)論我們是否注重都是存在地,那么我們?yōu)槭裁催€費(fèi)心查看字節(jié)碼呢?在很多情況下,知道編譯器如何將你的代碼轉(zhuǎn)換為字節(jié)碼可以幫助你寫(xiě)出更高效的代碼,而且在某些情況下可以防止不易發(fā)覺(jué)的錯(cuò)誤。考慮下面的例子:

//返回 str1+str2 的串連
String concat(String str1, String str2) {
return str1 + str2;
}

//將 str2 附加到 str1
void concat(StringBuffer str1, String str2) {
str1.append(str2);
}

猜猜每個(gè)方法需要多少個(gè)方法調(diào)用。現(xiàn)在編譯這些方法并且運(yùn)行javap,你會(huì)得到類(lèi)似下面的輸出:

Method java.lang.String concat1(java.lang.String, java.lang.String)
0 new #5 <Class java.lang.StringBuffer>
3 dup
4 invokespecial #6 <Method java.lang.StringBuffer()>
7 aload_1
8 invokevirtual #7 <Method java.lang.StringBuffer append(java.lang.String)>
11 aload_2
12 invokevirtual #7 <Method java.lang.StringBuffer append(java.lang.String)>
15 invokevirtual #8 <Method java.lang.String toString()>
18 areturn

Method void concat2(java.lang.StringBuffer, java.lang.String)
0 aload_1
1 aload_2
2 invokevirtual #7 <Method java.lang.StringBuffer append(java.lang.String)>
5 pop
6 return

concat1方法執(zhí)行了5個(gè)方法調(diào)用s: new, invokespecial和三個(gè)invokevirtuals,這比concat2方法執(zhí)行了更多的工作,后者只執(zhí)行了一個(gè)invokevirtual調(diào)用。大多Java程序員已經(jīng)得到過(guò)警告,因?yàn)镾tring是不可變的,而使用StringBuffer進(jìn)行字符串連接效率更高。使用javap分析這個(gè)使得這點(diǎn)變得很生動(dòng)。假如你不能肯定兩個(gè)語(yǔ)言構(gòu)造在性能上是否相等,你應(yīng)該使用javap分析字節(jié)碼。然而,對(duì)just-in-time (JIT)編譯器要小心,因?yàn)镴IT編譯器將字節(jié)碼重新編譯為本機(jī)代碼而能執(zhí)行一些javap不能揭示的附加優(yōu)化。除非你有你的虛擬機(jī)的源代碼,否則你應(yīng)該補(bǔ)充你的字節(jié)碼的基準(zhǔn)性能分析。

最后的一個(gè)范例展示了檢查字節(jié)碼如何幫助防止程序中的錯(cuò)誤。像下面那樣創(chuàng)建兩個(gè)類(lèi),確保它們?cè)讵?dú)立的文件中。

public class ChangeALot {
public static final boolean debug=false;
public static boolean log=false;
}

public class EternallyConstant {
public static void main(String [] args) {
System.out.println("EternallyConstant beginning execution");
if (ChangeALot.debug)
System.out.println("Debug mode is on");
if (ChangeALot.log)
System.out.println("Logging mode is on");
}
}

假如你運(yùn)行EternallyConstant,你會(huì)得到信息:

EternallyConstant beginning execution.

現(xiàn)在試著編輯ChangeALot,修改debug和log變量的值為true(兩個(gè)都為true)。只重新編譯ChangeALot。再次運(yùn)行EternallyConstant,你將看到下面的輸出:

EternallyConstant beginning execution
Logging mode is on

debug變量怎么了?即使你將debug設(shè)置為true,信息"Debug mode is on"并沒(méi)有出現(xiàn)。答案在字節(jié)碼中。對(duì) EternallyConstant運(yùn)行javap你會(huì)看到:

Method void main(java.lang.String[])
0 getstatic #2 <Field java.io.PrintStream out>
3 ldc #3 <String "EternallyConstant beginning execution">
5 invokevirtual #4 <Method void println(java.lang.String)>
8 getstatic #5 <Field boolean log>
11 ifeq 22
14 getstatic #2 <Field java.io.PrintStream out>
17 ldc #6 <String "Logging mode is on">
19 invokevirtual #4 <Method void println(java.lang.String)>
22 return

驚異吧!在log成員上有一個(gè)'ifeq'檢查,而代碼根本沒(méi)有檢查debug成員。因?yàn)閐ebug成員被標(biāo)記為final類(lèi)型,編譯器知道debug成員在運(yùn)行時(shí)永遠(yuǎn)不會(huì)改變,因此它通過(guò)移除'if'聲明進(jìn)行優(yōu)化。這確實(shí)是一個(gè)非常有用的優(yōu)化,因?yàn)樗饝?yīng)你在程序中嵌入調(diào)試代碼而在將它設(shè)置為false時(shí)不用付出運(yùn)行時(shí)的代價(jià)。不幸的是這個(gè)優(yōu)化能夠?qū)е轮饕木幾g時(shí)混亂。假如你改變一個(gè)final成員,你必須記住重新編譯任何可能引用該成員的類(lèi)。這是因?yàn)檫@個(gè)'reference'可能已經(jīng)經(jīng)過(guò)優(yōu)化了。Java開(kāi)發(fā)環(huán)境不能總是發(fā)現(xiàn)這個(gè)微妙的相關(guān)性,一些能導(dǎo)致非常希奇的錯(cuò)誤。因此,古老的C++格言對(duì)于java環(huán)境仍然有效:"When in douBT, rebuild all."(有疑問(wèn),重新編譯所有的代碼)。

知道一些字節(jié)碼的知識(shí)對(duì)于使用java編程的程序員都是有價(jià)值的。javap工具使得查看字節(jié)碼很輕易。有時(shí)候,使用javap檢查你的代碼以期提高性能和捕捉非凡的不易察覺(jué)的錯(cuò)誤時(shí)是沒(méi)有用的。

字節(jié)碼和VM是相當(dāng)?shù)膹?fù)雜的,這已經(jīng)超過(guò)這個(gè)技巧可以涵蓋的范圍。要知道更多東西,看看Bill Venners寫(xiě)的《Inside the Java Virtual Machine》(譯者注: http://www.artima.com/insidejvm/ed2/ 有該書(shū)的1、2、3、4、5、7、8、9和20章的免費(fèi)Html版,英文。)

發(fā)表評(píng)論 共有條評(píng)論
用戶(hù)名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 共和县| 道真| 高尔夫| 天气| 天峨县| 陈巴尔虎旗| 盐城市| 顺义区| 元江| 汉川市| 聂拉木县| 时尚| 高州市| 光泽县| 墨玉县| 龙口市| 沙湾县| 巴塘县| 睢宁县| 鹤山市| 会同县| 安仁县| 阿巴嘎旗| 松桃| 道真| 龙井市| 万山特区| 奉新县| 繁昌县| 织金县| 定结县| 宁国市| 通渭县| 新泰市| 图们市| 屏东市| 九龙坡区| 云和县| 张家口市| 万山特区| 车致|