如何创建 ByteBuffer 的子类?

11
因此,Java NIO的架构师们没有创建一个ByteBuffer接口,而是创建了一个ByteBuffer,它不是一个final类,但它没有公共包构造函数,因此它不能在其包之外被子类化。糟糕。:P
我有一个程序,在很多地方使用内存映射文件字节缓冲区(通过FileChannel.map()获得),我正在尝试查找一个令人讨厌的错误,即由于至少有一个未释放到垃圾收集器的ByteBuffer而导致相关文件未关闭。
我想创建一个InstrumentedByteBuffer类,它看起来像一个字节缓冲区,但装饰一个常规的ByteBuffer(或其子类,如MappedByteBuffer),并跟踪其存在(包括由duplicate()slice()创建的新缓冲区)--这样我可以保持我的代码不变,只需要装饰原始的字节缓冲区。
是否有任何方法(通过反射或代理或其他方式)绕过私有构造函数来实现这一点?我不需要将其发布到最终产品中,我只需要暂时使用它来解决这个错误。

我想知道像 YourKit 这样的工具,带有内存调试器和探针,是否能够帮助您追踪到那个游离的对象(我经常使用 YourKit,但从未遇到过这样的问题需要调试)。 - NPE
2
另外,我不确定子类化会如何帮助,因为您无法控制缓冲区的创建。您说您使用FileChannel.map(),所以您需要以某种方式诱导后者创建您的类的实例,是吗? - NPE
你能够转储堆并跟踪你的失控实例的引用吗? - jtoberon
@aix:我对缓冲区的初始创建具有控制权(好吧,我编写了调用FileChannel.map()函数的代码),只是最终在数十个地方使用它,其中任何一个都可以被切片并存储为私有变量。 - Jason S
ByteBuffer 是为了性能而构建的,这是完全正确的。 - bestsss
显示剩余2条评论
5个回答

2
我正在寻找一个非常讨厌的错误,导致文件没有关闭,因为至少有一个ByteBuffer没有被垃圾回收释放。
这并不合理,未被回收的ByteBuffer不会阻止文件被关闭。你在这里走错了方向。然而,有一个众所周知的问题是MappedByteBuffer永远不会被垃圾回收,从而使文件实际上保持打开状态。这真的是一个设计问题:多年来一直存在,但没有真正的解决方案。教训是不要使用大量的MappedByteBuffers。

1
@Jason S 但是你一直在谈论子类化ByteBuffers,这不是问题所在。子类化它们不会有任何区别。问题在于Mapped ByteBuffers。即使MBB本身被垃圾回收,映射的数据区域也永远不会被释放。而且,正如我所说,没有解决方案。 - user207421
你误解了重点:我不关心我正在子类化的类;我关心的是能够替换另一个类为我的仪器化类。 (如果他们制作了一个ByteBuffer接口,这将不是问题)我想创建一个类,可以将其方法委托给真正的“MappedByteBuffer”,但拦截“slice()”和“duplicate()”和“asReadOnlyBuffer()”,以便我也可以装饰这些缓冲区,然后在我的函数期望ByteBuffer的地方使用我的类。 - Jason S
就我个人而言,在我的电脑上,当MappedByteBuffer被垃圾回收时,Java确实会释放文件。(但我使用的是只读模式;如果它在读写模式下有问题,那对我没有影响) - Jason S
@JasonS 那么是有人或者某些事情阻止你这样做吗?ByteBuffer 是一个抽象类:只需要继承它即可。 - user207421

2

JMockit 提供了一种便捷的方式来创建模拟类。在这种情况下,它可能会有所帮助。

模拟您感兴趣的方法,并让它们管理您的账务和然后调用原始类的方法


1

我认为你走错了方向。FileChannel.map() 的设计存在缺陷,因为它仅限于2GB的块,并且您无法控制映射何时被垃圾回收拾取。在Windows上,这经常导致应用程序无法再次打开映射的文件。

因此,与其使用字节码指令或类似的黑客方式使情况变得更糟,不如重构代码以摆脱FileChannel.map()。如果您这样做了,下一个维护您代码的程序员将非常感激您。;-)


-1

我不明白为什么你需要在这里使用子类。为什么不使用AOP呢?AspectJ似乎非常适合这个任务。如果你真的感到雄心勃勃,可以使用Java Instrumentation并进行一些字节码工程 :P


-1

嗯——我刚刚看到这个:JavaSpecialists newsletter 168似乎可以产生一个通用的委托框架——但由于反射而有点慢。

Socket使用策略模式进行实际通信,我们能够自己指定实现。因此,我们需要编写自己的策略来计算前后流动的字节数。不幸的是,标准的策略实现在java.net.*包中只有包访问权限,因此我们无法直接使用它们。我们当然不能对它们进行子类化,但是我们可以通过反射调用方法。但是,因为类本身具有包访问权限,所以我们需要找到声明的构造函数,将其设置为可访问,并实例化它。


调用包私有方法的主要问题在于它们可能会突然更改,而且这可能是跨JVMs的非可移植解决方案。某些不变量可能无法得到遵守。然而,如果您只需要取消映射(unmap()),则可以考虑调用清理器(cleaner)。但是,我将解决如何跟踪slice()等问题。 - bestsss
获取内存转储并查找所有与有问题的那个地址相同的DirectByteBuffer,然后检查谁保留了引用,您就可以解决问题。 - bestsss

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接