面试官:Netty中的缓冲区为什么比原生NIO更高效,2024年最新java面试逻辑题答案分析

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

===========

容量默认值为256字节,最大值为Integer.MAX_VALUE,也就是2GB

实际调用

AbstractByteBuf.writeBytes,如下:

AbstractByteBuf.writeBytes

@Override

public ByteBuf writeBytes(byte[] src) {

writeBytes(src, 0, src.length);

return this;

}

AbstractByteBuf.writeBytes(src, 0, src.length);

@Override

public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {

ensureWritable(length); //检查是否有足够的可写空间,是否需要扩容

setBytes(writerIndex, src, srcIndex, length);

writerIndex += length;

return this;

}

AbstractByteBuf.ensureWritable(length);

@Override

public ByteBuf ensureWritable(int minWritableBytes) {

ensureWritable0(checkPositiveOrZero(minWritableBytes, “minWritableBytes”));

return this;

}

AbstractByteBuf.ensureWritable0(checkPositiveOrZero(minWritableBytes, “minWritableBytes”));

final void ensureWritable0(int minWritableBytes) {

final int writerIndex = writerIndex(); //获取当前写下标

final int targetCapacity = writerIndex + minWritableBytes; //计算最少需要的容量

// using non-short-circuit & to reduce branching - this is a hot path and targetCapacity should rarely overflow

if (targetCapacity >= 0 & targetCapacity <= capacity()) { //判断当前容量是否够用

ensureAccessible(); //检查ByteBuf的引用计数,如果为0则不允许继续操作

return;

}

if (checkBounds && (targetCapacity < 0 || targetCapacity > maxCapacity)) { //判断需要的容量是否是合法值,不合法为true直接抛出越界异常

ensureAccessible();//检查ByteBuf的引用计数,如果为0则不允许继续操作

throw new IndexOutOfBoundsException(String.format(

“writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s”,

writerIndex, minWritableBytes, maxCapacity, this));

}

// Normalize the target capacity to the power of 2.(标准化为2的次幂)

final int fastWritable = maxFastWritableBytes();

int newCapacity = fastWritable >= minWritableBytes ? writerIndex + fastWritable
alloc().calculateNewCapacity(targetCapacity, maxCapacity); //计算扩容后容量(只要扩容最小64)

// Adjust to the new capacity.

capacity(newCapacity); //设置新的容量

}

alloc().calculateNewCapacity(targetCapacity, maxCapacity) -> AbstractByteBufAllocator

@Override

public int calculateNewCapacity(int minNewCapacity, int maxCapacity) {

checkPositiveOrZero(minNewCapacity, “minNewCapacity”); //最小所需容量

if (minNewCapacity > maxCapacity) { //判断最小所需容量是否合法

throw new IllegalArgumentException(String.format(

“minNewCapacity: %d (expected: not greater than maxCapacity(%d)”,

minNewCapacity, maxCapacity));

}

final int threshold = CALCULATE_THRESHOLD; // 4 MiB page 阈值超过4M以其他方式计算

if (minNewCapacity == threshold) { //等于4M直接返回4M

return threshold;

}

// If over threshold, do not double but just increase by threshold.

if (minNewCapacity > threshold) { //大于4M,不需要加倍,只需要扩大阈值即可

int newCapacity = minNewCapacity / threshold * threshold;

if (newCapacity > maxCapacity - threshold) {

newCapacity = maxCapacity;

} else {

newCapacity += threshold;

}

return newCapacity;

}

// 64 <= newCapacity is a power of 2 <= threshold

final int newCapacity = MathUtil.findNextPositivePowerOfTwo(Math.max(minNewCapacity, 64)); //计算不少于所需容量的最小的2次幂的值

return Math.min(newCapacity, maxCapacity); //取容量所允许的最大值和计算的2次幂的最小值,当然在这儿就是newCapacity=64

}

总结一下就是最小所需容量是否等于阈值,如果是直接返回阈值此后直接扩大阈值,否则以64为最小2次幂为基础每次扩大二倍直到阈值.


选择合适的ByteBuf实现

==============

netty针对ByteBuf提供了8中具体的实现方式,如下:

堆内/堆外

是否池化

访问方式

具体实现类

备注

heap堆内

unpool

safe

UnpooledHeapByteBuf

数组实现

heap堆内

unpool

unsafe

UnpooledUnsafeHeapByteBuf

Unsafe类直接操作内存

heap堆内

pool

safe

PooledHeapByteBuf

heap堆内

pool

unsafe

PooledUnsafeHeapByteBuf

~

direct堆外

unpool

safe

UnpooledDirectByteBuf

NIO DirectByteBuffer

direct堆外

unpool

unsafe

UnpooleUnsafedDirectByteBuf

~

direct堆外

pool

safe

PooledDirectByteBuf

~

direct堆外

pool

unsafe

PooledUnsafeDirectByteBuf

~

在使用时,都是通过ByteBufAllocator分配器进行申请,同时分配器具有内存管理的功能。

在这儿堆内和堆外没有什么区别,对api的使用时一样的,仅仅是通过Unpooled申请的不一样.

那个safe和unsafe有什么区别呢?

以UnpooledHeapByteBuf和UnpooledUnsafeHeapByteBuf中的getByte(int index)方法为例进行分析

UnpooledHeapByteBuf

@Override

public byte getByte(int index) {

ensureAccessible();

return _getByte(index); //真正的获取字节的方法

}

@Override

protected byte _getByte(int index) {

return HeapByteBufUtil.getByte(array, index); //通过HeapByteBufUtil工具类获取数据

}

HeapByteBufUtil

static byte getByte(byte[] memory, int index) {

return memory[index];

}

UnpooledHeapByteBuf从堆内数组中获取数据,这是安全的

UnpooledUnsafeHeapByteBuf

@Override

public byte getByte(int index) {

checkIndex(index);

return _getByte(index);

}

@Override

protected byte _getByte(int index) {

return UnsafeByteBufUtil.getByte(array, index);

}

PlatformDependent0

static byte getByte(byte[] data, int index) {

return UNSAFE.getByte(data, BYTE_ARRAY_BASE_OFFSET + index);

}

UnpooledUnsafeHeapByteBuf是通过UNSAFE来操作内存的


现在我们来研究一下Unsafe

Unsafe的实现

=========

Unsafe意味着不安全的操作,但是更底层的操作会带来性能提升和特殊功能,Netty中会尽力使用unsafe以提升系统性能

Java语言很重要的特性就是一次编译到处运行,所以它针对底层的内存或者其他操作做了很多封装,而unsafe提供了一系列我们操作底层的方法,可能会导致不兼容或不可知的异常.

比如:

  • 返回一些低级的内存信息

  • addressSize

  • pageSize

  • 提供用于操作对象及其字段的方法

  • allocateInstance

  • objectFieldOffset

  • 提供用于操作类及其静态字段的方法

  • staticFieldOffset

  • defineClass

  • defineAnonymousClass

  • ensureClassInitialized

  • 低级的同步原语

  • monitorEnter

  • tryMonitorEnter

  • monitorExit

  • compareAndSwapInt

  • putOrderedInt

  • 直接访问内存的方法 allocateMomery copyMemory freeMemory getAddress getInt putInt

  • 操作数组

  • arrayBaseOffset

  • arrayIndexScale

既然这些东西都是jdk封装好的,而是netty也是直接使用的,所以我们无论在使用safe还是unsafe的时候都是无感知的,我们无需关系底层的操作逻辑,因为api都是一样的,只是实现不一样


是否还有一个疑问,池化和非池化是什么意思?

池化和非池化

======

比如在使用Unpooled.buffer(10)申请一个缓存区的时候,默认非池化申请的一个缓冲区.

池化和非池化的区别主要是申请内存缓存空间以及缓存空间的使用上,体现为内存复用.

  • 在申请内存缓存空间方面:

  • pool:池化申请的时候会申请一个比当前所需内存空间更大的内存空间,这就好比一个快递柜,为此netty提供了buf分配管理器专门用来处理这种事情,来创建或复用ByteBuf.

  • unpool:非池化申请只会申请特定大小能够使用的内存缓存空间,使用完之后立刻释放,这就像直接把快递放到你的手中,你所在的位置就是开辟的内存空间.

  • 在缓存空间使用方面:

  • pool:池化申请的内存空间有一定扩容容积,也就是这个快递柜可以存放多个快递,只需要找到对应的方格即可存放,同样buf分配管理器来复用已经创建好的内存空间,在创建ByteBuf的时候已经开辟3中大小的内存块 normal:16MN small:8KB tiny:512B

  • unpool:毫无疑问,非池化的方式必然是每次都会再去开辟内存空间的.

理论如此,netty中是如何做到内存复用的?

最后

面试前一定少不了刷题,为了方便大家复习,我分享一波个人整理的面试大全宝典

  • Java核心知识整理

2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多

Java核心知识

  • Spring全家桶(实战系列)

2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多

  • 其他电子书资料

2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多

Step3:刷题

既然是要面试,那么就少不了刷题,实际上春节回家后,哪儿也去不了,我自己是刷了不少面试题的,所以在面试过程中才能够做到心中有数,基本上会清楚面试过程中会问到哪些知识点,高频题又有哪些,所以刷题是面试前期准备过程中非常重要的一点。

以下是我私藏的面试题库:

2020年五面蚂蚁、三面拼多多、字节跳动最终拿offer入职拼多多

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
其他电子书资料

[外链图片转存中…(img-bkCPj6od-1713559552642)]

Step3:刷题

既然是要面试,那么就少不了刷题,实际上春节回家后,哪儿也去不了,我自己是刷了不少面试题的,所以在面试过程中才能够做到心中有数,基本上会清楚面试过程中会问到哪些知识点,高频题又有哪些,所以刷题是面试前期准备过程中非常重要的一点。

以下是我私藏的面试题库:

[外链图片转存中…(img-v4Rj0b9r-1713559552643)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-N7a7HC3y-1713559552644)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值