導航:首頁 > 源碼編譯 > 編譯器內存管理

編譯器內存管理

發布時間:2022-08-30 17:37:21

1. 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{} 等類型是不可避免要用到的,為了減少不必要的逃逸,只能拿指針開刀了。

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

2. 有沒有java高手從編譯器和內存管理的角度解析一下java的向上轉型跟向下轉型

不是高手,談下我的淺見。要具體分為編譯時和運行時,舉個例子給你
List list = new ArrayList();
編譯器編譯的時候,編譯器只認為生成的是List類型的對象,編譯器時只認為list是一個指向List類型的引用;並不分配實際內存。
運行的時候,內存裡面分配一個list引用地址,分配一片內存區域來放置實際生成的ArrayList對象,所以此時可以完成『父類』list轉向子類ArrayList的 轉型;

3. ios arc中內存管理對象有哪些

iOS開發中,內存管理是從來都不能忽視的問題,OC採用的是動態內存管理方式,跟蹤每個對象被引用的次數,當對象引用次數為0時,則釋放對象佔用的內存。引用計數分為自動和手動計數(retain 引用、release釋放,autorelease 廢棄),在此我主要對自動引用計數做相關的分享。

自動引用計數
自動引用計數顧名思義是自動計數管理,是編譯器在編譯過程中自動添加retain、release來確保對象被釋放(註:arc 只能管理oc的對象,不能管理通過malloc申請的內存)並利用@autoreleasepool代替NSAutoreleasePool。

首先讓我們先了解下內存管理的思維方式:

自己生成的對象,自己持有

非自己生成的對象,自己持有

不再需要自己持有的對象時釋放

非自己持有的對象不能釋放

了解了思維方式,那麼怎麼去生成並持有對象呢,在OC中有多種方法族大家並不陌生,用於初始化並持有對象,分別是alloc/new//mutableCopy。另 init 方法族:以init 開頭的方法必須被定義為實例方法,它一定要返回id 類型或父類、子類的指針;其他族可以是類方法也可以是實例方法。另 所有權聲明 是通過 _ _strong(強引用,ARC中默認)、_ _weak(弱引用,常用於防止循環引用)、_ _unsafe_unretained(iOS 5下相當於weak)、_ _autoreleasing (自動釋放池所用,id/對象 另加 星 * 類型變數 默認)。

引用計數表,在OC 中採用hash表來管理引用計數表鍵值為內存塊地址;這樣對象內存塊就無需考慮頭部了,直接通過引用計數表的內存塊地址就可以找到對象內存塊。

ARC規則
在ARC中有一些規則必須遵守否則會警告甚至引起程序崩潰

1、不能使用retain/release/retainCount/autorelease

arc 中內存由編譯器控制,不必使用上述內存管理方法

2、不能使用NSAllocateObject/NSDeallocateObject

3、必須遵守內存管理方法命名規則 alloc/new//mutableCopy/init

4、不可顯示調用dealloc,不能使用NSZone

5、使用@autoreleasepool塊代替NSAutoreleasePool

6、對象型變數不能作為C語言結構體的成員

7、顯示轉換id 和void 如 id obj =[NSObject alloc] init]; void *p =(_ _bridge void *)obj

屬性
1、property 指一個對象的屬性或特性

2、@synthesize :自動生成getter、setter方法;@dynamic 告訴編譯器要自己手動實現 getter、setter

3、給屬性指定選項







註:默認為 atomic ,必須要用lock unlock 保證屬性的線程安全,如果不是頻繁的使用且不考慮多線程的話,盡量用noatomic

一些記錄點:
1、arc 的實現 是通過clang 編譯器 和objc 運行時庫結合進行內存管理

2、引用計數獲取方法: _objec_rootRetainCount(id obj)

3、strong 與 retain 在 block 下,strong相當於 ,retain 相當於 assign

4、_ _block 修飾相當於 指針拷貝 ,_ _weak 即為防循環引用

5、GC 垃圾回收機制 只支持 mac os

4. 用.net開發C++程序,.net編譯器會對C++進行內存管理管理做優化嗎資源會被自動回收嗎

如果使用了C++/CLI,在CLR環境下,如果創建了refrence對象的時候是會被自動垃圾回收的:

using namespace System;

ref class MyClass // managed class
{
public:
void foo(){}
}

int main()
{
MyClass^ handle = gcnew MyClass; // 在CLR heap上創建
// 你也可以手動delete: delete handle
}//自動回收

5. 求知道Java是如何進行內存管理和垃圾回收的

一部分是編譯器處理的,一部分是Java虛擬機實現的。如下:
通常Java用堆內存和棧內存來存放數據。
(heap)內存:由Java虛擬機的垃圾回收器來管理,可以動態地分配內存大小。new出來的對象總是存儲在堆內存中。
(stack)內存:由編譯器自動分配釋放,存取速度比堆內存快,但存儲在棧中的數據大小與生存期必須是確定的,缺乏靈活性。基礎數據類型 一般存儲在棧內存中。
關於Java的內存管理和垃圾回收機制,在秒秒學上可以看到的。

6. c++內存管理

char *a="asdfasdfasdf",你不能夠釋放a所佔的資源
關於什麼時候它的資源會釋放,要看它的作用域,如果它的作用域是局部的:比如數據塊中{},或函數中,那麼在跳出數據塊與函數後,它的內存就被釋放了,如果它的作用域為全局,或static,那麼知道程序結束後,它的內存就釋放了
int a = 10;同樣也不可以手動釋放它所佔的內存,關於它什麼時候被釋放,要看它的作用域,同上,要知道它存在哪裡,顯然它不會存在動態內存空間,那麼它只有存在於靜態內存空間與棧空間了,而究竟是靜態內存空間,還是棧空間也是由它的作用域決定,作用域為全局的就存在靜態內存空間中,作用域為局部的就存在棧內存空間中
如果你的意思是要知道a真正的地址,那麼就涉及到邏輯地址於物理地址的概念,設計到內存映射的概念
如果你感興趣需要多看點書

7. java編程內存管理需要注意的問題

大家在進行程序系統維護的時候是否因為java編程的內存管理問題而無法快速解決導致系統出錯呢?下面我們就一起來了解和學習一下,關於java編程內存管理都有哪些知識點。

程序計數器(了解)


程序計數器,可以看做是當前線程所執行的位元組碼的行號指示器。在虛擬機的概念模型里,位元組碼解釋器工作就是通過改變程序計數器的值來選擇下一條需要執行的位元組碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都要依賴這個計數器來完成。


Java虛擬機棧(了解)


Java虛擬機棧也是線程私有的,它的生命周期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法在執行的同時都會創建一個棧幀用於存儲局部變數表、操作數棧、動態鏈表、方法出口信息等。每一個方法從調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程。


局部變數表中存放了編譯器可知的各種基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用和returnAddress類型(指向了一條位元組碼指令的地址)。


如果擴展時無法申請到足夠的內存,就會拋出OutOfMemoryError異常。


本地方法棧(了解)


本地方法棧與虛擬機的作用相似,不同之處在於虛擬機棧為虛擬機執行的Java方法服務,而本地方法棧則為虛擬機使用到的Native方法服務。有的虛擬機直接把本地方法棧和虛擬機棧合二為一。


會拋出stackOverflowError和OutOfMemoryError異常。


Java堆


堆內存用來存放由new創建的對象實例和數組。(重點)


Java堆是所有線程共享的一塊內存區域,在虛擬機啟動時創建,此內存區域的目的就是存放對象實例。


Java堆是垃圾收集器管理的主要區域。java課程培訓機構http://www.kmbdqn.cn/發現由於現在收集器基本採用分代回收演算法,所以Java堆還可細分為:新生代和老年代。從內存分配的角度來看,線程共享的Java堆中可能劃分出多個線程私有的分配緩沖區(TLAB)。


8. java編程內存管理需要注意的問題

大家在進行程序系統維護的時候是否因為java編程的內存管理問題而無法快速解決導致系統出錯呢?下面我們就一起來了解和學習一下,關於java編程內存管理都有哪些知識點。

程序計數器(了解)


程序計數器,可以看做是當前線程所執行的位元組碼的行號指示器。在虛擬機的概念模型里,位元組碼解釋器工作就是通過改變程序計數器的值來選擇下一條需要執行的位元組碼指令,分支、循環、跳轉、異常處理、線程恢復等基礎功能都要依賴這個計數器來完成。


Java虛擬機棧(了解)


Java虛擬機棧也是線程私有的,它的生命周期與線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法在執行的同時都會創建一個棧幀用於存儲局部變數表、操作數棧、動態鏈表、方法出口信息等。每一個方法從調用直至執行完成的過程,就對應著一個棧幀在虛擬機棧中入棧到出棧的過程。


局部變數表中存放了編譯器可知的各種基本數據類型(boolean、byte、char、short、int、float、long、double)、對象引用和returnAddress類型(指向了一條位元組碼指令的地址)。


如果擴展時無法申請到足夠的內存,就會拋出OutOfMemoryError異常。


本地方法棧(了解)


本地方法棧與虛擬機的作用相似,不同之處在於虛擬機棧為虛擬機執行的Java方法服務,而本地方法棧則為虛擬機使用到的Native方法服務。有的虛擬機直接把本地方法棧和虛擬機棧合二為一。


會拋出stackOverflowError和OutOfMemoryError異常。


Java堆


堆內存用來存放由new創建的對象實例和數組。(重點)


Java堆是所有線程共享的一塊內存區域,在虛擬機啟動時創建,此內存區域的目的就是存放對象實例。


Java堆是垃圾收集器管理的主要區域。java課程培訓機構http://www.kmbdqn.com/發現由於現在收集器基本採用分代回收演算法,所以Java堆還可細分為:新生代和老年代。從內存分配的角度來看,線程共享的Java堆中可能劃分出多個線程私有的分配緩沖區(TLAB)。


9. c++中為什麼有些變數在編譯是就由編譯器分配了內存空間,還沒有運行怎麼會佔用內存呢

還沒有運行怎麼會佔用內存呢?!(這一點還要懷疑嗎!?)

所謂在編譯期間分配空間指的是靜態分配空間(相對於用new動態申請空間),如全局變數或靜態變數(包括一些復雜類型的常量),它們所需要的空間大小可以明確計算出來,並且不會再改變,因此它們可以直接存放在可執行文件的特定的節里(而且包含初始化的值),程序運行時也是直接將這個節載入到特定的段中,不必在程序運行期間用額外的代碼來產生這些變數。

其實在運行期間再看「變數」這個概念就不再具備編譯期間那麼多的屬性了(諸如名稱,類型,作用域,生存期等等),對應的只是一塊內存(只有首址和大小),所以在運行期間動態申請的空間,是需要額外的代碼維護,以確保不同變數不會混用內存。比如寫new表示有一塊內存已經被佔用了,其它變數就不能再用它了; 寫delete表示這塊內存自由了,可以被其它變數使用了。(通常我們都是通過變數來使用內存的,就編碼而言變數是給內存塊起了個名字,用以區分彼此)

內存申請和釋放時機很重要,過早會丟失數據,過遲會耗費內存。特定情況下編譯器可以幫我們完成這項復雜的工作(增加額外的代碼維護內存空間,實現申請和釋放)。從這個意義上講,局部自動變數也是由編譯器負責分配空間的。進一步講,內存管理用到了我們常常掛在嘴邊的堆和棧這兩種數據結構。

最後對於「編譯器分配空間」這種不嚴謹的說法,你可以理解成編譯期間它為你規劃好了這些變數的內存使用方案,這個方案寫到可執行文件裡面了(該文件中包含若干並非出自你大腦衍生的代碼),直到程序運行時才真正拿出來執行!

閱讀全文

與編譯器內存管理相關的資料

熱點內容
解除電腦加密文件夾 瀏覽:358
androidcheckbox組 瀏覽:546
linux在線安裝軟體 瀏覽:823
如何設置手機安卓版 瀏覽:285
簡歷pdfword 瀏覽:123
鋒雲視頻伺服器網關設置 瀏覽:162
linux伺服器如何查看網卡型號 瀏覽:142
加密相冊誤刪了怎麼恢復 瀏覽:380
安卓代練通怎麼下載 瀏覽:518
知道域名如何查詢伺服器 瀏覽:906
方舟手游怎麼才能進伺服器 瀏覽:289
抖音演算法自動爆音 瀏覽:24
linux修改網卡配置 瀏覽:913
雲伺服器和本地伺服器數據 瀏覽:843
在家如何創業python 瀏覽:225
編譯原理好課 瀏覽:717
python中實數的表示 瀏覽:372
php下載中文名文件 瀏覽:351
哪裡有專門注冊app實名的 瀏覽:274
魔爪mx穩定器app去哪裡下載 瀏覽:469