導航:首頁 > 源碼編譯 > 編譯器逃逸分析

編譯器逃逸分析

發布時間:2022-10-09 07:42:12

A. 編譯器的工作分為哪幾個階段

編譯器就是一個普通程序,沒什麼大不了的
什麼是編譯器?

編譯器是一個將高級語言翻譯為低級語言的程序。

首先我們一定要意識到編譯器就是一個普通程序,沒什麼大不了的。

在沒有弄明白編譯器如何工作之前你可以簡單的把編譯器當做一個黑盒子,其作用就是輸入一個文本文件輸出一個二進制文件。

基本上編譯器經過了以下幾個階段,等等,這句話教科書上也有,但是我相信很多同學其實並沒有真正理解這幾個步驟到底在說些什麼,為了讓你徹底理解這幾個步驟,我們用一個簡單的例子來講解。

假定我們有一段程序:

while (y < z) {
int x = a + b;
y += x;
}
那麼編譯器是怎樣把這一段程序人類認識的程序轉換為CPU認識的二進制機器指令呢?

提取出每一個單詞:詞法分析
首先編譯器要把源代碼中的每個「單詞」提取出來,在編譯技術中「單詞」被稱為token。其實不只是每個單詞被稱為一個token,除去單詞之外的比如左括弧、右括弧、賦值操作符等都被稱為token。

從源代碼中提取出token的過程就被稱為詞法分析,Lexical Analysis。

經過一遍詞法分析,編譯器得到了以下token:

T_While while
T_LeftParen (
T_Identifier y
T_Less <
T_Identifier z
T_RightParen )
T_OpenBrace {
T_Int int
T_Identifier x
T_Assign =
T_Identifier a
T_Plus +
T_Identifier b
T_Semicolon ;
T_Identifier y
T_PlusAssign +=
T_Identifier x
T_Semicolon ;
T_CloseBrace }
就這樣一個磁碟中保存的字元串源代碼文件就轉換為了一個個的token。

這些token想表達什麼意思:語法分析
有了這些token之後編譯器就可以根據語言定義的語法恢復其原本的結構,怎麼恢復呢?

原來,編譯器在掃描出各個token後根據規則將其用樹的形式表示出來,這顆樹就被稱為語法樹。

語法樹是不是合理的:語義分析
有了語法樹後我們還要檢查這棵樹是不是合法的,比如我們不能把一個整數和一個字元串相加、比較符左右兩邊的數據類型要相同,等等。

這一步通過後就證明了程序合法,不會有編譯錯誤。

B. java 異常at org.fenixsoft.oom.VMStackSOF.leak(VMStackSOF.java:20)

先了解SOF的生成原因,以下為轉載,可以自己網路。

Hotspot虛擬機並不區分VM棧和本地方法棧,因此-Xoss參數實際上是無效的,棧容量只由-Xss參數設定。關於VM棧和本地方法棧在VM Spec描述了兩種異常:StackOverflowError與OutOfMemoryError,當棧空間無法繼續分配分配時,到底是內存太小還是棧太大其實某種意義上是對同一件事情的兩種描述而已,在筆者的實驗中,對於單線程應用嘗試下面3種方法均無法讓虛擬機產生OOM,全部嘗試結果都是獲得SOF異常。

1.使用-Xss參數削減棧內存容量。結果:拋出SOF異常時的堆棧深度相應縮小。

2.定義大量的本地變數,增大此方法對應幀的長度。結果:拋出SOF異常時的堆棧深度相應縮小。

3.創建幾個定義很多本地變數的復雜對象,打開逃逸分析和標量替換選項,使得JIT編譯器允許對象拆分後在棧中分配。結果:實際效果同第二點。

VM棧和本地方法棧OOM測試(僅作為第1點測試程序)

/**
*VMArgs:-Xss128k
*@authorzzm
*/
publicclassJavaVMStackSOF{

privateintstackLength=1;

publicvoidstackLeak(){
stackLength++;
stackLeak();
}

publicstaticvoidmain(String[]args)throwsThrowable{
JavaVMStackSOFoom=newJavaVMStackSOF();
try{
oom.stackLeak();
}catch(Throwablee){
System.out.println("stacklength:"+oom.stackLength);
throwe;
}
}
}

運行結果:

stacklength:2402Exceptioninthread"main"java.lang.StackOverflowErroratorg.fenixsoft.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:20)atorg.fenixsoft.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:21)atorg.fenixsoft.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:21)
這個應該和你遇到的問題一樣,看看是不是存在以上3點的問題。

C. Java中有類似於NGen的工具嗎

Java世界裡有很多AOT編譯的解決方案,雖然Oracle/Sun JDK到JDK8為止都還沒有提供這樣的功能。

先來幾個傳送門:

HotSpot VM JIT的編譯產出,理論上能否被復用? - RednaxelaFX 的回答

逃逸分析為何不能在編譯期進行? - RednaxelaFX 的回答

為什麼Java不能由JVM產生針對特定操作系統的機器碼從而提高效率? - RednaxelaFX 的回答

請教:對Java類庫jar文件,有什麼好的防止反編譯辦法,最好是加密/解密方案,而不是代碼混淆方案。 - RednaxelaFX 的回答

如何將Java打包成exe文件在沒有JRE環境的電腦上執行? - RednaxelaFX 的回答

各個操作系統下的 JVM 是誰開發出來的? - RednaxelaFX 的回答

ios設備如何java編譯? - RednaxelaFX 的回答
java現在能直接編譯成機器碼? - Java

在深入到具體實現前,要先強調一點:

AOT編譯(Ahead-of-Time Compilation)不但涉及一個編譯器,還要涉及配套的運行時支持系統(runtime system)。兩者通常是緊密耦合的。

句話說,一個AOT編譯器只能跟自己的runtime搭配使用,這個runtime可以是一個完整的VM(如NGen與CLR的搭配),也可以是一個比較
小的runtime(如.NET Native里的MRT(Minimal Runtime),只提供基礎的GC、多線程支持等功能)。

所以不要想像說下面列舉的工具能夠對Java做AOT編譯,然後運行時還搭配Oracle JDK / OpenJDK來使用。

來看看一些具體實現。

IBM JDK6+ - Enhance performance with class sharing

IBM JDK是主流JDK之一,並且提供了AOT編譯的功能。
這個AOT編譯的主要目的是提高啟動速度,以及在多個進程之間共享AOT編譯出來的機器碼。
被AOT編譯的代碼在運行時還可以再次被JIT編譯,這樣既能提高啟動速度,又不會影響最高速度(peak performance)。

這個AOT編譯用的編譯器就是IBM J9 VM里的JIT編譯器,只是讓它以AOT模式來工作。這點跟NGen有點類似(NGen也是直接用CLR里的JIT編譯器來生成native image)。跟它搭配使用的runtime自然就是完整的J9 VM。

過跟NGen不同的是,NGen是真的在程序執行前就做了編譯,而IBM
J9提供的AOT編譯其實是在用戶第一次運行程序時把特殊模式的JIT編譯生成的代碼緩存到磁碟上,後續執行的時候就可以直接使用該緩存里的機器碼。所以
IBM把這個功能叫做「dynamic AOT」。

Excelsior JET

一個比較成熟的商業的Java AOT編譯解決方案。僅支持x86上的若干操作系統。
這個AOT編譯器是自己寫的一個私有的編譯器,其搭配使用的runtime也是私有的。它支持幾種不同的編譯模式,搭配使用的runtime可以完全不帶解釋器/JIT編譯器,只帶有GC、線程支持等功能,也可以帶有更完整的JVM功能。

現在可能很多人都知道Android的ART,而對Java世界裡的老前輩們沒啥認知。其實ART的AOT編譯與解釋器/JIT混合的方式,跟Excelsior JET(以及下面提到的GCJ)是相當相似的。

GCJ

一個開源的Java運行時系統,支持AOT編譯、解釋執行與JIT編譯。GCJ是GCC的一部分。在OpenJDK流行起來之前,通常各種Linux發行版帶的Java實現會是GCJ。

RoboVM

一個讓Java程序可以運行在iOS上的開源解決方案。

iOS不允許第三方程序做運行時代碼生成(也就是不允許JIT編譯),所以在iOS上運行程序要麼得AOT編譯,要麼只能解釋執行。Oracle ADF選擇使用一個只能解釋執行的JVM來支持Java程序,而RoboVM選擇使用AOT編譯。

RoboVM的AOT編譯器藉助了不少現成的框架來實現。其中最重要的兩個是Soot與LLVM,前者解決編譯器前端、後者解決編譯器後端,RoboVM自己只要解決一些跟runtime搭配的地方就好了。
RoboVM配套的runtime是自己寫的一個比較小的runtime。

VMKit

VMKit是一個基於許多現成的庫組合起來實現的VM,主要可以用作JVM,也可配置為一個CLI。
VMKit支持AOT編譯。它的JIT與AOT編譯器都是基於LLVM實現的。不過實現得比較粗糙嗯。

Avian

Avian不是一個完整的JVM,只支持Java的一個比較有用的子集。很多時候也夠用了。
它可以支持AOT編譯。

ART (Android Runtime)

ART和Dalvik VM雖然不直接實現Java位元組碼,但從整個系統的角度看它們倆都是不折不扣的Java系統。
ART支持AOT編譯與解釋執行。Java程序的啟動速度在ART上是比在Dalvik VM上快多了。

只想強調一點:ART的AOT編譯並不依賴LLVM。詳情請參考另外幾個回答:

Android 中的 LLVM 主要做什麼? - RednaxelaFX 的回答

如何看待微軟新出的LLILC,一個新的基於LLVM的CoreCLR JIT/AOT編譯器? - RednaxelaFX 的回答

Jikes RVM、Maxine VM、Joeq

這三個是元循環Java虛擬機的代表。關於元循環虛擬機(metacircular VM),請參考另一個回答:用 JavaScript 寫成的 JavaScript 解釋器,意義是什麼? - RednaxelaFX 的回答
它們都是用純Java實現的Java虛擬機,而且都能獨立運行,也就是說可以自舉(bootstrap)。要實現bootstrap,它們就必然需要能支持AOT編譯的編譯器。

所以說在這類實現里,AOT編譯不是為了提高啟動速度,而是為了實現bootstrap的根本需求。有趣的是,它們可以(在一定范圍內)支持定製boot image的內容,也就是說可以讓Java應用程序與JVM一起AOT編譯構成boot image。

JNode - Java New Operating System Design Effort

JNode是一個用純Java實現的操作系統。這比上面三個元循環Java虛擬機還要更進一步,可以在裸硬體上bootstrap。自然,它也需要一個支持AOT編譯的編譯器,同樣是出於實現的根本需求。

Oracle Labs: Substrate VM

Substrate VM是Oracle Labs的一個研究項目,跟Graal編譯器與Truffle框架搭配使用。它實現了一個很小型的、可定製的runtime,可以讓基於Truffle實現的編程語言可以脫離標准JVM獨立運行。
這里,Substrate VM提供的是runtime的功能,真正的AOT編譯器是Graal。

Oracle/Sun JDK


實Sun以前在JDK6時期研究過實現AOT編譯,但是當時選擇的實現方式比較取巧。後來發現效果並不理想,而且在有了多層編譯系統(tiered
compilation system)之後這個AOT編譯的原型實現在啟動速度上根本沒有優勢,就把這個項目擱置了。具體細節抱歉我無法多說。

但是Oracle JDK在計劃提供新的AOT編譯支持。或許會在未來版本的Oracle JDK里出現。請拭目以待。
目前Oracle在公開場合介紹這個AOT編譯器的主要資料是JVMLS 2015上的一個演講,Java Goes AOT - JVMLS 2015 (打不開請自備工具…),有興趣的同學可以參考下。它是一個基於Graal編譯器的實現。

IKVM.NET

前面說的AOT編譯解決方案都是把Java(包含Java位元組碼的Class文件)編譯到native code。http://IKVM.NET是一種比較特殊的方案,把Java Class文件編譯到.NET Assembly,然後可以用任意CLI(Common Language Infrastructure)實現上運行,例如微軟的CLR和Xamarin的Mono。

藉助CLR的NGen和Mono的AOT編譯,http://IKVM.NET生成的.NET Assembly還可以進一步被編譯為native code。這樣也算間接達到了AOT編譯的目的。

這不是拿來搞笑的。http://IKVM.NET的作者做過一個演示,在Windows上基於IKVM+NGen來運行Eclipse,啟動速度比當時的Oracle JDK6快得多…

跟http://IKVM.NET類似的項目以前還有幾個,例如Ja.NET(官網掛了,介紹可以看InfoQ的新聞稿)。但活到現在的恐怕就IKVM.NET一家了。

D. Go 語言內存管理(三):逃逸分析

Go 語言較之 C 語言一個很大的優勢就是自帶 GC 功能,可 GC 並不是沒有代價的。寫 C 語言的時候,在一個函數內聲明的變數,在函數退出後會自動釋放掉,因為這些變數分配在棧上。如果你期望變數的數據可以在函數退出後仍然能被訪問,就需要調用 malloc 方法在堆上申請內存,如果程序不再需要這塊內存了,再調用 free 方法釋放掉。Go 語言不需要你主動調用 malloc 來分配堆空間,編譯器會自動分析,找出需要 malloc 的變數,使用堆內存。編譯器的這個分析過程就叫做逃逸分析。

所以你在一個函數中通過 dict := make(map[string]int) 創建一個 map 變數,其背後的數據是放在棧空間上還是堆空間上,是不一定的。這要看編譯器分析的結果。

可逃逸分析並不是百分百准確的,它有缺陷。有的時候你會發現有些變數其實在棧空間上分配完全沒問題的,但編譯後程序還是把這些數據放在了堆上。如果你了解 Go 語言編譯器逃逸分析的機制,在寫代碼的時候就可以有意識地繞開這些缺陷,使你的程序更高效。

Go 語言雖然在內存管理方面降低了編程門檻,即使你不了解堆棧也能正常開發,但如果你要在性能上較真的話,還是要掌握這些基礎知識。

這里不對堆內存和棧內存的區別做太多闡述。簡單來說就是, 棧分配廉價,堆分配昂貴。 棧空間會隨著一個函數的結束自動釋放,堆空間需要時間 GC 模塊不斷地跟蹤掃描回收。如果對這兩個概念有些迷糊,建議閱讀下面 2 個文章:

這里舉一個小例子,來對比下堆棧的差別:

stack 函數中的變數 i 在函數退出會自動釋放;而 heap 函數返回的是對變數 i 的引用,也就是說 heap() 退出後,表示變數 i 還要能被訪問,它會自動被分配到堆空間上。

他們編譯出來的代碼如下:

邏輯的復雜度不言而喻,從上面的匯編中可看到, heap() 函數調用了 runtime.newobject() 方法,它會調用 mallocgc 方法從 mcache 上申請內存,申請的內部邏輯前面文章已經講述過。堆內存分配不僅分配上邏輯比棧空間分配復雜,它最致命的是會帶來很大的管理成本,Go 語言要消耗很多的計算資源對其進行標記回收(也就是 GC 成本)。

Go 編輯器會自動幫我們找出需要進行動態分配的變數,它是在編譯時追蹤一個變數的生命周期,如果能確認一個數據只在函數空間內訪問,不會被外部使用,則使用棧空間,否則就要使用堆空間。

我們在 go build 編譯代碼時,可使用 -gcflags '-m' 參數來查看逃逸分析日誌。

以上面的兩個函數為例,編譯的日誌輸出是:

日誌中的 &i escapes to heap 表示該變數數據逃逸到了堆上。

需要使用堆空間,所以逃逸,這沒什麼可爭議的。但編譯器有時會將 不需要 使用堆空間的變數,也逃逸掉。這里是容易出現性能問題的大坑。網上有很多相關文章,列舉了一些導致逃逸情況,其實總結起來就一句話:

多級間接賦值容易導致逃逸

這里的多級間接指的是,對某個引用類對象中的引用類成員進行賦值。Go 語言中的引用類數據類型有 func , interface , slice , map , chan , *Type(指針) 。

記住公式 Data.Field = Value ,如果 Data , Field 都是引用類的數據類型,則會導致 Value 逃逸。這里的等號 = 不單單只賦值,也表示參數傳遞。

根據公式,我們假設一個變數 data 是以下幾種類型,相應的可以得出結論:

下面給出一些實際的例子:

如果變數值是一個函數,函數的參數又是引用類型,則傳遞給它的參數都會逃逸。

上例中 te 的類型是 func(*int) ,屬於引用類型,參數 *int 也是引用類型,則調用 te(&j) 形成了為 te 的參數(成員) *int 賦值的現象,即 te.i = &j 會導致逃逸。代碼中其他幾種調用都沒有形成 多級間接賦值 情況。
同理,如果函數的參數類型是 slice , map 或 interface{} 都會導致參數逃逸。

匿名函數的調用也是一樣的,它本質上也是一個函數變數。有興趣的可以自己測試一下。

只要使用了 Interface 類型(不是 interafce{} ),那麼賦值給它的變數一定會逃逸。因為 interfaceVariable.Method() 先是間接的定位到它的實際值,再調用實際值的同名方法,執行時實際值作為參數傳遞給方法。相當於 interfaceVariable.Method.this = realValue

向 channel 中發送數據,本質上就是為 channel 內部的成員賦值,就像給一個 slice 中的某一項賦值一樣。所以 chan *Type , chan map[Type]Type , chan []Type , chan interface{} 類型都會導致發送到 channel 中的數據逃逸。

這本來也是情理之中的,發送給 channel 的數據是要與其他函數分享的,為了保證發送過去的指針依然可用,只能使用堆分配。

可變參數如 func(arg ...string) 實際與 func(arg []string) 是一樣的,會增加一層訪問路徑。這也是 fmt.Sprintf 總是會使參數逃逸的原因。

例子非常多,這里不能一一列舉,我們只需要記住分析方法就好,即,2 級或更多級的訪問賦值會 容易 導致數據逃逸。這里加上 容易 二字是因為隨著語言的發展,相信這些問題會被慢慢解決,但現階段,這個可以作為我們分析逃逸現象的依據。

下面代碼中包含 2 種很常規的寫法,但他們卻有著很大的性能差距,建議自己想下為什麼。

Benchmark 和 pprof 給出的結果:

熟悉堆棧概念可以讓我們更容易看透 Go 程序的性能問題,並進行優化。

多級間接賦值會導致 Go 編譯器出現不必要的逃逸,在一些情況下可能我們只需要修改一下數據結構就會使性能有大幅提升。這也是很多人不推薦在 Go 中使用指針的原因,因為它會增加一級訪問路徑,而 map , slice , interface{} 等類型是不可避免要用到的,為了減少不必要的逃逸,只能拿指針開刀了。

大多數情況下,性能優化都會為程序帶來一定的復雜度。建議實際項目中還是怎麼方便怎麼寫,功能完成後通過性能分析找到瓶頸所在,再對局部進行優化。

E. Go切片數組深度解析

Go 中的分片數組,實際上有點類似於Java中的ArrayList,是一個可以擴展的數組,但是Go中的切片由比較靈活,它和數組很像,也是基於數組,所以在了解Go切片前我們先了解下數組。

數組簡單描述就由相同類型元素組成的數據結構, 在創建初期就確定了長度,是不可變的。

但是Go的數組類型又和C與Java的數組類型不一樣, NewArray 用於創建一個數組,從源碼中可以看出最後返回的是 &Array{}的指針,並不是第一個元素的指針,在Go中數組屬於值類型,在進行傳遞時,採取的是值傳遞,通過拷貝整個數組。Go語言的數組是一種有序的struct。

Go 語言的數組有兩種不同的創建方式,一種是顯示的初始化,一種是隱式的初始化。

注意一定是使用 [...]T 進行創建,使用三個點的隱式創建,編譯器會對數組的大小進行推導,只是Go提供的一種語法糖。

其次,Go中數組的類型,是由數值類型和長度兩個一起確定的。[2]int 和 [3]int 不是同一個類型,不能進行傳參和比較,把數組理解為類型和長度兩個屬性的結構體,其實就一目瞭然了。

Go中的數組屬於值類型,通常應該存儲於棧中,局部變數依然會根據逃逸分析確定存儲棧還是堆中。

編譯器對數組函數中做兩種不同的優化:

在靜態區完成賦值後復制到棧中。

總結起來,在不考慮逃逸分析的情況下,如果數組中元素的個數小於或者等於 4 個,那麼所有的變數會直接在棧上初始化,如果數組元素大於 4 個,變數就會在靜態存儲區初始化然後拷貝到棧上。

由於數組是值類型,那麼賦值和函數傳參操作都會復制整個數組數據。

不管是賦值或函數傳參,地址都不一致,發生了拷貝。如果數組的數據較大,則會消耗掉大量內存。那麼為了減少拷貝我們可以主動的傳遞指針呀。

地址是一樣的,不過傳指針會有一個弊端,從列印結果可以看到,指針地址都是同一個,萬一原數組的指針指向更改了,那麼函數裡面的指針指向都會跟著更改。

同樣的我們將數組轉換為切片,通過傳遞切片,地址是不一樣的,數組值相同。

切片是引用傳遞,所以它們不需要使用額外的內存並且比使用數組更有效率。

所以,切片屬於引用類型。

通過這種方式可以將數組轉換為切片。

中間不加三個點就是切片,使用這種方式創建切片,實際上是先創建數組,然後再通過第一種方式創建。

使用make創建切片,就不光編譯期了,make創建切片會涉及到運行期。1. 切片的大小和容量是否足夠小;
切片是否發生了逃逸,最終在堆上初始化。如果切片小的話會先在棧或靜態區進行創建。

切片有一個數組的指針,len是指切片的長度, cap指的是切片的容量。

cap是在初始化切片是生成的容量。

發現切片的結構體是數組的地址指針array unsafe.Pointer,而Go中數組的地址代表數組結構體的地址。

slice 中得到一塊內存地址,&array[0]或者unsafe.Pointer(&array[0])。

也可以通過地址構造切片

nil切片:指的unsafe.Pointer 為nil

空切片:

創建的指針不為空,len和cap為空

當一個切片的容量滿了,就需要擴容了。怎麼擴,策略是什麼?

如果原來數組切片的容量已經達到了最大值,再想擴容, Go 默認會先開一片內存區域,把原來的值拷貝過來,然後再執行 append() 操作。這種情況對現數組的地址和原數組地址不相同。

從上面結果我們可以看到,如果用 range 的方式去遍歷一個切片,拿到的 Value 其實是切片裡面的值拷貝,即淺拷貝。所以每次列印 Value 的地址都不變。

由於 Value 是值拷貝的,並非引用傳遞,所以直接改 Value 是達不到更改原切片值的目的的,需要通過 &slice[index] 獲取真實的地址。

F. 反射的性能開銷都在哪

1.反射調用過程中會產生大量的臨時對象,這些對象會佔用內存,可能會導致頻繁 gc,從而影響性能。
2.反射調用方法時會從方法數組中遍歷查找,並且會檢查可見性等操作會耗時。
3.反射在達到一定次數時,會動態編寫位元組碼並載入到內存中,這個位元組碼沒有經過編譯器優化,也不能享受JIT優化。
4.Method#invoke 方法會對參數做封裝和解封操作,會涉及自動裝箱/拆箱和類型轉換,都會帶來一定的資源開銷。
5.反射方法難以內聯
6.需要檢查方法可見性和校驗參數

invoke 方法的參數是一個可變長參數,也就是構建一個 Object 數組存參數,這也同時帶來了基本數據類型的裝箱操作,在 invoke 內部會進行運行時許可權檢查,這也是一個損耗點。普通方法調用可能有一系列優化手段,比如方法內聯、逃逸分析,而這又是反射調用所不能做的,性能差距再一次被放大。
優化反射調用,可以盡量避免反射調用虛方法、關閉運行時許可權檢查、可能需要增大基本數據類型對應的包裝類緩存、如果調用次數可知可以關閉 Inflation 機制,以及增加內聯緩存記錄的類型數目。

G. 菜鳥:剛學java,堆區,棧區,靜態區,代碼區,暈了!!!!!

你問題太多了。簡單為你解答一下吧,JAVA語言的內存管理分為棧內存,堆內存和方法區,棧內存用來存儲基本數據類型和對象的引用(對象的實體和引用這兩個概念你要搞明白),堆內存用來存儲對象的實體。。你記住,JAVA是一門面向對象的語言,在JAVA理萬事萬物都是對象,除了兩個東西:1,8個基本數據類型(對應的,還有8個相關的包裝類,但是為了JAVA運行速度的考慮,SUN公司保留了這8個基本數據類型);2,就是你所謂的入口方法,即main方法;這兩點是JAVA不是純粹的面向對象語言的表現,也就是他比較特殊的地方,你記住就行了;接下來,我們來看棧內存和堆內存,JAVA裡面所有東西都是對象,那麼對象保存在哪呢?其實,對象里的所有東西保存在堆內存里,裡麵包括了這個對象的成員變數和方法等東西,而棧內存里,保存的是這個對象所屬的這塊堆內存的首地址?也就是一個16進制的數字,明白了?因為你要告訴JAVA虛擬機從哪裡去開始讀取這塊堆內存啊。所以,你明白棧內存用來存儲基本數據類型和對象的引用,堆內存用來存儲對象的實體了。。而內存管理裡面還有一塊叫方法區,這是JAVA虛擬機用來在執行一個JAVA程序之前保存這個程序的結構等級的地方,虛擬機按照這個結構等級來調用程序里德對象方法等,而靜態變數和靜態方法正是保存在方法區里,所以靜態方法可以在不創建對象的時候就調用,因為創建對象就是為對象分配堆內存,只有創建了對象之後才能調用對象的非靜態方法和非靜態變數。。你的第一個問題就能解答了,這個情況就是zhangsan的堆內存里保存的car對象的引用,而這個引用又指向car對象的堆內存;對象的成員變數是保存在自己的堆內存里的;而入口類是一個特殊的東西,你特殊對待就行了。

H. 英語Max Non Heap Memory怎麼翻譯

為什麼要學習JVM?

深入理解JVM可以幫助我們從平台角度提高解決問題的能力,例如,有效防止內存泄漏(Memory leak),優化線程鎖的使用 (Thread Lock),更加高效的進行垃圾回收 (Garbage collection),提高系統吞吐量 (throughput),降低延遲(Delay),提高其性能(performance)等。

你是如何理解JVM的?

JVM 是 Java Virtual Machine的縮寫,顧名思義,它是一個虛擬計算機,是硬體計算機的抽象(虛構)實現,是JAVA平台的一部分,如圖所示(見圖中的最底端):

JVM是 Java 程序能夠實現跨平台的基礎(Java的跨平台本質上是通過不同平台的JVM實現的),它的作用是載入 Java 程序,把位元組碼(bytecode)翻譯成機器碼再交由 CPU 執行。如圖所示:

程序在執行之前先要把 Java 代碼(.java)轉換成位元組碼(.class),JVM 通過類載入器(ClassLoader)把位元組碼載入到內存中,【關注尚矽谷,輕松學IT】但位元組碼文件是 JVM 的一套指令集規范,並不能直接交給底層操作系統去執行,因此需要特定的命令解析器執行引擎(Execution Engine) 將位元組碼翻譯成底層機器碼,再交由 CPU 去執行。

市場上有哪些主流的JVM呢?

JVM是一種規范,基於這種規范,不同公司做了具體實現,BEA公司研發JRockit VM,後在2008年由Oracle公司收購;IBM公司研發了J9 VM,只應用於IBM 內部。Sun公司研發了HotSpot VM,後在2010年由Oracle公司收購。目前是甲骨文公司最主流的一款JVM虛擬機,也是我們現在最常用的一種。

JVM的體系結構是怎樣的?

JVM 的體系結構,如圖所示:

類載入系統 (ClassLoader System)負責載入類到內存;運行時數據區 (Runtime Data Area)負責存儲對象數據信息;執行引擎(Execution Engine)負責調用對象執行業務;本地庫介面(Native Interface)負責融合不同的編程語言為 Java 所用。

JVM有哪些運行模式嗎?

JVM有兩種運行模式Server與Client。兩種模式的區別在於,Client模式啟動速度較快,Server模式啟動較慢;但是啟動進入穩定期之後Server模式的程序運行速度比Client要快很多。這是因為Server模式啟動的JVM採用的是重量級的虛擬機,對程序採用了更多的優化;而Client模式啟動的JVM採用的是輕量級的虛擬機。所以Server啟動慢,但穩定後速度比Client遠遠要快。

現在64位的jdk中默認都是server模式(可通過 java -version進行查看)。當虛擬機運行在-client模式的時候,使用的是一個代號為C1的輕量級編譯器, 而server模式啟動的虛擬機採用相對重量級,代號為C2的編譯器.c1、c2都是JIT編譯器, C2比C1編譯器編譯的相對徹底,服務起來之後,性能更高。

JVM 運行時內存結構是怎樣的?

不同虛擬機實現可能略微有所不同,但都會遵從 Java 虛擬機規范,Java 8 虛

擬機規范規定,Java 虛擬機所管理的內存將會包括以下幾個區域,如圖所示:


I. 編譯器的工作原理

編譯 是從源代碼(通常為高級語言)到能直接被計算機或虛擬機執行的目標代碼(通常為低級語言或機器語言)的翻譯過程。然而,也存在從低級語言到高級語言的編譯器,這類編譯器中用來從由高級語言生成的低級語言代碼重新生成高級語言代碼的又被叫做反編譯器。也有從一種高級語言生成另一種高級語言的編譯器,或者生成一種需要進一步處理的的中間代碼的編譯器(又叫級聯)。
典型的編譯器輸出是由包含入口點的名字和地址, 以及外部調用(到不在這個目標文件中的函數調用)的機器代碼所組成的目標文件。一組目標文件,不必是同一編譯器產生,但使用的編譯器必需採用同樣的輸出格式,可以鏈接在一起並生成可以由用戶直接執行的EXE,
所以我們電腦上的文件都是經過編譯後的文件。

J. 編譯器錯誤怎麼解決

閱讀全文

與編譯器逃逸分析相關的資料

熱點內容
如何登錄伺服器看源碼 瀏覽:522
如何做伺服器端 瀏覽:154
注冊伺服器地址指什麼 瀏覽:433
文本命令行 瀏覽:97
撲克牌睡眠解壓 瀏覽:192
rc4演算法流程圖 瀏覽:159
胡蘿卜解壓方法 瀏覽:35
掃描pdf格式軟體 瀏覽:876
程序員在銀行開賬戶 瀏覽:516
android資料庫下載 瀏覽:749
中午伺服器崩潰怎麼辦 瀏覽:425
產品經理和程序員待遇 瀏覽:442
解憂程序員免費閱讀 瀏覽:109
錄像免壓縮 瀏覽:508
總結所學過的簡便演算法 瀏覽:362
南昌哪些地方需要程序員 瀏覽:761
三台伺服器配置IP地址 瀏覽:175
如何用命令方塊連續對話 瀏覽:280
win7linux共享文件夾 瀏覽:305
命令符打開本地服務 瀏覽:601