文档章节

netty源码分析系列——PooledByteBuf&PooledByteBufAllocator

杨武兵
 杨武兵
发布于 2017/05/26 22:26
字数 1739
阅读 333
收藏 0

对象池

池技术在计算机届世界中也是司空见惯,我们连接数据库会用到数据连接池可以使得我们连接数据的时候能够更快,java中的很对基本类型例如Integer针对常用的数字也做了池化处理,避免创建过多的重复对象。对于那些构造或者初始化起来代价非常大的对象,我们会把它们提前初始化好放入一个池中,如果有消费者需要使用,则直接从池中获取,这个对象池也会根据情况自动补充更多的对象,或者及时清除那些过多的对象,这种技术就称作为对象池。

而对于ByteBuf来说,如果频繁使用,对性能又有更高的要求,则也可以使用类似的对象池技术来近一步提升性能。本文重点关注netty使用池化技术实现的ByteBuf类。

例子

我们先来看使用PooledByteBuf的例子,通过例子来演示一下它的使用以及价值。

public class PooledByteBufTest {

    public static void main(String[] args) throws InterruptedException {

        for(int i=0; i<5; i++){
            long s = System.currentTimeMillis();
            ByteBuf byteBuf = PooledByteBufAllocator.DEFAULT.buffer(1024*1024);
            long e = System.currentTimeMillis();
            System.out.println("初始化pooled的ByteBuf第"+i+"次,耗时"+(e-s)+"毫秒");
        }
    }
}

例子的代码非常简单,就是通过PooledByteBufAllocator的默认对象对象去分配一个ByteBuf对象,得到一个ByteBuf对象之后,你可以参考它提供的统一api来使用它了,这里初始化大小是1KB,然后记录一下分配的耗时,总共循环执行5次,你可以猜一猜它的实际执行情况会是什么样的?

我这里一次实际执行的结果如下:

初始化pooled的ByteBuf第0次,耗时165毫秒
初始化pooled的ByteBuf第1次,耗时2毫秒
初始化pooled的ByteBuf第2次,耗时0毫秒
初始化pooled的ByteBuf第3次,耗时0毫秒
初始化pooled的ByteBuf第4次,耗时0毫秒

通过实际执行结果我们可以看出来池化带来的效果吧,第一次执行耗时165ms,从第2次开始执行耗时几乎是0ms,性能提升效果非常的明显,而且还能够节省内存,避免没有必要的分配和释放内存的开销。这对于适合于频繁使用ByteBuf,可以循环利用的场景非常适合,对于系统的性能及资源利用率都有较大价值。

类图

本文件将分析以下三个类的源码,其它类型不够典型不做介绍。

PooledByteBufAllocator:PooledByteBuf的分配器,通过它来分配一个池化的ByteBuf对象。

PooledByteBuf:抽象模版类其它类都继承了它,关于池化相关的技术都应该在这里体现了。

PooledHeapByteBuf:使用堆内存的字节数组实现的PooledByteBuf;

PooledDirectBytBuf:使用直接内存缓冲区DirectByteBuffer实现的PooledByteBuf;

PooledByteBufAllocator

关键属性列表

    private final PoolArena<byte[]>[] heapArenas; //堆内存对象池区域数组。
    private final PoolArena<ByteBuffer>[] directArenas;//直接内存对象池区域数组。
    private final int tinyCacheSize;//微型缓冲区大小
    private final int smallCacheSize;//小型缓冲区大小
    private final int normalCacheSize;//一般缓冲区大小
    private final List<PoolArenaMetric> heapArenaMetrics;//堆内存对象池区域度量
    private final List<PoolArenaMetric> directArenaMetrics;//直接内存对象池区域度量。
    private final PoolThreadLocalCache threadCache;//本地线程缓冲区。

上述属性列表是分配器最重要的组成部分,后续分配PooledByteBuf使用这些属性来分配。

构造函数

  public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectArena, int pageSize, int maxOrder,
                                  int tinyCacheSize, int smallCacheSize, int normalCacheSize,
                                  boolean useCacheForAllThreads) {
        super(preferDirect);
        threadCache = new PoolThreadLocalCache(useCacheForAllThreads);//构造本地线程缓存。
        this.tinyCacheSize = tinyCacheSize;
        this.smallCacheSize = smallCacheSize;
        this.normalCacheSize = normalCacheSize;
        final int chunkSize = validateAndCalculateChunkSize(pageSize, maxOrder);//对于chunkSize它是通过pageSize和maxOrder参数计算而来,计算公式是chunkSize=pageSize*(2的maxOrder次幂),maxOrder可以通过PooledByteBufAllocator构造子的maxOrder参数或io.netty.allocator.maxOrder系统变量设置,只能设置0-14范围内的值,默认值11,也就是说一个chunk大小默认等于2的11次方个pageSize.

        if (nHeapArena < 0) {
            throw new IllegalArgumentException("nHeapArena: " + nHeapArena + " (expected: >= 0)");
        }
        if (nDirectArena < 0) {
            throw new IllegalArgumentException("nDirectArea: " + nDirectArena + " (expected: >= 0)");
        }

        int pageShifts = validateAndCalculatePageShifts(pageSize);//pageShifts就是pageSize二进制表示时尾部0的个数,那么按照这种方式有没有可能因为0的数量小于9个而导致计算结果是负数呢?当然不会,上面提到了pageSize不是随意设置的,它必须大于4096(4K),pageSize是4096时,它的二进制表示是1000000000000,那么这个pageShifts就是12,所以small块的数量就是3

        if (nHeapArena > 0) {
            heapArenas = newArenaArray(nHeapArena);
            List<PoolArenaMetric> metrics = new ArrayList<PoolArenaMetric>(heapArenas.length);
            for (int i = 0; i < heapArenas.length; i ++) {
                PoolArena.HeapArena arena = new PoolArena.HeapArena(this, pageSize, maxOrder, pageShifts, chunkSize);//使用pageSize,maxOrder,pageShifts和chunkSize构造一个HeapArena。
                heapArenas[i] = arena;
                metrics.add(arena);
            }
            heapArenaMetrics = Collections.unmodifiableList(metrics);
        } else {
            heapArenas = null;
            heapArenaMetrics = Collections.emptyList();
        }

        if (nDirectArena > 0) {
            directArenas = newArenaArray(nDirectArena);
            List<PoolArenaMetric> metrics = new ArrayList<PoolArenaMetric>(directArenas.length);
            for (int i = 0; i < directArenas.length; i ++) {
                PoolArena.DirectArena arena = new PoolArena.DirectArena(
                        this, pageSize, maxOrder, pageShifts, chunkSize);
                directArenas[i] = arena;
                metrics.add(arena);
            }
            directArenaMetrics = Collections.unmodifiableList(metrics);
        } else {
            directArenas = null;
            directArenaMetrics = Collections.emptyList();
        }
    }

构造函数就是通过参数的值来构造初始化定义的一些属性值,这些属性值的默认值从代码中设置的默认值来,也会默认读取指定环境变量的值,当然还可以通过构造函数来控制。

分配池化的堆字节缓冲区方法

newHeapBuffer方法是真正的分配ButeBuf的方法。

    protected ByteBuf newHeapBuffer(int initialCapacity, int maxCapacity) {
        PoolThreadCache cache = threadCache.get();//从线程上下文中获得缓存。
        PoolArena<byte[]> heapArena = cache.heapArena;//获得缓存中的heapArena对象。

        ByteBuf buf;
        if (heapArena != null) {//线程上下文中的对象有
            buf = heapArena.allocate(cache, initialCapacity, maxCapacity);//分配一个ByteBuf独享。
        } else {
            buf = new UnpooledHeapByteBuf(this, initialCapacity, maxCapacity); //没有则自己创建一非池化的堆内存ByteBuf对象。
        }

        return toLeakAwareBuffer(buf);
    }

通常我们使用jdk自带的ThreadLocal来实现线程变量存储,可在同一个线程上下文内共享一些变量,而这种实现在普通场景下都是满足要求的,但是如果对于性能有严格的要求则需要自己实现,netty就是未了追求更高的性能,它自己实现了一个与ThreadLocal相似使用接口的线程变量。详细内容参考文章:http://blog.csdn.net/prestigeding/article/details/54945658

得到线程变量中的PoolThreadCache对象,如果

PooledByteBuf

关键属性列表

    private final Recycler.Handle<PooledByteBuf<T>> recyclerHandle;//回收处理器,后面详细介绍这个类。
    protected PoolChunk<T> chunk;//
    protected long handle;
    protected T memory;//内存区域。
    protected int offset;//
    protected int length;//当前长度,用于实现当前容量值。
    int maxLength;//最大长度,用于控制缓冲区最大容量。
    PoolThreadCache cache;
    private ByteBuffer tmpNioBuf;
    private ByteBufAllocator allocator;//ByteBuf分配器。

重点方法列表

构造方法

    protected PooledByteBuf(Recycler.Handle<? extends PooledByteBuf<T>> recyclerHandle, int maxCapacity) {
        super(maxCapacity);
        this.recyclerHandle = (Handle<PooledByteBuf<T>>) recyclerHandle;
    }

构造方法需要传入一个Recyler.Handle设置属性,传入macCapacity来控制最大容量限制值。

参考资料

http://m.blog.csdn.net/pentiumchen/article/details/45372625

© 著作权归作者所有

共有 人打赏支持
杨武兵

杨武兵

粉丝 255
博文 61
码字总数 123254
作品 1
昌平
架构师
私信 提问
加载中

评论(1)

罗文浩
罗文浩
学习了。
Java系列文章(全)

JVM JVM系列:类装载器的体系结构 JVM系列:Class文件检验器 JVM系列:安全管理器 JVM系列:策略文件 Java垃圾回收机制 深入剖析Classloader(一)--类的主动使用与被动使用 深入剖析Classloader(二...

www19
2017/07/04
0
0
《深入探索Netty原理及源码分析》文集小结

写在2017年末尾,翻看文集的第一篇文章已经是三个月前的事了,也没想过这文集会写那么久,这么慢。。。 Netty文集中的文章主要都是我学习过程的笔记,写博客的主要目的是为了通过输出来倒逼输...

tomas家的小拨浪鼓
2017/12/30
0
0
《成神之路-基础篇》Java基础知识——常用的Java工具库

本文是《成神之路系列文章》的第一篇,主要是关于JVM的一些介绍。 持续更新中 commons.lang https://commons.apache.org/proper/commons-lang/ commons.*… guava-libraries Google guava工具...

HollisChuang's Blog
2018/10/14
0
0
spark2.1.0之源码分析——RPC管道初始化

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/beliefer/article/details/81197447 提示:阅读本文前最好先阅读: 《Spark2.1.0之内置RPC框架》 《spark2.1....

泰山不老生
2018/07/25
0
0
Qzone 微信 Java高级——dubbo源码分析之远程通信 netty

Java高级——dubbo源码分析之远程通信 netty dubbo 底层通信选择了 netty 这个 nio 框架做为默认的网络通信框架并且通过自定义协议进行通信。dubbo 支持以下网络通信框架: Netty(默认) Min...

Java架构师那些事
2018/08/29
0
0

没有更多内容

加载失败,请刷新页面

加载更多

看过上百部片子的这个人教你视频标签算法解析

本文由云+社区发表 随着内容时代的来临,多媒体信息,特别是视频信息的分析和理解需求,如图像分类、图像打标签、视频处理等等,变得越发迫切。目前图像分类已经发展了多年,在一定条件下已经...

腾讯云加社区
16分钟前
0
0
2. 红黑树

定义:红黑树(Red-Black Tree,简称R-B Tree),它一种特殊的二叉查找树(Binary Search Tree)。 要理解红黑树,先要了解什么是二叉查找树。在上一章中,我们学习了什么是二叉树,以及二叉树...

火拳-艾斯
17分钟前
0
0
input的button类型,点击页面跳转

一、input type=button 不做任何操作 例如: <input type="button" class="btn btn-primary" style="width: 30%" value="返回" onclick="window.location.href='/users/list'"></input> onc......

Sunki
23分钟前
0
0
踩坑:js 小数运算出现精度问题

背景 在学习小程序商城源码时发现了这个问题,单价可能出现小数,小数之间运算结果会莫名其妙多出一大串数字,比如下面这样👇。 在此之前我是知道 js 中著名的 0.1 + 0.2 != 0.3 的问题的,...

dkvirus
28分钟前
0
0
zookeeper和HBASE总结

zookeeper快速上手 zookeeper的基本功能和应用场景 zookeeper的整体运行机制 zookeeper的数据存储机制 数据存储形式 zookeeper中对用户的数据采用kv形式存储 只是zk有点特别: key:是以路径...

瑞查德-Jack
51分钟前
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部