缓冲区溢出
了解Java缓冲池
缓冲池空间位于垃圾收集器管理的内存之外。 这是分配本地堆外内存的一种方法。 使用缓冲池有什么好处? 为了回答这个问题,让我们首先了解什么是字节缓冲区。
字节缓冲区
非直接缓冲区
ByteBuffer
类附带了java.nio
包。 它允许我们分配直接和非直接字节缓冲区。 非直接字节缓冲区没有什么特别的-它们是由ByteBuffer.allocate()
和ByteBuffer.wrap()
工厂方法创建的HeapByteBuffer
的实现。 就像该类的名称所暗示的那样,它们是堆上的字节缓冲区。 那么,在Java堆空间上分配所有缓冲区会不会更容易? 为什么有人需要在本机内存中分配某些内容? 要回答这个问题,我们需要了解操作系统如何执行I / O操作。 任何读或写指令都在连续的字节存储区上执行。 那么byte[]
是否在堆上占据了连续的空间? 尽管从技术上讲是有意义的,但JVM规范没有这样的保证。 更有趣的是,规范甚至不能保证堆空间本身是连续的! 尽管JVM不太可能将一维原语数组放置在内存中的不同位置,但是Java堆空间中的字节数组不能直接用于本机I / O操作。 必须在每次I / O之前将其复制到本机内存,这显然会导致效率低下。 因此,引入了直接缓冲区。
直接缓冲
直接缓冲区是与Java共享的本机内存块,您可以从中执行直接读取。 可以使用ByteBuffer.allocateDirect()
工厂方法创建DirectByteBuffer
的实例。 字节缓冲区是执行I / O操作的最有效方法,因此,它们在许多库和框架中使用,例如在Netty中。
内存映射缓冲区
直接字节缓冲区也可以通过将文件区域直接映射到内存中来创建。 换句话说,我们可以将文件的区域加载到可以稍后访问的特定本机内存区域。 您可以想象,如果我们需要多次读取文件内容,它可以显着提高性能。 多亏了内存映射文件,后续读取将使用内存中文件的内容,而不是每次需要时都从光盘加载数据。 MappedByteBuffer
可以通过创建FileChannel.map()
方法。
内存映射文件的另一个优点是,当系统关闭时,操作系统可以直接将缓冲区刷新到磁盘。 此外,操作系统可以锁定文件的映射部分,使其免受计算机上其他进程的影响。
分配很贵
直接缓冲区的问题之一是分配它们很昂贵。 无论缓冲区的大小如何,调用ByteBuffer.allocateDirect()
都是一个相对较慢的操作。 因此,对于大型和长寿命的缓冲区使用直接缓冲区,或者创建一个大型缓冲区,按需分割部分,并在不再需要它们时将它们返回以重新使用,效率更高。 当切片的大小不总是相同时,可能会出现切片问题。 当分配和释放不同大小的对象时,初始的大字节缓冲区可能会变得碎片化。 与Java堆不同,直接字节缓冲区无法压缩,因为它不是垃圾收集器的目标。
监视缓冲池的使用
如果您对应用程序使用的直接或映射的字节缓冲区的数量感兴趣,则可以使用许多工具(包括VisualVM(带有BufferMonitor插件)和FusionReactor)轻松监视它们。 Java将根据需要增加缓冲池,因此“已使用的直接内存”涵盖了下图的“直接容量”这一事实,这意味着到目前为止分配的所有缓冲内存都在使用中。
请注意–您可以使用-XX:MaxDirectMemorySize=N
标志来限制应用程序可以分配的直接字节缓冲区空间量。 尽管这是可能的,但是您将需要一个很好的理由。
翻译自: https://www.javacodegeeks.com/2018/03/understanding-java-buffer-pool.html
缓冲区溢出