文档章节

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

杨武兵
 杨武兵
发布于 2017/05/26 22:26
字数 1739
阅读 1K
收藏 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

© 著作权归作者所有

杨武兵

杨武兵

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

评论(1)

罗文浩
罗文浩
学习了。
开篇:Netty源码学习总结系列——线程调度模型全面总结

前言和目标 首先,好久之前写了:Netty 学习笔记(1)Netty 通信原理,可惜很久没更新了,不少博客园的网友经常问,到底还写不写,后来连问都不问了,深表惭愧,从今天开始,重新开启这个系列...

dashuai的博客
03/08
0
0
Netty源码学习总结系列——异步模型全面总结

文章集合 Netty的异步模型分析(1) Netty的异步模型分析(1) 从最常见,最简单的bind入手——如何正确启动一个Netty服务器 “你”怎么定义异步? 正确启动服务器的方式 前面扯了那么多Net...

dashuai的博客
03/28
0
0
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

没有更多内容

加载失败,请刷新页面

加载更多

如何在Windows上安装pip? - How to install pip on Windows?

问题: pip is a replacement for easy_install . pip替代了easy_install 。 But should I install pip using easy_install on Windows? 但是我应该在Windows上使用easy_install安装pip吗? ......

fyin1314
今天
21
0
gitlit二级目录访问

由于我们只有一个域名暴露,特殊需求,所以需要二级目录访问 配置文件在 defaults.properties 第1985行 contextPath 改掉就好了 # Context path for the GO application. You might want to...

shzwork
今天
24
0
OSChina 周一乱弹 —— 我电脑传染了新冠脚气

@性感码农 :不结婚,被老爸说,回村里别人都瞧不起你,及即使你赚了很多钱,不结婚,永远没有人瞧得起你。挺纳闷的,要别人瞧得起我干嘛 又不回村里, 跟他们生活也没什么交集啊, 用得着他...

小小编辑
今天
18
0
类加载的过程

加载->链接->初始化; 其中链接又分为:验证->准备->解析。

曦鱼violet
今天
21
0
Linux下几个与磁盘空间和文件尺寸相关的命令

硬盘是计算机非常重要的一个部件,不管是代码,还是 UI 、声音、文档,抑或是没人时偷偷看的小视频,都需要保存在硬盘里。 对于很多 Linux 服务器,会进行很多的编译操作。而编译操作在很多情...

Linux就该这么学
今天
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部