UnpooledDirectByteBuf
这个一看就知道是无池化直接缓冲区啦,跟上面的那些区别就是他的缓冲区是ByteBuffer
的直接缓冲区,内部的原理我在这篇文章有讲过了:
protected ByteBuffer allocateDirect(int initialCapacity) {
return ByteBuffer.allocateDirect(initialCapacity);
}
另外一个就是释放的问题:
protected void freeDirect(ByteBuffer buffer) {
PlatformDependent.freeDirectBuffer(buffer);
}
public static void freeDirectBuffer(ByteBuffer buffer) {
CLEANER.freeDirectBuffer(buffer);
}
这里有个CLEANER
,好像就是清除器的意思,最后是调用他的freeDirectBuffer
清除内存。
CLEANER
他是一个接口类型,定义了直接缓冲区的释放方法。
其实这个还跟平台有关,我们可以PlatformDependent
静态代码块的一这段源码:
if (!isAndroid()) {
// only direct to method if we are not running on android.
// See https://github.com/netty/netty/issues/2604
if (javaVersion() >= 9) {
CLEANER = CleanerJava9.isSupported() ? new CleanerJava9() : NOOP;
} else {
CLEANER = CleanerJava6.isSupported() ? new CleanerJava6() : NOOP;
}
} else {
CLEANER = NOOP;
}
根据不同的java
版本,会有不同的实现,分为CleanerJava9
和CleanerJava6
两种,也就是9
以及以上的版本用9
,其他的用6
。
CleanerJava6
先说说这个,其实这个就是在这篇文章有讲过的Cleaner
的释放方法。我们看看CleanerJava6
的静态代码块,他会先申请一个容量为1的直接缓冲区,然后用反射获取他的cleaner
属性,再获取cleaner
对象以及clean
方法,调用一次clean
方法,释放掉刚才的内存,然后返回,当然如果支持unsafe
的话还可以获得cleaner
属性的偏移地址fieldOffset
。
static {
long fieldOffset;
Method clean;
Field cleanerField;
Throwable error = null;
final ByteBuffer direct = ByteBuffer.allocateDirect(1);
try {
Object mayBeCleanerField = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
Field cleanerField = direct.getClass().getDeclaredField("cleaner");//反射获取cleaner属性
if (!PlatformDependent.hasUnsafe()) {
// We need to make it accessible if we do not use Unsafe as we will access it via
// reflection.
cleanerField.setAccessible(true);
}
return cleanerField;
} catch (Throwable cause) {
return cause;
}
}
});
if (mayBeCleanerField instanceof Throwable) {
throw (Throwable) mayBeCleanerField;
}
cleanerField = (Field) mayBeCleanerField;
final Object cleaner;
// If we have sun.misc.Unsafe we will use it as its faster then using reflection,
// otherwise let us try reflection as last resort.
if (PlatformDependent.hasUnsafe()) {
fieldOffset = PlatformDependent0.objectFieldOffset(cleanerField);
cleaner = PlatformDependent0.getObject(direct, fieldOffset);
} else {
fieldOffset = -1;
cleaner = cleanerField.get(direct);
}
clean = cleaner.getClass().getDeclaredMethod("clean");
clean.invoke(cleaner);//调用一次clean方法,释放内存
} catch (Throwable t) {
// We don't have ByteBuffer.cleaner().
fieldOffset = -1;
clean = null;
error = t;
cleanerField = null;
}
if (error == null) {
logger.debug("java.nio.ByteBuffer.cleaner(): available");
} else {
logger.debug("java.nio.ByteBuffer.cleaner(): unavailable", error);
}
CLEANER_FIELD = cleanerField;//clear对象
CLEANER_FIELD_OFFSET = fieldOffset;
CLEAN_METHOD = clean;//获取clear的clean方法
}
freeDirectBuffer
而他清除方法就是freeDirectBuffer
:
@Override
public void freeDirectBuffer(ByteBuffer buffer) {
if (!buffer.isDirect()) {
return;
}
if (System.getSecurityManager() == null) {
try {
freeDirectBuffer0(buffer);
} catch (Throwable cause) {
PlatformDependent0.throwException(cause);
}
} else {
freeDirectBufferPrivileged(buffer);
}
}
最终是调用:
private static void freeDirectBuffer0(ByteBuffer buffer) throws Exception {
final Object cleaner;
// If CLEANER_FIELD_OFFSET == -1 we need to use reflection to access the cleaner, otherwise we can use
// sun.misc.Unsafe.
if (CLEANER_FIELD_OFFSET == -1) {
cleaner = CLEANER_FIELD.get(buffer);
} else {
cleaner = PlatformDependent0.getObject(buffer, CLEANER_FIELD_OFFSET);
}
if (cleaner != null) {
CLEAN_METHOD.invoke(cleaner);//调用clean方法
}
}
可见最后就是调用cleaner
的clean
方法啦。
CleanerJava9
他的静态代码块就直接是用反射取出unsafe
的方法invokeCleaner
,顺便调用一次,把申请的内存先释放了。
static {
final Method method;
final Throwable error;
if (PlatformDependent0.hasUnsafe()) {
final ByteBuffer buffer = ByteBuffer.allocateDirect(1);
Object maybeInvokeMethod = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
// See https://bugs.openjdk.java.net/browse/JDK-8171377
Method m = PlatformDependent0.UNSAFE.getClass().getDeclaredMethod(
"invokeCleaner", ByteBuffer.class);
m.invoke(PlatformDependent0.UNSAFE, buffer);
return m;
} catch (NoSuchMethodException e) {
return e;
} catch (InvocationTargetException e) {
return e;
} catch (IllegalAccessException e) {
return e;
}
}
});
if (maybeInvokeMethod instanceof Throwable) {
method = null;
error = (Throwable) maybeInvokeMethod;
} else {
method = (Method) maybeInvokeMethod;
error = null;
}
} else {
method = null;
error = new UnsupportedOperationException("sun.misc.Unsafe unavailable");
}
if (error == null) {
logger.debug("java.nio.ByteBuffer.cleaner(): available");
} else {
logger.debug("java.nio.ByteBuffer.cleaner(): unavailable", error);
}
INVOKE_CLEANER = method;
}
freeDirectBuffer
他的其实就是直接调用invokeCleaner
这个方法。
@Override
public void freeDirectBuffer(ByteBuffer buffer) {
// Try to minimize overhead when there is no SecurityManager present.
// See https://bugs.openjdk.java.net/browse/JDK-8191053.
if (System.getSecurityManager() == null) {
try {
INVOKE_CLEANER.invoke(PlatformDependent0.UNSAFE, buffer);//反射调用
} catch (Throwable cause) {
PlatformDependent0.throwException(cause);
}
} else {
freeDirectBufferPrivileged(buffer);
}
}
Unsafe的invokeCleaner
我们来看看这个方法做了什么,其实也是获得缓冲区的clearn
调用clean
方法:
public void invokeCleaner(java.nio.ByteBuffer directBuffer) {
if (!directBuffer.isDirect())
throw new IllegalArgumentException("buffer is non-direct");
DirectBuffer db = (DirectBuffer)directBuffer;
if (db.attachment() != null)
throw new IllegalArgumentException("duplicate or slice");
Cleaner cleaner = db.cleaner();
if (cleaner != null) {
cleaner.clean();
}
}
上面两个最终实现是一样的,但是CleanerJava9
使用的反射方法比CleanerJava6
要少,最后调用的方法也是直接调用的,不是用反射,所以性能要好一点。
设置和获取
我们可以看到这里操作都是直接操作buffer
的,内部就是一堆unsafe
的方法,直接操作对外内存。
@Override
protected void _setInt(int index, int value) {
buffer.putInt(index, value);
}
@Override
protected void _setIntLE(int index, int value) {
buffer.putInt(index, ByteBufUtil.swapInt(value));
}
@Override
protected int _getInt(int index) {
return buffer.getInt(index);
}
@Override
protected int _getIntLE(int index) {
return ByteBufUtil.swapInt(buffer.getInt(index));
}
ByteBufUtil
里面除了字节序转换方法之外还有好多字节缓冲区相关的方法,我们现在只关心字节序的转换,我们来看他内部怎么做的:
基本上是调用了很多类型的reverseBytes
方法,也就是把字节翻转,我们来看个Integer
的:
@HotSpotIntrinsicCandidate
public static int reverseBytes(int i) {
return (i << 24) |
((i & 0xff00) << 8) |
((i >>> 8) & 0xff00) |
(i >>> 24);
}
其实就是做了字节位移取掩码做高低位互换,调用一次可以换一次,即是大端和小端互相切换。
UnpooledUnsafeDirectByteBuf
这个其实就是UnpooledDirectByteBuf
的子类,用了unsafe
,跟上面的差不多,我就不多说了。
UnpooledUnsafeNoCleanerDirectByteBuf
这个名字一看就是没有清除器的,就是前面讲的Cleaner
,那他是怎么释放内存的呢,我们来看看这个类。
主要是这两个方法,其实没什么特备,只是申请和释放内存交给PlatformDependent
了,这两个方法是对应的。
//申请没有清除器的直接缓冲区
@Override
protected ByteBuffer allocateDirect(int initialCapacity) {
return PlatformDependent.allocateDirectNoCleaner(initialCapacity);
}
//释放没有清除器的直接缓冲区
@Override
protected void freeDirect(ByteBuffer buffer) {
PlatformDependent.freeDirectNoCleaner(buffer);
}
PlatformDependent的allocateDirectNoCleaner
public static ByteBuffer allocateDirectNoCleaner(int capacity) {
assert USE_DIRECT_BUFFER_NO_CLEANER;
incrementMemoryCounter(capacity);
try {
return PlatformDependent0.allocateDirectNoCleaner(capacity);
} catch (Throwable e) {
decrementMemoryCounter(capacity);
throwException(e);
return null;
}
}
PlatformDependent0的allocateDirectNoCleaner
可以看到调用了UNSAFE.allocateMemory
,内部最终是调用了本地方法allocateMemory0
申请内存,返回一个基地址,注意这里没有调用DirectByteBuffer(int cap)
,这个里面有很多操作的,这个前面讲过。
static ByteBuffer allocateDirectNoCleaner(int capacity) {
// Calling malloc with capacity of 0 may return a null ptr or a memory address that can be used.
// Just use 1 to make it safe to use in all cases:
// See: http://pubs.opengroup.org/onlinepubs/009695399/functions/malloc.html
return newDirectBuffer(UNSAFE.allocateMemory(Math.max(1, capacity)), capacity);
}
PlatformDependent0的newDirectBuffer
static ByteBuffer newDirectBuffer(long address, int capacity) {
ObjectUtil.checkPositiveOrZero(capacity, "capacity");
try {
return (ByteBuffer) DIRECT_BUFFER_CONSTRUCTOR.newInstance(address, capacity);
} catch (Throwable cause) {
// Not expected to ever throw!
if (cause instanceof Error) {
throw (Error) cause;
}
throw new Error(cause);
}
}
然后通过newDirectBuffer
用反射封装成DirectByteBuffer
,这里的反射调用了DirectByteBuffer(long addr, int cap)
,可以到下面的静态代码块里获取的。
而这个构造函数是没有清除器的,也没有一堆其他操作要做:
前面文章讲过DirectByteBuffer(int cap)
可是有一堆事情要做呢:
PlatformDependent的freeDirectNoCleaner
public static void freeDirectNoCleaner(ByteBuffer buffer) {
assert USE_DIRECT_BUFFER_NO_CLEANER;
int capacity = buffer.capacity();
PlatformDependent0.freeMemory(PlatformDependent0.directBufferAddress(buffer));//获取地址,释放
decrementMemoryCounter(capacity);
}
PlatformDependent0的directBufferAddress
这里最后还是调用了unsafe
的方法,其实就是本地方法freeMemory0
。
static void freeMemory(long address) {
UNSAFE.freeMemory(address);
}
可见UnpooledUnsafeNoCleanerDirectByteBuf
要比他的父类都要简洁多了,直接申请和释放内存,不需要清除器帮助,性能能提高不少。不过如果你忘记是释放内存的话,那就很尴尬了,不会有清除器为你释放内存了。
UnpooledUnsafeNoCleanerDirectByteBuf注意的点
注意JDK9
以下的默认可以使用这个,以上的就得自己设置io.netty.tryReflectionSetAccessible
为true
才可以。因为源码里有限制:
这个会导致DirectByteBuffer(long addr, int cap)
无法访问,抛出异常,自然就没办法用UnpooledUnsafeNoCleanerDirectByteBuf
了:
我的是JDK11
,所以直接缓冲区默认不是NoCleaner
的:
所以我打算设置下-Dio.netty.tryReflectionSetAccessible=true
:
调试发现好了:
不过也给我警告了,其实关系不大,说设置可访问object.setAccessible(true)
非法了,因为DirectByteBuffer(long addr, int cap)
是包内私有的,外面不能访问,会报错,所以要设置可访问啦:
不过这回创建的缓冲区类型对了:
总结
这次我们讲了堆外缓冲区,讲了是否用unsafe
方法操作的,堆外缓冲区的话是否有清除器的,还有一个要注意的就是JDK9
以前的默认是无清除器的,因为涉及到反射不安全,不过没清除器的性能好点。JDK9
以及以上的只能设置-Dio.netty.tryReflectionSetAccessible=true
参数啦。
好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。