編寫過C語言程序的肯定知道通過malloc()方法動(dòng)態(tài)申請(qǐng)內(nèi)存,其中內(nèi)存分配器使用的是glibc提供的ptmalloc2。 除了glibc,業(yè)界比較出名的內(nèi)存分配器有Google的tcmalloc和Facebook的jemalloc。二者在避免內(nèi)存碎片和性能上均比glic有比較大的優(yōu)勢(shì),在多線程環(huán)境中效果更明顯。
槐蔭網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián),槐蔭網(wǎng)站設(shè)計(jì)制作,有大型網(wǎng)站制作公司豐富經(jīng)驗(yàn)。已為槐蔭成百上千提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\外貿(mào)營(yíng)銷網(wǎng)站建設(shè)要多少錢,請(qǐng)找那個(gè)售后服務(wù)好的槐蔭做網(wǎng)站的公司定做!
Golang中也實(shí)現(xiàn)了內(nèi)存分配器,原理與tcmalloc類似,簡(jiǎn)單的說就是維護(hù)一塊大的全局內(nèi)存,每個(gè)線程(Golang中為P)維護(hù)一塊小的私有內(nèi)存,私有內(nèi)存不足再從全局申請(qǐng)。另外,內(nèi)存分配與GC(垃圾回收)關(guān)系密切,所以了解GC前有必要了解內(nèi)存分配的原理。
為了方便自主管理內(nèi)存,做法便是先向系統(tǒng)申請(qǐng)一塊內(nèi)存,然后將內(nèi)存切割成小塊,通過一定的內(nèi)存分配算法管理內(nèi)存。 以64位系統(tǒng)為例,Golang程序啟動(dòng)時(shí)會(huì)向系統(tǒng)申請(qǐng)的內(nèi)存如下圖所示:
預(yù)申請(qǐng)的內(nèi)存劃分為spans、bitmap、arena三部分。其中arena即為所謂的堆區(qū),應(yīng)用中需要的內(nèi)存從這里分配。其中spans和bitmap是為了管理arena區(qū)而存在的。
arena的大小為512G,為了方便管理把a(bǔ)rena區(qū)域劃分成一個(gè)個(gè)的page,每個(gè)page為8KB,一共有512GB/8KB個(gè)頁;
spans區(qū)域存放span的指針,每個(gè)指針對(duì)應(yīng)一個(gè)page,所以span區(qū)域的大小為(512GB/8KB)乘以指針大小8byte = 512M
bitmap區(qū)域大小也是通過arena計(jì)算出來,不過主要用于GC。
span是用于管理arena頁的關(guān)鍵數(shù)據(jù)結(jié)構(gòu),每個(gè)span中包含1個(gè)或多個(gè)連續(xù)頁,為了滿足小對(duì)象分配,span中的一頁會(huì)劃分更小的粒度,而對(duì)于大對(duì)象比如超過頁大小,則通過多頁實(shí)現(xiàn)。
根據(jù)對(duì)象大小,劃分了一系列class,每個(gè)class都代表一個(gè)固定大小的對(duì)象,以及每個(gè)span的大小。如下表所示:
上表中每列含義如下:
class: class ID,每個(gè)span結(jié)構(gòu)中都有一個(gè)class ID, 表示該span可處理的對(duì)象類型
bytes/obj:該class代表對(duì)象的字節(jié)數(shù)
bytes/span:每個(gè)span占用堆的字節(jié)數(shù),也即頁數(shù)乘以頁大小
objects: 每個(gè)span可分配的對(duì)象個(gè)數(shù),也即(bytes/spans)/(bytes/obj)waste
bytes: 每個(gè)span產(chǎn)生的內(nèi)存碎片,也即(bytes/spans)%(bytes/obj)上表可見最大的對(duì)象是32K大小,超過32K大小的由特殊的class表示,該class ID為0,每個(gè)class只包含一個(gè)對(duì)象。
span是內(nèi)存管理的基本單位,每個(gè)span用于管理特定的class對(duì)象, 跟據(jù)對(duì)象大小,span將一個(gè)或多個(gè)頁拆分成多個(gè)塊進(jìn)行管理。src/runtime/mheap.go:mspan定義了其數(shù)據(jù)結(jié)構(gòu):
以class 10為例,span和管理的內(nèi)存如下圖所示:
spanclass為10,參照class表可得出npages=1,nelems=56,elemsize為144。其中startAddr是在span初始化時(shí)就指定了某個(gè)頁的地址。allocBits指向一個(gè)位圖,每位代表一個(gè)塊是否被分配,本例中有兩個(gè)塊已經(jīng)被分配,其allocCount也為2。next和prev用于將多個(gè)span鏈接起來,這有利于管理多個(gè)span,接下來會(huì)進(jìn)行說明。
有了管理內(nèi)存的基本單位span,還要有個(gè)數(shù)據(jù)結(jié)構(gòu)來管理span,這個(gè)數(shù)據(jù)結(jié)構(gòu)叫mcentral,各線程需要內(nèi)存時(shí)從mcentral管理的span中申請(qǐng)內(nèi)存,為了避免多線程申請(qǐng)內(nèi)存時(shí)不斷的加鎖,Golang為每個(gè)線程分配了span的緩存,這個(gè)緩存即是cache。src/runtime/mcache.go:mcache定義了cache的數(shù)據(jù)結(jié)構(gòu)
alloc為mspan的指針數(shù)組,數(shù)組大小為class總數(shù)的2倍。數(shù)組中每個(gè)元素代表了一種class類型的span列表,每種class類型都有兩組span列表,第一組列表中所表示的對(duì)象中包含了指針,第二組列表中所表示的對(duì)象不含有指針,這么做是為了提高GC掃描性能,對(duì)于不包含指針的span列表,沒必要去掃描。根據(jù)對(duì)象是否包含指針,將對(duì)象分為noscan和scan兩類,其中noscan代表沒有指針,而scan則代表有指針,需要GC進(jìn)行掃描。mcache和span的對(duì)應(yīng)關(guān)系如下圖所示:
mchache在初始化時(shí)是沒有任何span的,在使用過程中會(huì)動(dòng)態(tài)的從central中獲取并緩存下來,跟據(jù)使用情況,每種class的span個(gè)數(shù)也不相同。上圖所示,class 0的span數(shù)比class1的要多,說明本線程中分配的小對(duì)象要多一些。
cache作為線程的私有資源為單個(gè)線程服務(wù),而central則是全局資源,為多個(gè)線程服務(wù),當(dāng)某個(gè)線程內(nèi)存不足時(shí)會(huì)向central申請(qǐng),當(dāng)某個(gè)線程釋放內(nèi)存時(shí)又會(huì)回收進(jìn)central。src/runtime/mcentral.go:mcentral定義了central數(shù)據(jù)結(jié)構(gòu):
lock: 線程間互斥鎖,防止多線程讀寫沖突
spanclass : 每個(gè)mcentral管理著一組有相同class的span列表
nonempty: 指還有內(nèi)存可用的span列表
empty: 指沒有內(nèi)存可用的span列表
nmalloc: 指累計(jì)分配的對(duì)象個(gè)數(shù)線程從central獲取span步驟如下:
將span歸還步驟如下:
從mcentral數(shù)據(jù)結(jié)構(gòu)可見,每個(gè)mcentral對(duì)象只管理特定的class規(guī)格的span。事實(shí)上每種class都會(huì)對(duì)應(yīng)一個(gè)mcentral,這個(gè)mcentral的集合存放于mheap數(shù)據(jù)結(jié)構(gòu)中。src/runtime/mheap.go:mheap定義了heap的數(shù)據(jù)結(jié)構(gòu):
lock: 互斥鎖
spans: 指向spans區(qū)域,用于映射span和page的關(guān)系
bitmap:bitmap的起始地址
arena_start: arena區(qū)域首地址
arena_used: 當(dāng)前arena已使用區(qū)域的最大地址
central: 每種class對(duì)應(yīng)的兩個(gè)mcentral
從數(shù)據(jù)結(jié)構(gòu)可見,mheap管理著全部的內(nèi)存,事實(shí)上Golang就是通過一個(gè)mheap類型的全局變量進(jìn)行內(nèi)存管理的。mheap內(nèi)存管理示意圖如下:
系統(tǒng)預(yù)分配的內(nèi)存分為spans、bitmap、arean三個(gè)區(qū)域,通過mheap管理起來。接下來看內(nèi)存分配過程。
針對(duì)待分配對(duì)象的大小不同有不同的分配邏輯:
(0, 16B) 且不包含指針的對(duì)象: Tiny分配
(0, 16B) 包含指針的對(duì)象:正常分配
[16B, 32KB] : 正常分配
(32KB, -) : 大對(duì)象分配其中Tiny分配和大對(duì)象分配都屬于內(nèi)存管理的優(yōu)化范疇,這里暫時(shí)僅關(guān)注一般的分配方法。
以申請(qǐng)size為n的內(nèi)存為例,分配步驟如下:
Golang內(nèi)存分配是個(gè)相當(dāng)復(fù)雜的過程,其中還摻雜了GC的處理,這里僅僅對(duì)其關(guān)鍵數(shù)據(jù)結(jié)構(gòu)進(jìn)行了說明,了解其原理而又不至于深陷實(shí)現(xiàn)細(xì)節(jié)。1、Golang程序啟動(dòng)時(shí)申請(qǐng)一大塊內(nèi)存并劃分成spans、bitmap、arena區(qū)域
2、arena區(qū)域按頁劃分成一個(gè)個(gè)小塊。
3、span管理一個(gè)或多個(gè)頁。
4、mcentral管理多個(gè)span供線程申請(qǐng)使用
5、mcache作為線程私有資源,資源來源于mcentral。
gojvm目錄
1、搭建go環(huán)境
2、cmd命令行參數(shù)解析
3、搜索class文件
4、添加testOption 便于單元測(cè)試
5、解析classfile文件
6、運(yùn)行時(shí)數(shù)據(jù)區(qū)
7、指令集
8、解釋器
9、創(chuàng)建Class
10、類加載器
11、對(duì)象實(shí)例化new object
12、方法調(diào)用和返回
13 類初始化
14、jvm支持?jǐn)?shù)組
15、jvm支持字符串-數(shù)組擴(kuò)展
16、本地方法調(diào)用
17、ClassLoader原理
18、異常處理
19、 啟動(dòng)jvm
java jvm根據(jù)類路徑(class path)來搜索類,加載到內(nèi)存
可以通過 -Xbootclasspath 修改啟動(dòng)類路徑
參數(shù) -classpath /-cp
go語言不需要顯式實(shí)現(xiàn)接口
defer 確保異常及時(shí)處理
1、Entry搜索類路徑
2、DirEntry 搜索目錄下類路徑
3、ZipEntry 搜索zip或jar文件形式類路徑
4、CompositeEntry 組合類路徑
5、WildcardEntry 所有通配符下類路徑
Entry 類路徑查找
測(cè)試類
提交標(biāo)簽classpath
1. 保留但大幅度簡(jiǎn)化指針
Go語言保留著C中值和指針的區(qū)別,但是對(duì)于指針繁瑣用法進(jìn)行了大量的簡(jiǎn)化,引入引用的概念。所以在Go語言中,你幾乎不用擔(dān)心會(huì)因?yàn)橹苯硬僮鲀?nèi)寸而引起各式各樣的錯(cuò)誤。
2. 多參數(shù)返回
還記得在C里面為了回饋多個(gè)參數(shù),不得不開辟幾段指針傳到目標(biāo)函數(shù)中讓其操作么?在Go里面這是完全不必要的。而且多參數(shù)的支持讓Go無需使用繁瑣的exceptions體系,一個(gè)函數(shù)可以返回期待的返回值加上error,調(diào)用函數(shù)后立刻處理錯(cuò)誤信息,清晰明了。
3. Array,slice,map等內(nèi)置基本數(shù)據(jù)結(jié)構(gòu)
如果你習(xí)慣了Python中簡(jiǎn)潔的list和dict操作,在Go語言中,你不會(huì)感到孤單。一切都是那么熟悉,而且更加高效。如果你是C++程序員,你會(huì)發(fā)現(xiàn)你又找到了STL的vector 和 map這對(duì)朋友。
4. Interface
Go語言最讓人贊嘆不易的特性,就是interface的設(shè)計(jì)。任何數(shù)據(jù)結(jié)構(gòu),只要實(shí)現(xiàn)了interface所定義的函數(shù),自動(dòng)就implement了這個(gè)interface,沒有像Java那樣冗長(zhǎng)的class申明,提供了靈活太多的設(shè)計(jì)度和OO抽象度,讓你的代碼也非常干凈。千萬不要以為你習(xí)慣了Java那種一條一條加implements的方式,感覺還行,等接口的設(shè)計(jì)越來越復(fù)雜的時(shí)候,無數(shù)Bug正在后面等著你。
同時(shí),正因?yàn)槿绱耍珿o語言的interface可以用來表示任何generic的東西,比如一個(gè)空的interface,可以是string可以是int,可以是任何數(shù)據(jù)類型,因?yàn)檫@些數(shù)據(jù)類型都不需要實(shí)現(xiàn)任何函數(shù),自然就滿足空interface的定義了。加上Go語言的type assertion,可以提供一般動(dòng)態(tài)語言才有的duck typing特性, 而仍然能在compile中捕捉明顯的錯(cuò)誤。
5. OO
Go語言本質(zhì)上不是面向?qū)ο笳Z言,它還是過程化的。但是,在Go語言中, 你可以很輕易的做大部分你在別的OO語言中能做的事,用更簡(jiǎn)單清晰的邏輯。是的,在這里,不需要class,仍然可以繼承,仍然可以多態(tài),但是速度卻快得多。因?yàn)楸举|(zhì)上,OO在Go語言中,就是普通的struct操作。
6. Goroutine
這個(gè)幾乎算是Go語言的招牌特性之一了,我也不想多提。如果你完全不了解Goroutine,那么你只需要知道,這玩意是超級(jí)輕量級(jí)的類似線程的東西,但通過它,你不需要復(fù)雜的線程操作鎖操作,不需要care調(diào)度,就能玩轉(zhuǎn)基本的并行程序。在Go語言里,觸發(fā)一個(gè)routine和erlang spawn一樣簡(jiǎn)單?;旧弦莆誈o語言,以Goroutine和channel為核心的內(nèi)存模型是必須要懂的。不過請(qǐng)放心,真的非常簡(jiǎn)單。
7. 更多現(xiàn)代的特性
和C比較,Go語言完全就是一門現(xiàn)代化語言,原生支持的Unicode, garbage collection, Closures(是的,和functional programming language類似), function是first class object,等等等等。
看到這里,你可能會(huì)發(fā)現(xiàn),我用了很多輕易,簡(jiǎn)單,快速之類的形容詞來形容Go語言的特點(diǎn)。我想說的是,一點(diǎn)都不夸張,連Go語言的入門學(xué)習(xí)到提高,都比別的語言門檻低太多太多。在大部分人都有C的背景的時(shí)代,對(duì)于Go語言,從入門到能夠上手做項(xiàng)目,最多不過半個(gè)月。Go語言給人的感覺就是太直接了,什么都直接,讀源代碼直接,寫自己的代碼也直接。