前 言?
從實(shí)踐中看,Golang(以下簡稱Go)應(yīng)用程序比Java占用更少的內(nèi)存肴楷,這與它們的運(yùn)行時(shí)環(huán)境有關(guān)珍坊,其運(yùn)行時(shí)自帶了內(nèi)存動態(tài)分配和自動垃圾回收的管理機(jī)制种蘸,本文通過分析Go與Java在內(nèi)存管理機(jī)制上的差異渗钉,以期對兩者在運(yùn)行時(shí)內(nèi)存方面有更進(jìn)一步的認(rèn)識鳄橘。本文以Go(1.12)和當(dāng)前使用較多的JDK8 HotSpot VM為例進(jìn)行說明瘫怜。
本篇文章包含以下內(nèi)容:
介紹Go與Java的運(yùn)行時(shí)內(nèi)存結(jié)構(gòu)差異
介紹Go與Java的內(nèi)存資源占用差異
介紹Go與Java如何為對象分配內(nèi)存
介紹Go與Java的內(nèi)存回收策略差異
內(nèi)存結(jié)構(gòu)差異
應(yīng)用程序要能在linux系統(tǒng)上運(yùn)行(其他平臺類似),其可執(zhí)行文件要求符合ELF規(guī)范(Executable and Linkable Format,可執(zhí)行和可鏈接格式)暗挑。操作系統(tǒng)加載目標(biāo)可執(zhí)行文件到內(nèi)存中并以獨(dú)立進(jìn)程方式運(yùn)行程序炸裆。
操作系統(tǒng)為每個進(jìn)程分配一個連續(xù)的虛擬內(nèi)存地址空間烹看,并將該進(jìn)程內(nèi)存空間劃分成多個不同用途的邏輯區(qū)域听系。JVM進(jìn)程以及Go進(jìn)程的內(nèi)存結(jié)構(gòu)如下圖所示:
圖1
Java用戶程序是運(yùn)行在Java虛擬機(jī)(以下簡稱“JVM”)之上的,從上圖我們看到用戶程序的字節(jié)碼指令并沒有存儲到JVM進(jìn)程的堆空間或者text段中毕源。實(shí)際上霎褐,虛擬機(jī)將用戶程序字節(jié)碼放在了使用本地直接內(nèi)存實(shí)現(xiàn)的方法區(qū)中冻璃,并不占用虛擬機(jī)的堆內(nèi)存省艳。
JVM的堆空間保存Java用戶程序運(yùn)行時(shí)創(chuàng)建的對象和字符串常量池等數(shù)據(jù)赖晶,椂舨澹空間則為Java線程提供了私有的內(nèi)存區(qū)域胳嘲。在HotSpot VM的實(shí)現(xiàn)中胎围,Java線程棧使用操作系統(tǒng)棧和線程模型表示白魂,且Java方法與本地方法共享同一個棧區(qū)福荸。因此虛擬機(jī)棧與本地方法棧其實(shí)是同一個區(qū)域敬锐。
Go與Java有較大不同台夺,Go進(jìn)程空間的text段不但保存了內(nèi)置的運(yùn)行時(shí)機(jī)器指令,而且還有用戶程序的機(jī)器指令(Go在編譯時(shí)就已確定)。堆內(nèi)存區(qū)則為用戶程序創(chuàng)建對象提供了存儲空間冤灾。Go天然支持并發(fā)編程模型韵吨,采用了系統(tǒng)線程與用戶線程(goroutine)相結(jié)合的實(shí)現(xiàn)機(jī)制归粉,進(jìn)程椪到剑空間為系統(tǒng)線程提供了棧內(nèi)存绢掰,而用戶線程棧的內(nèi)存默認(rèn)從堆中分配。
Java內(nèi)存結(jié)構(gòu)?
?1. 運(yùn)行時(shí)內(nèi)存?
Java程序的運(yùn)行時(shí)內(nèi)存被劃分為元數(shù)據(jù)區(qū)(方法區(qū))顾复、堆萧芙、虛擬機(jī)棧双揪、本地方法棧渔期、程序計(jì)數(shù)器5個部分疯趟,這可以看作是JVM對進(jìn)程可用堆信峻、楉镂瑁空間進(jìn)行的二次分配矾策,以滿足運(yùn)行Java用戶程序的內(nèi)存需求贾虽。其內(nèi)存結(jié)構(gòu)如下圖所示:
圖2
上圖中堆內(nèi)存對應(yīng)了圖1中JVM的堆內(nèi)存,虛擬機(jī)棧地粪、本地方法棧蟆技、程序計(jì)數(shù)器則對應(yīng)了圖1中JVM的棧區(qū)质礼,元數(shù)據(jù)區(qū)則是JVM另外開辟的內(nèi)存塊眶蕉。
·?元數(shù)據(jù)區(qū)是JVM向操作系統(tǒng)申請的堆外內(nèi)存造挽,用于實(shí)現(xiàn)“方法區(qū)”饭入,主要存儲虛擬機(jī)加載class的類信息、JIT編譯的代碼毁欣、運(yùn)行時(shí)常量池等數(shù)據(jù)饭耳,其默認(rèn)大小由系統(tǒng)的可用物理內(nèi)存上限限制寞肖。
·?堆內(nèi)存是被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動時(shí)創(chuàng)建觅赊。它存放了包括幾乎所有的Java對象以及數(shù)組吮螺,這也是垃圾收集器關(guān)注的主要內(nèi)存區(qū)。由于逃逸分析等優(yōu)化技術(shù)嘀掸,對象也有可能被分配到棧上泉蝌。
·?虛擬機(jī)棧即我們常說的椑嬗耄空間粥鞋,其生命周期與線程相同呻粹。線程的椀茸牵空間存儲了方法調(diào)用的棧幀筹燕,每個棧幀則存儲局部變量表撒踪、操作數(shù)棧制妄、動態(tài)鏈接耕捞、方法出口等信息。
·?本地方法棧為JVM使用到的Native方法提供內(nèi)存空間敞映,而虛擬機(jī)棧為JVM執(zhí)行Java方法提供內(nèi)存空間振愿。HotSpot將虛擬機(jī)棧與本地方法棧合并管理。
·?程序計(jì)數(shù)器是一塊較小的內(nèi)存空間孩哑,用來指向當(dāng)前線程需要執(zhí)行的下一條字節(jié)碼指令横蜒。
元數(shù)據(jù)區(qū)丛晌、堆內(nèi)存為所有線程共享澎蛛,多線程訪問時(shí)需要進(jìn)行同步控制谋逻。虛擬機(jī)棧毁兆、本地方法棧气堕、程序計(jì)數(shù)器都是線程私有的內(nèi)存空間(堆的TLAB空間也是線程私有的空間)茎芭,訪問時(shí)無需加鎖骗爆,速度很快煮寡。
?2.? 分代的堆內(nèi)存?
Java用戶程序創(chuàng)建的對象主要存放在Java堆內(nèi)存中幸撕,操作非常頻繁坐儿,而且大部分對象的生命周期都很短暫貌矿。根據(jù)對象存活的特點(diǎn)逛漫,Java堆空間進(jìn)一步劃分為新生代和老年代酌毡,其中新生代又可以細(xì)分為eden區(qū)和兩塊相等大小的survivor區(qū)枷踏。JVM根據(jù)對象根據(jù)存活周期將其存放在不同的內(nèi)存代中旭蠕,不同的內(nèi)存代可以采用不同的垃圾收集器回收內(nèi)存下梢。如下圖所示:
圖3
·?新生代將內(nèi)存劃分為eden空間和兩塊較小的survivor空間孽江,每次使用eden和其中一塊survivor岗屏。當(dāng)回收時(shí),將eden和survivor中還存活著的對象一次性地復(fù)制到另外一塊survivor空間上暇屋,最后清理掉eden和剛才用過的survivor空間咐刨,這為新生代“標(biāo)記-復(fù)制”回收內(nèi)存提供算法實(shí)現(xiàn)便捷性定鸟。eden區(qū)與survivor區(qū)的大小比例是8:1:1。
·?老年代一般存放存活周期較長的對象以及大對象。老年代采用“標(biāo)記-清除”或“標(biāo)記-整理”算法回收內(nèi)存季眷。
·?新建對象總是優(yōu)先在新生代的eden區(qū)中分配子刮,“熬過”多次GC的對象可以從新生代晉升到老年代话告。
Go內(nèi)存結(jié)構(gòu)
?1.? 運(yùn)行時(shí)內(nèi)存?
Go的運(yùn)行時(shí)內(nèi)存就是操作系統(tǒng)分配給進(jìn)程空間的堆、棧內(nèi)存裳朋,在堆內(nèi)存的使用上不像JVM分代管理鲤嫡,而是采用分層級的內(nèi)存管理模式。
?2.? 分層級的堆內(nèi)存?
Go的堆內(nèi)存直接采用了TCMalloc庫的內(nèi)存管理模型纺裁。TCMalloc庫是Google開發(fā)的現(xiàn)代內(nèi)存分配器栋豫,其基本特征是內(nèi)存分層級谚殊、對抗內(nèi)存碎片以及快速分配等丛肢。Go語言根據(jù)自身需求對TCMalloc做了很多優(yōu)化摔踱,但仍保留了其基本架構(gòu)蛹批。
Go的內(nèi)存分配器是分層級的腐芍,由mcache/mcentral/mheap 三個組件構(gòu)成猪勇。因此整個堆內(nèi)存結(jié)構(gòu)可以看成是三層級的內(nèi)存模型,其結(jié)構(gòu)如下圖所示:
圖4
·?緩存組件mcache與工作線程(goroutine)綁定椅您,是goroutine私有的內(nèi)存空間。在mcache中為對象分配內(nèi)存時(shí)员舵,無需競爭马僻,性能很高。
·?中間組件mcentral 只負(fù)責(zé)一種規(guī)格(size class)的內(nèi)存塊仍秤,為mcache緩存組件提供備用的特定規(guī)格的可用空間。mcache的內(nèi)存擴(kuò)容請求會被分散到不同的mcentral 組件上可很,以減小共享內(nèi)存的競爭鎖粒度诗力。
·?堆組件mheap負(fù)責(zé)管理用戶程序的所有可用堆內(nèi)存空間以及為大對象直接分配內(nèi)存。它為上層組件提供擴(kuò)容支持。當(dāng)空間不足時(shí)苇本,mheap組件向操作系統(tǒng)申請內(nèi)存袜茧。
中間組件mcentral、堆組件mheap為所有工作線程(goroutine)所共享瓣窄,所以在內(nèi)存分配時(shí)通常存在同步競爭的情況笛厦。
“多出來”的方法區(qū)
Go程序指令被操作系統(tǒng)加載到內(nèi)存并保存在text段中俺夕,其大小在運(yùn)行時(shí)基本是確定的。
而JVM的text段存儲的是虛擬機(jī)本身的指令數(shù)據(jù)件甥,Java用戶程序的字節(jié)碼被加載并存儲在堆外內(nèi)存的元數(shù)據(jù)區(qū)(方法區(qū))中。Java字節(jié)碼的加載過程如下圖所示:
圖5
·?在程序啟動导帝、運(yùn)行期間虐秦,JVM中的類裝載器子系統(tǒng)按需動態(tài)加載類文件(包括Java API基礎(chǔ)類庫、用戶程序class文件暮现、第三方依賴庫等)塘幅、以及由字節(jié)碼框架動態(tài)生成的類信息呼伸,這些數(shù)據(jù)經(jīng)加載铃辖、驗(yàn)證穴翩、準(zhǔn)備背蟆、解析伦糯、初始化等一系列過程最終都會保存在方法區(qū)中惭等。
·?程序運(yùn)行時(shí)秤茅,執(zhí)行引擎中的解釋器解釋執(zhí)行方法區(qū)中的字節(jié)碼指令∏说混合模式下的JIT編譯器將探測到的熱點(diǎn)代碼編譯為本地可執(zhí)行的機(jī)器指令达罗,編譯的機(jī)器指令也保存在方法區(qū)中。
隨著程序不斷運(yùn)行膨蛮,方法區(qū)所占的內(nèi)存空間可能會越來越大持偏,存儲的數(shù)據(jù)有時(shí)候能達(dá)到數(shù)百兆;而對該內(nèi)存區(qū)域中類型卸載的條件又比較苛刻附帽,內(nèi)存回收效率并不如堆內(nèi)存理想。
這“多出來”的用于存儲Java用戶程序字節(jié)碼指令的區(qū)域是Java程序比Go消耗更多內(nèi)存的一個重要因素抽莱。
對象內(nèi)存分配差異
不同的內(nèi)存結(jié)構(gòu)決定了對象內(nèi)存分配方式的差異偶惠,也會給垃圾回收帶來影響瘾腰。Go以及Java都支持變量的逃逸分析析藕,逃逸到棧上的對象會隨著方法退出而自然回收账胧,而分配到堆內(nèi)存的對象則需要垃圾收集器回收才能釋放出內(nèi)存空間。因而我們更多關(guān)注對象在堆上分配空間的過程先紫。
? Java對象??
JVM會為新創(chuàng)建的線程在stack棧區(qū)分配一塊私有的線程椫文啵空間。某一個線程中創(chuàng)建的Java對象可能被分配在新生代遮精,也可能分配在老年代居夹,其具體的分配方法如下圖所示:
圖6
JVM執(zhí)行線程的創(chuàng)建對象指令,會向堆內(nèi)存申請對象空間本冲。堆內(nèi)存被所有線程共享意狠,在為對象分配空間時(shí)需要同步鎖定,這會降低內(nèi)存分配的效率顶瞒。用戶可以設(shè)置-XX: +UseTLAB參數(shù)啟用TLAB功能,這樣JVM總是優(yōu)先嘗試在當(dāng)前線程的TALB空間為對象分配內(nèi)存。TLAB(Thread-Local Allocation Buffer,線程本地分配緩沖區(qū))是JVM預(yù)先為每一個線程在堆區(qū)中劃分的一小塊私有內(nèi)存空間常柄,線程分配的對象空間總是在自己的TLAB上分配,無需加鎖镀岛。如果TLAB內(nèi)存用完了則重新申請一塊新的TLAB匆赃。如果在TLAB中分配失敗猪杭,則會嘗試在新生代中繼續(xù)分配操作麻裳。一般過程如下所示:
1)首先檢查該類是否已加載、解析、初始化拢肆。如果沒有鄙才,則執(zhí)行類加載的過程叙甸。
2)分配對象內(nèi)存時(shí)僚祷,檢查是否啟用-XX: +UseTLAB參數(shù)。如果啟用TLAB,則直接從當(dāng)前線程的TLAB空間以lock free方式分配指定大小的內(nèi)存塊,速度很快憨愉。
■? 如果TLAB剩余空間大于其可浪費(fèi)空間閾值径密,則直接在新生代中分配。
■? 否則躺孝,JVM會嘗試為當(dāng)前線程重新開辟一塊TLAB空間享扔。
3)如果未啟用UseTLAB或者TLAB分配失敗,JVM將繼續(xù)在eden區(qū)或老年代上為對象分配空間植袍,這時(shí)需要做同步操作惧眠。
4)對象內(nèi)存分配成功后,JVM初始化對象零值于个、設(shè)置對象頭等元信息氛魁,執(zhí)行對象初始化方法,最后將對象引用壓入線程的棧內(nèi)存中。
新建對象總是分配在新生代的eden區(qū)秀存,當(dāng)空間不夠時(shí)捶码,會存放在老年代中。如果剩余空間還是不夠或链,JVM會申請擴(kuò)容或觸發(fā)一次GC回收內(nèi)存后繼續(xù)在各個內(nèi)存代中嘗試分配惫恼。
Java大對象的分配過程稍有不同,JVM總是直接在老年代中為其分配存儲空間澳盐。我們可以通過-XX:PretenureSizeThreshold參數(shù)來設(shè)定大對象的閾值祈纯,該參數(shù)默認(rèn)值為0,說明對象總是先在eden區(qū)分配洞就,不管這個對象有多大盆繁。
?Go對象??
Go內(nèi)存分配器管理span以及object兩種類型的內(nèi)存塊掀淘。span是Go內(nèi)存管理的基本單元旬蟋,由多個地址連續(xù)的頁(8k大小的page內(nèi)存塊)組成的?塊內(nèi)存。object則是將span按照特定規(guī)格(size class)切分成的多個?塊革娄,每個?塊都可以用來存儲?個對象倾贰。
Go的object內(nèi)存塊大小為8字節(jié)的整倍數(shù),被劃分為67種規(guī)格拦惋。Go對象的內(nèi)存分配就是從有限的67種規(guī)格中找出與對象大小最合適的一塊可用內(nèi)存塊的過程匆浙。Go使用“空閑列表”方式管理可分配的內(nèi)存空間,相同規(guī)格的內(nèi)存塊連接成一個雙向鏈表厕妖。以下是Go object的size class對應(yīng)表首尼,其中包含66種規(guī)格,另外一種規(guī)格是大于32KB的大對象:
根據(jù)不同對象的規(guī)格大小,Go內(nèi)存分配器有不同的內(nèi)存分配邏輯言秸。比如零長度對象由于沒有可讀寫內(nèi)容软能,在分配時(shí)不同類型可能指向同一位置,如struct{}與[0]int举畸。內(nèi)存分配器會將小于16字節(jié)且不包含指針(noscan)的微小對象組合起來查排,并嘗試用單個object內(nèi)存塊存儲以減少內(nèi)存浪費(fèi)。32KB以內(nèi)大小的小對象使用mcache組件進(jìn)行分配抄沮,大于32KB的大對象直接在mheap組件管理的堆上分配跋核。
用戶程序中創(chuàng)建的對象大部分是小對象,這也是內(nèi)存分配器的重心所在叛买,小對象分配在每個goroutine mcache中避免了競爭鎖提升了分配效率砂代。如前所述,Go內(nèi)存分配器采用分層級組件的方式來管理應(yīng)用的堆內(nèi)存率挣,為小對象分配內(nèi)存需要多級組件相互協(xié)作完成刻伊。分配過程如下圖所示:
圖7
·?mcache緩存是goroutine的私有內(nèi)存空間,直接為當(dāng)前goroutine無鎖分配小對象的內(nèi)存塊,速度很快娃圆。內(nèi)存分配器首先根據(jù)對象大小獲取mcache中對應(yīng)size class的span鏈表玫锋,并從表頭span中提取object塊進(jìn)行分配。
·?如果分配器發(fā)現(xiàn)mcache下沒有對應(yīng)規(guī)格的可用span資源讼呢,則會嘗試從堆區(qū)相應(yīng)class的mcentral區(qū)域中申請擴(kuò)容(mcentral是所有線程共享撩鹿,在為mcache擴(kuò)容時(shí)總是會先lock)。分配器將申請到的span資源鏈接到mcache鏈表悦屏,繼續(xù)為對象分配object塊空間节沦。
·?如果mcentral中沒有找到可用的內(nèi)存塊,分配器會向mheap申請擴(kuò)容础爬,擴(kuò)容成功后繼續(xù)為對象分配內(nèi)存甫贯。
對于大對象,Go的內(nèi)存分配器直接在mheap分配內(nèi)存看蚜,如果沒有找到合適的span內(nèi)存塊叫搁,分配器將向操作系統(tǒng)申請擴(kuò)容后繼續(xù)分配。提取的span內(nèi)存塊如果超過了對象規(guī)格所需的頁數(shù)供炎,分配器將嘗試分割該span合適大小分配給對象渴逻,并合并剩余的空間歸還給mheap管理,以減少堆內(nèi)存碎片音诫。
由上可知惨奕,小對象內(nèi)存分配,Go mcache方式與Java TLAB方式相似竭钝,都是從堆內(nèi)存中為線程劃分私有空間以便進(jìn)行快速分配梨撞,但是仍然會有所差異:
垃圾收集差異
內(nèi)存結(jié)構(gòu)劃分、對象分配方式與垃圾收集策略密切相關(guān)香罐,而且自動GC很大程度上會成為影響系統(tǒng)性能的瓶頸卧波。Go與Java的GC策略是判斷對象是否存活,并對其進(jìn)行標(biāo)記穴吹,或采用“復(fù)制”算法幽勒、“清除”算法、“整理”算法等完成不可引用對象的內(nèi)存回收操作港令。目前垃圾收集過程中總是會遇到STW(stop the world)的問題啥容。
Java GC
·垃圾收集器一覽
JVM的垃圾收集是分代的收集。比如新生代采用“標(biāo)記-復(fù)制”算法進(jìn)行回收顷霹,老年代通常使用“標(biāo)記-清理”或“標(biāo)記-整理”算法咪惠。其中在G1收集器下,內(nèi)存的劃分又稍有不同了淋淀。
以下是JVM中主要的垃圾收集器實(shí)現(xiàn)遥昧,其中JDK8默認(rèn)GC組合是Parallel Scavenge(新生代)和Parallel Old(老年代):
?· GC觸發(fā)時(shí)機(jī)一覽
JVM的GC策略根據(jù)內(nèi)存分代可以分為Minor GC(新生代垃圾收集)和Full GC(Major GC,老年代垃圾收集)兩類。它們的觸發(fā)時(shí)機(jī)一般如下表所示:
?Go GC?
Go只有一種垃圾收集器,其基于優(yōu)化改進(jìn)的“標(biāo)記-清除”算法炭臭,特征為“非分代永脓、非緊縮、寫屏障鞋仍、三色標(biāo)記常摧、并發(fā)標(biāo)記清理”。Go的“非分代”內(nèi)存管理使得Go并不需要實(shí)現(xiàn)多種GC算法策略威创,“非緊縮”的特征使得回收的內(nèi)存塊非常容易的復(fù)用,較少產(chǎn)生內(nèi)存碎片肚豺,基本上不需要壓縮整理溃斋。Go的垃圾收集器與JVM中的CMS垃圾收集器原理上是非常相似的隐岛。
·? GC中的STW問題
垃圾收集器在回收對象內(nèi)存的過程中,總是需要掛起所有的用戶線程(即STW,stop the world),以避免GC線程在回收時(shí)對象的引用關(guān)系還在不斷變化導(dǎo)致回收結(jié)果不準(zhǔn)確旗们。但是STW可能會因GC時(shí)間過長而使得用戶線程長時(shí)間的停頓锹锰,這對追求響應(yīng)速度的程序來說將是令人難以接收的恃慧。Go GC的目標(biāo)就是盡量減小STW的時(shí)間园蝠,以使得程序能夠獲取最大限度的響應(yīng)速度。
Go GC線程與用戶線程是并發(fā)的痢士,其過程可以分為如下四個階段:
■ Sweep termination:清理掉意外遺留的span內(nèi)存塊彪薛,只有上一次的GC清除工作完成了才能開始下一次GC。
■?Mark:
1)初始標(biāo)記怠蹂,需要STW善延。準(zhǔn)備GC Roots對象的掃描、開啟寫屏障等城侧。
2)并發(fā)標(biāo)記易遣,GC Roots到所有的對象的可達(dá)性分析,采用三色標(biāo)記法嫌佑。
■?Mark termination:
重新標(biāo)記豆茫,需要STW。重新掃描部分GC Roots對象屋摇,修正并發(fā)標(biāo)記期間因用戶線程繼續(xù)運(yùn)行而導(dǎo)致標(biāo)記產(chǎn)生變動的那一部分對象的標(biāo)記記錄揩魂。
■?Sweep:
根據(jù)標(biāo)記結(jié)果并發(fā)清除,回收內(nèi)存炮温。
可見火脉,在初始標(biāo)記以及重新標(biāo)記期間仍然存在STW問題。Go GC雖然沒有完全消除STW柒啤,但在整個GC回收周期中倦挂,已將STW局限在有限的2個階段,這讓程序?qū)崟r(shí)性有了很大改善白修。
·GC觸發(fā)時(shí)機(jī)一覽
Go的堆內(nèi)存沒有分代妒峦,每次GC時(shí)都要回收整個堆中的對象。
上文我們只對Go與Java在內(nèi)存管理方面做了一個簡要的比較分析兵睛,其中還有很多方面以及細(xì)節(jié)并未展開或涉及肯骇。我們看到了Go與Java在內(nèi)存管理上采用的不同策略所帶來的一些影響:Java的分代內(nèi)存管理支持各個內(nèi)存代選擇合適的GC算法實(shí)現(xiàn)窥浪,通常GC只需要對某一個內(nèi)存代進(jìn)行回收;Go對整塊堆內(nèi)存分層級管理笛丙,GC時(shí)就不得不掃描整塊區(qū)域漾脂。Java與Go為了實(shí)現(xiàn)對象空間的快速分配,都為線程分配了一塊私有堆內(nèi)存胚鸯。Java分配確定的大小對象內(nèi)存骨稿,回收時(shí)更容易造成堆內(nèi)存碎片問題。Go按size class的分塊對象內(nèi)存模式雖然在重用性上得到了改善姜钳,但是又造成了一些浪費(fèi)坦冠。可見Go與Java 在內(nèi)存管理上各有特點(diǎn)哥桥。
Go語言社區(qū)活躍辙浑,生態(tài)也在逐漸繁榮。Go與Java各有優(yōu)勢拟糕,兩者之間也有許多可以相互借鑒之處判呕,因此它們未來的發(fā)展也非常值得期待。