面試官:說說Netty對象池的實現原理?

磊哥課程 2024-06-05 04:45:03

Netty 作爲一個高性能的網絡通訊框架,它內置了很多恰奪天工的設計,目的都是爲了將網絡通訊的性能做到極致,其中「對象池技術」也是實現這一目標的重要技術。

1.什麽是對象池技術?

對象池技術是一種重用對象以減少對象創建和銷毀帶來的開銷的方法。在對象池中,只有第一次訪問時會創建對象,並將其維護在內存中,當再次需要使用對象時,會直接從對象池中獲取對象,並在使用完畢後歸還給對象池,而不是頻繁地創建和銷毀對象。

使用對象池技術的優點有以下幾個:

提高性能:複用對象可以減少對象的創建和銷毀次數,降低系統開銷,提高系統性能和吞吐量。減少內存碎片:對象池可以避免頻繁地創建和銷毀對象,減少內存碎片的産生,提高內存利用率。避免頻繁GC:減少了對象的創建和銷毀,可以減少垃圾回收(GC)的頻率,降低系統的負擔,提高系統的穩定性。2.對象池基本使用

Netty 對象池技術的核心實現類爲 Recycler,Recycler 主要提供了以下 3 個方法:

get():獲取一個可重複使用的對象,如果對象池中有空閑對象,則返回其中一個;否則會創建一個新對象。recycle(T, Handle):回收一個對象,將對象放回對象池中以便下次複用。newObject(Handle):當對象池中沒有可用對象時,此方法會被調用以創建新的對象實例。

接下來我們寫一個 Recycler 對象池的使用 Demo,假設我們有一個 User 類,需要實現 User 對象的複用,具體實現代碼如下:

public UserRecyclerDemo { private static final Recycler<User> userRecycler = new Recycler<User>() { @Override protected User newObject(Handle<User> handle) { return new User(handle); } }; static final User { private String name; private Recycler.Handle<User> handle; public void setName(String name) { this.name = name; } public String getName() { return name; } public User(Recycler.Handle<User> handle) { this.handle = handle; } public void recycle() { handle.recycle(this); } } public static void main(String[] args) { User user1 = userRecycler.get(); // 1.從對象池獲取 User 對象 user1.setName("zhangsan"); // 2.設置 User 對象的屬性 user1.recycle(); // 3.回收對象到對象池 User user2 = userRecycler.get(); // 4.從對象池獲取對象 System.out.println(user1 == user2); System.out.println(user2.getName()); }}

以上程序的執行結果如下:

true

zhangsan

從上述結果可以看出,當第一次調用 userRecycler.get() 時,因爲對象池中尚未存在 user 對象,所以創建了 name 爲“zhangsan”的對象。但第二次再調用 userRecycler.get() 時,因爲對象池中已經存在了 user 對象,所以直接從對象池中取出了 user 對象,所以 user1==user2 時,得到的結果是 true。

3.對象池技術應用

在 Netty 中,使用 Recycler 對象池管理對象的常見類有以下幾個:

PooledHeapByteBuf:管理堆內存中的 ByteBuf 對象。PooledDirectByteBuf:管理堆外內存中的 ByteBuf 對象。ChannelOutboundBuffer.Entry:Netty 出站緩沖區(ChannelOutboundBuffer)中,每一個待發送的消息都包裝在一個 Entry 對象中。4.實現原理

要搞清楚 Netty 對象池技術的實現原理,就要搞清楚 Netty 對象池的核心組件,以及組件之間的關系。

Netty 對象池技術的實現依靠以下 4 大組件:

Stack(棧):每個線程都關聯一個 Stack(使用 FastThreadLocal 進行存儲),用于存儲和管理該線程回收的對象。Stack 中存儲的是 DefaultHandle 對象,這些 DefaultHandle 對象包裝了實際要重用的對象。Stack 是與線程綁定的,每個線程從自己的 Stack 中獲取對象。WeakOrderQueue(弱序隊列):當某個線程(非主線程)回收對象時,這些對象不會直接放入主線程的 Stack 中,而是放入 WeakOrderQueue 中。WeakOrderQueue 存儲的是從其他線程回收的對象,這些對象被包裝在 DefaultHandle 中。WeakOrderQueue 與 Stack 關聯,但屬于非主線程。當主線程的 Stack 爲空時,會嘗試從 WeakOrderQueue 中獲取對象。Link(鏈表):WeakOrderQueue 中的存儲單元,用于存儲回收的對象。Link 中存儲的是 DefaultHandle 對象數組,這些數組包含從其他線程回收的對象。DefaultHandle:對象的包裝類,在 Recycler 中緩存的對象都會包裝成 DefaultHandle 類。DefaultHandle 中存儲了實際要重用的對象,以及與之相關的元數據。

簡單來說,這 4 個組件的關系是,(每個)線程爲了保證線程安全和高效性操作,所以會把使用的對象放到 Stack 棧中,且每個線程都有自己的 Stack 棧。當線程中的對象不再被使用時(也就是被回收時),並不會將回收對象直接放到 Stack 中(因爲當前線程已經不再使用了),此時會將對象存放到 WeakOrderQueue 隊列中,因爲 WeakOrderQueue 隊列相當于“線程共享的區域”,這樣其他線程就可以方便的從 WeakOrderQueue 中獲取對象進行重用了。而 WeakOrderQueue 中的存儲單元是 Link 鏈表,它存儲的是對象池中的包裝對象 DefaultHandle,這就是這四大核心組件之間的關系。

5.線程如何獲取對象?

在 Netty 中,獲取對象池中對象的流程如下:

判斷 Stack:線程首先會嘗試從自己的 Stack 中獲取對象。如果 Stack 中有對象,則直接彈出(pop)並返回。Stack 爲空:如果 Stack 爲空,線程會檢查 WeakOrderQueue。如果 WeakOrderQueue 中有對象,則按照一定的規則(如“1/7規則”,每 7 個移動 1 個)將部分對象轉移到 Stack 中,然後從 Stack 中彈出並返回。創建新對象:如果 Stack 和 WeakOrderQueue 都爲空,線程會調用 newObject() 方法創建一個新的對象,並包裝成 DefaultHandle 後放入 Stack 中,然後返回該對象。

通過這樣的設計,Netty 的 Recycler 對象池技術能夠高效地重用對象,減少內存分配和垃圾收集的開銷,提升性能。

課後思考

Netty 是如何利用池化技術管理內存的?講講它的具體實現?

本文已收錄到我的面試小站 [www.javacn.site](https://www.javacn.site),其中包含的內容有:Redis、JVM、並發、並發、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、設計模式、消息隊列等模塊。

0 阅读:0

磊哥課程

簡介:感謝大家的關注