我們都知道,.net的GC是不會壓縮大對象堆的,因為其時間開銷不可接受,但這是以大對象堆產生大塊碎片為代價的,如果以后要分配的大對象比最大的碎片還大,那么即使它比所有碎片的總大小要小,也是無法在不擴展大對象堆的前提下分配成功的,此時有可能引發內存不足的異常。
我想到一個方案,可以讓大對象堆也能壓縮,而且時間開銷在可接受的范圍內,原理是利用頁表。我們知道,程序能看到的內存地址都是虛擬地址,是通過頁表映射到物理地址的,連續的虛擬地址對應的物理地址未必連續,反之亦然。在內存中移動大量數據,開銷很大,因為數據真的要在物理內存上復制,但如果我們不動物理內存上的數據,只修改頁表及其緩存TLB,即修改了物理地址與虛擬地址的映射關系,開銷就會小得多,而且對于應用程序來說,同樣達到了內存移動的效果。(物理內存上沒有數據移動,但對象的虛擬地址卻變了,對應用程序來說,這就是數據移動了!)
當然,如果要用這種方法實現壓縮大對象堆,也會有一些局限性:比如每個大對象必須占據整數頁的空間,且大對象的起始地址必須是某頁的起始地址,這樣大對象之間會出現一些小碎片(不會超過一頁的大小,即不超過4K,與85K以上的大對象本身相比,還是很小的),但小碎片總比大碎片好呀,就看怎么權衡了,而且這些小碎片也是可以被利用的,比如可以把一些大小合適的2代小對象存儲到這些小碎片中,以節約小對象堆的空間。
PS: 現在的一些虛擬機軟件的實現似乎就使用了類似的方法,以達到提高效率的目的。
該問題的英文討論貼:https://github.com/dotnet/coreclr/issues/555
新聞熱點
疑難解答