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

首頁 > 學(xué)院 > 開發(fā)設(shè)計 > 正文

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

2019-11-18 11:03:22
字體:
供稿:網(wǎng)友

 

開始使用javaP

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

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

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

javap -c ByteCodeDemo

你應(yīng)該會看到輸出類似這樣:

public class ByteCodeDemo extends java.lang.Object {
public ByteCodeDemo();
public static void main(java.lang.String[]);
}
Method ByteCodeDemo()
0 aload_0
1 invokespecial #1
4 return
Method void main(java.lang.String[])
0 getstatic #2
3 ldc #3
5 invokevirtual #4
8 return

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

0 getstatic #2

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

分析完第一個指令后很輕易猜到其它指令的意思。'ldc' (load constant) 指令將常量"Hello, World."壓入操作數(shù)棧。'invokevirtual'指令調(diào)用println方法,它從操作數(shù)棧彈出它的兩個參數(shù)。不要忘記一個像println這樣的實例方法有兩個參數(shù):上面的字符串,加上隱含的'this'引用。

字節(jié)碼如何預(yù)防內(nèi)存錯誤

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Method java.lang.String concat1(java.lang.String, java.lang.String)
0 new #5
3 dup
4 invokespecial #6
7 aload_1
8 invokevirtual #7
11 aload_2
12 invokevirtual #7
15 invokevirtual #8
18 areturn
Method void concat2(java.lang.StringBuffer, java.lang.String)
0 aload_1
1 aload_2
2 invokevirtual #7
5 pop
6 return

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

最后的一個范例展示了檢查字節(jié)碼如何幫助防止程序中的錯誤。像下面那樣創(chuàng)建兩個類,確保它們在獨立的文件中。

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");
}
}

假如你運行EternallyConstant,你會得到信息:

EternallyConstant beginning execution.

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

EternallyConstant beginning execution
Logging mode is on

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

Method void main(java.lang.String[])
0 getstatic #2
3 ldc #3
5 invokevirtual #4
8 getstatic #5
11 ifeq 22
14 getstatic #2
17 ldc #6
19 invokevirtual #4
22 return

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

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




發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 荥经县| 永川市| 凤庆县| 芷江| 紫云| 阿拉尔市| 交口县| 桃源县| 于田县| 慈利县| 红河县| 罗定市| 宁海县| 黄石市| 泾川县| 武隆县| 东宁县| 昭苏县| 永城市| 仙居县| 新疆| 洛南县| 浦县| 汾阳市| 扎兰屯市| 饶平县| 山阳县| 弥勒县| 板桥市| 平遥县| 临沂市| 漳州市| 磐石市| 巴青县| 龙川县| 禄劝| 南澳县| 定州市| 莲花县| 新宁县| 太白县|