簡體   English   中英

Java類文件的創建是否確定?

[英]Is the creation of Java class files deterministic?

使用相同的JDK (即相同的javac可執行文件)時,生成的類文件是否始終相同? 可能會有所不同,具體取決於操作系統硬件 除JDK版本外,是否還有其他因素導致差異? 是否有任何編譯器選項可以避免差異? 僅在理論上可能存在差異,或者Oracle的javac實際為相同的輸入和編譯器選項生成不同的類文件?

更新1我對生成感興趣,即編譯器輸出,而不是類文件是否可以在各種平台上運行

更新2通過'相同的JDK',我也意味着相同的javac可執行文件。

更新3 Oracle編譯器中理論差異與實際差異的區別。

[編輯,添加轉述問題]
“在不同的平台上運行相同的javac可執行文件會產生不同的字節碼的情況是什么?”

我們這樣說吧:

在給定相同的.java文件的情況下,我可以輕松地生成一個完全符合規范的Java編譯器,它永遠不會生成兩次相同的.class文件。

我可以通過調整各種字節碼構造或簡單地向我的方法添加多余的屬性(這是允許的)來做到這一點。

鑒於規范要求編譯器生成逐字節的相同類文件,我會避免依賴於這樣的結果。

但是 ,我已經檢查了幾次,使用相同的開關(和相同的庫!)使用相同的編譯器編譯相同的源文件確實產生了相同的.class文件。

更新:我最近偶然發現了這篇有趣的博客文章,關於Java 7中的String on switch的實現 在這篇博文中,有一些相關的部分,我在這里引用(強調我的):

為了使編譯器的輸出可預測和可重復,這些數據結構中使用的映射和集合是LinkedHashMapLinkedHashSet而不僅僅是HashMapsHashSets 給定編譯期間生成的代碼的功能正確性方面使用HashMapHashSet會很好 ; 迭代順序無關緊要。 但是, 根據系統類的實現細節我們發現讓javac的輸出不變是有益的

這很清楚地說明了問題:編譯器不需要以確定的方式操作,只要它與規范匹配即可。 然而,編譯器開發人員意識到嘗試 通常是一個好主意(假設它不太昂貴,可能)。

編譯器沒有義務在每個平台上生成相同的字節碼。 您應該咨詢不同供應商的javac實用程序以獲得具體答案。


我將通過文件排序顯示一個實際的例子。

假設我們有2個jar文件: my1.jarMy2.jar 它們並排放在lib目錄中。 編譯器按字母順序讀取它們(因為這是lib ),但是當文件系統不區分大小寫時,順序為my1.jarMy2.jar ,如果區分大小寫, My2.jarmy1.jar

my1.jar有一個帶有方法的類A.class

public class A {
     public static void a(String s) {}
}

My2.jar具有相同的A.class ,但具有不同的方法簽名(接受Object ):

public class A {
     public static void a(Object o) {}
}

很明顯,如果你有電話

String s = "x"; 
A.a(s); 

它將在不同情況下編譯具有不同簽名的方法調用。 因此, 根據您的文件系統區分大小寫,您將獲得不同的類。

簡答 - 沒有


答案很長

對於不同的平台,它們的bytecode不必相同。 它是JRE(Java運行時環境),它知道如何執行字節碼。

如果您仔細閱讀Java VM規范,您將會發現,對於不同的平台,字節碼是相同的,這不一定是真的。

通過類文件格式 ,它將類文件的結構顯示為

ClassFile {
    u4 magic;
    u2 minor_version;
    u2 major_version;
    u2 constant_pool_count;
    cp_info constant_pool[constant_pool_count-1];
    u2 access_flags;
    u2 this_class;
    u2 super_class;
    u2 interfaces_count;
    u2 interfaces[interfaces_count];
    u2 fields_count;
    field_info fields[fields_count];
    u2 methods_count;
    method_info methods[methods_count];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

檢查次要版本和主要版本

minor_version,major_version

minor_version和major_version項的值是此類文件的次要版本號和主要版本號。總的來說,主要版本號和次要版本號確定類文件格式的版本。 如果類文件具有主版本號M和次版本號m,則我們將其類文件格式的版本表示為Mm。因此,類文件格式版本可以按字典順序排序,例如,1.5 <2.0 <2.1。 當且僅當v位於某個連續范圍Mi.0 v Mj.m中時,Java虛擬機實現可以支持版本v的類文件格式。 只有Sun可以指定符合Java平台特定發行版級別的Java虛擬機實現可支持的版本范圍

通過腳注閱讀更多內容

1 Sun JDK 1.0.2版的Java虛擬機實現支持45.0到45.3的類文件格式版本。 Sun的JDK 1.1.X版可以支持45.0到45.65535范圍內的類文件格式。 Java 2平台1.2版的實現可以支持45.0到46.0范圍內的版本的類文件格式。

因此,調查所有這些表明在不同平台上生成的類文件不必相同。

首先,規范中絕對沒有這樣的保證。 符合標准的編譯器可以將編譯時間標記為生成的類文件作為附加(自定義)屬性,並且類文件仍然是正確的。 然而,它會在每個構建上產生一個字節級別的不同文件,而且非常簡單。

其次,即使沒有這些令人討厭的技巧,也沒有理由期望編譯器連續兩次執行完全相同的操作,除非它的配置和輸入在兩種情況下都是相同的。 規范確實將源文件名描述為標准屬性之一,並且向源文件添加空行可以很好地更改行號表。

第三,由於主機平台,我從未遇到任何構建上的差異(除了可歸因於類路徑上的差異)。 基於平台(即本機代碼庫)而變化的代碼不是類文件的一部分,並且在加載類之后從字節碼實際生成本機代碼。

第四(也是最重要的一點)它想要知道這個過程中有一個糟糕的過程氣味 (比如代碼氣味,但是你如何對代碼采取行動)。 如果可能,請為源代碼版本,而不是版本,如果您確實需要在整個組件級別而不是在單個類文件上對版本,版本進行版本控制。 首選,使用CI服務器(如Jenkins)來管理將源轉換為可運行代碼的過程。

我相信,如果使用相同的JDK,生成的字節代碼將始終相同,與使用的harware和OS無關。 字節碼生成由java編譯器完成,它使用確定性算法將源代碼“轉換”為字節代碼。 因此,輸出將始終相同。 在這些情況下,只有源代碼的更新才會影響輸出。

Java allows you write/compile code on one platform and run on different platform. AFAIK ; 只有當在不同平台上生成的類文件相同或技術相同(即相同)時,才可能實現這一點。

編輯

我的意思是技術上相同的評論是。 如果逐字節比較,它們不需要完全相同。

因此,根據規范,不同平台上的類的.class文件不需要逐字節匹配。

總的來說,我不得不說,當由相同的編譯器編譯但在不同的平台上時,不能保證相同的源將產生相同的字節碼。

我將研究涉及不同語言(代碼頁)的場景,例如支持日語的Windows。 想想多字節字符; 除非編譯器總是假設它需要支持它可能針對8位ASCII優化的所有語言。

Java語言規范中有一節關於二進制兼容性的部分。

在SOM中的Release-to-Release二進制兼容性框架內(Forman,Conner,Danforth和Raper,OePSLA '99的Proceedings),Java編程語言二進制文件在作者識別的所有相關轉換下是二進制兼容的(有一些注意事項)關於添加實例變量)。 使用他們的方案,這里列出了Java編程語言支持的一些重要的二進制兼容更改:

•重新實現現有方法,構造函數和初始化程序以提高性能。

•更改方法或構造函數以返回輸入值,這些輸入值先前通過進入無限循環或導致死鎖而拋出通常不應發生或失敗的異常。

•向現有類或接口添加新字段,方法或構造函數。

•刪除類的私有字段,方法或構造函數。

•更新整個包時,刪除包中的類和接口的默認(僅包)訪問字段,方法或構造函數。

•重新排序現有類型聲明中的字段,方法或構造函數。

•在類層次結構中向上移動方法。

•重新排序類或接口的直接超接口列表。

•在類型層次結構中插入新的類或接口類型。

本章規定了所有實現保證的二進制兼容性的最低標准。 當混合類和接口的二進制文件時,Java編程語言保證了兼容性,這些二進制文件不是來自兼容的源,但其源代碼已經以此處描述的兼容方式進行了修改。 請注意,我們正在討論應用程序版本之間的兼容性。 討論Java SE平台版本之間的兼容性超出了本章的范圍。

對於這個問題:

“在不同的平台上運行相同的javac可執行文件會產生不同的字節碼的情況是什么?”

交叉編譯示例顯示了我們如何使用Javac選項:-target version

此標志生成的類文件與我們在調用此命令時指定的Java版本兼容。 因此,類文件將根據我們在使用此選項進行的比較期間提供的屬性而有所不同。

最有可能的答案是“是”,但要得到准確答案,需要在編譯期間搜索一些鍵或guid生成。

我不記得發生這種情況的情況。 例如,為了具有用於序列化目的的ID,它是硬編碼的,即由程序員或IDE生成。

PS JNI也很重要。

PPS我發現javac本身是用java編寫的。 這意味着它在不同平台上是相同的。 因此,如果沒有理由,它就不會產生不同的代碼。 因此,它只能通過本機調用來實現。

我會換句話說。

首先,我認為問題不在於確定性:

當然,這是確定性的:隨機性很難在計算機科學中實現,並且沒有理由編譯器會出於任何原因在此引入它。

其次,如果你通過“相同的源代碼文件的字節碼文件有多相似?”來重新表述它,那么 ,你不能依賴它們將是相似的事實

確保這一點的一個好方法是將.class(或我的情況下的.pyc)留在你的git階段。 您將意識到,在您的團隊中的不同計算機之間,當沒有對.py文件進行任何更改時(無論如何重新編譯),git會注意.pyc文件之間的更改。

至少那是我觀察到的。 所以把* .pyc和* .class放在.gitignore中!

有兩個問題。

Can there be a difference depending on the operating system or hardware? 

這是一個理論問題,答案很清楚,是的, 可以 正如其他人所說,規范不要求編譯器生成逐字節的相同類文件。

即使當前存在的每個編譯器在所有情況下(不同的硬件等)都產生相同的字節代碼,明天的答案也許會有所不同。 如果您從未計划更新javac或您的操作系統,您可以在特定情況下測試該版本的行為,但是如果您從例如Java 7 Update 11轉到Java 7 Update 15,結果可能會有所不同。

What are the circumstances where the same javac executable, when run on a different platform, will produce different bytecode?

那是不可知的。

我不知道配置管理是否是您提出問題的理由,但這是一個可以理解的理由。 比較字節代碼是合法的IT控件,但僅用於確定類文件是否更改,而不是確定源文件是否更改。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM