可参考: MappedByteBuffer以及ByteBufer的底层原理
Bytebuffer分为两种:间接地和直接的,所谓直接就是指MappedByteBuffer,直接使用内存映射(java的话就意味着在JVM之外分配虚拟地址空间);而间接的ByteBuffer是在JVM的堆上面的。间接缓冲区就是我们通常说的堆缓冲区。
直接缓冲区 java内部是使用 DirectByteBuffer 来实现的。
堆缓冲区java内部是使用 HeapByteBuffer 来实现的。
映射的字节缓冲区(MappedByteBuffer ) 不提供关闭或销毁方法。也就是说,创建完直接缓冲区,就一直有效,直到缓冲区本身被垃圾收集。
映射字节缓冲区的内容可以在任何时间改变,例如,如果映射的文件的对应区域的内容由该程序或其他程序改变。无论这种变化是否发生,当它们发生时,都是依赖于 *** 作系统的,因此不明确。
映射的字节缓冲区的全部或部分可能在任何时间变得不可访问,例如映射的文件被截断。试图访问映射字节缓冲区的不可访问区域不会改变缓冲区的内容,并且会导致在访问时或稍后某个时间抛出一个未指定桥行饥的异常。因此,强烈建议采取适当的预防措施,以避免由该程序或由同时运行的程序 *** 纵映射文件,除了读取或写入文件的内容。
MappedByteBuffer 继承了 ByteBuffer 。
load( )方法会整个文件加载到内存中。
此方法尽最大努力确保当它返回时,该缓冲区的内容驻留在物理内存中。调用此方法可能会导致一些页面错误和I/O *** 作发生。
*** 作系统会采用虚拟内存映射,把缓冲区和文件建立虚拟内存映射。此映射使得 *** 作系统的底层虚拟内存子系统可以根据需要将文件中相应区块的数据读进内存。带猛已经在内存中或通过验证的页会占用实际内存空间,并且在它们被读进 RAM 时会挤出最近较少使用的其他内存页。(swap in,swap out)
在一个映射缓冲区上调用 load( )方法会是一个代价高的 *** 作,因为它会导致大量的页调入(page-in),具体数量取决于文件中被映射区域的实际大小。然而,load( )方法返回并不能保证文件就会完全加载到内存,这是由于请求页面调入是动态的。具体结果会因某些因素而有所差异,这些因素包括: *** 作系统、文件系统,可用 Java 虚拟机内存,最大 Java 虚拟机内存,垃圾收集器实现过程等等。
请小心使用 load( )方法,它可能会导致您不希望出现的结果。该方法的主要作用是为提前加载文件埋单,以便后续的访问速度可以尽可能的快。
对于那些要求近乎实时访问的程序,解决方案就是预加载。但是请记住,不能保证全部页都加载到内存,不管怎样,之后可能还会有页调入发生( *** 作系统自己维护,依赖 *** 作系统的实现)。内存页什么时候swap in 和 swap out 受多个因素影响,这些因素中的许多都是不受 Java 虚拟机控制的。
JDK 1.4 的 NIO 并没有提供一个可以把页面固定到物理内存上的API,尽管一些 *** 作系统是支持这样做的。对于大多数程序,特别是交互性的或其他事件驱动(event-driven)的程序而言,为提前加载文件消耗资源是不划算的。在实际访问时分摊页调入开销才是更好的选择。让 *** 作系统根据需要来调入页意味着不访问的页永远不需要被加载。同预加载整个被映射的文件相比,这很容易减少 I/O 活动总次数。 *** 作系统已经有一个复杂的内存管理系统了,就让它来替您完成此工作吧!
我们可以通过调用 isLoaded( )方法来判断一个被映射的文件是否完全加载内存了。
如果该方法返回ture,意味着该缓冲区中的所有数据很可能完全加载到物理内存中了,因此可以在不产生任何虚拟内存页错误或I/O *** 作的情况下访问。
不过,该方法返回false,并不一定意味着缓冲区的内容没有加载到物理内存中。
返回值是一个提示,而不是一个保证,因为底层 *** 作系统在调用该方法返回的时候可能已经分出了一些缓冲区的数据。
该敏返方法会强制将此缓冲区上的任何更改写入映射到永久磁盘存储器上。
如果映射到该缓冲区的文件驻留在本地存储设备上,那么当该方法返回时,它保证对创建的缓冲区进行的所有更改,或者自上次调用该方法后,将被写入该设备。
如果文件不驻留在本地设备上,则不提供这样的保证。
当用 MappedByteBuffer 对象来更新一个文件,您应该总是使用 MappedByteBuffer.force( )而非 FileChannel.force( ),因为通道对象可能
不清楚通过映射缓冲区做出的文件的全部更改。MappedByteBuffer 没有不更新文件元数据的选项——元数据总是会同时被更新的。
如果映射是以 MapMode.READ_ONLY 或 MAP_MODE.PRIVATE 模式建立的,那么调用 force( )
方法将不起任何作用,因为永远不会有更改需要应用到磁盘上(但是这样做也是没有害处的)。
喜欢本文的朋友们,欢迎长按下图关注订阅号 java404 ,收听更多精彩的内容
JDK1.4中加入了一个新的包:NIO(java.nio.*).这个库最大的功能就是增加了对异步套接字的支持异步套接字对服务器程序来说更具吸引力.一般同步SOCKET服务器的实现都是采用线程池来处理客户请求的,基于请求超时时间和并发线程数目的限制,如果并发处理能力能够达到上千就已经是不错了.异步服务器的能力则至少是它的数倍(有人测试一个简单的ECHO服务程序,说可以达到上万个并发,不知道是否真的能达到). SocketChannel的读写是通过一个类叫ByteBuffer(java.nio.ByteBuffer)来 *** 作的.这个类本身的设计是不错的,比直接 *** 作byte[]方便多了. ByteBuffer有两种模式:直接/间接.间接模式最典型(也只有这么一种)的就是HeapByteBuffer,即 *** 作堆内存(byte[]).但是内存毕竟有限,如果我要发送一个1G的文件怎么办?不可能真的去分配1G的内存.这时就必须使用"直接"模式,即MappedByteBuffer,文件映射. MappedByteBuffer也是类似的,你可以把整个文件(不管文件有多大)看成是一个ByteBuffer.这是一个很好的设计,除了一点,令人头疼的一点. MappedByteBuffer只能通过调用FileChannel的map()取得,再没有其他方式.但是令人奇怪的是,SUN提供了map()却没有提供unmap().这样会导致什么后果呢? 举个例子,文件test.tmp是一个临时构建胡旅唯的文件,在业务处理(通过SocketChannel发送)完之后将不再有效.一般的做法都是这样的: (1)File file = new File("test.tmp")FileInputStream in = new FileInputStream(file)FileChannel ch = in.getChannel()MappedByteBuffer buf = ch.map(FileChannel.MapMode.READ_ONLY, 0, file.length())(2)SocketChannel sch = 已经构造好了while (buf.hasRemaining()) sch.write(buf)(3)ch.close()in.close()file.delete()上面的 *** 作都会正常的完成,除了最后一步:文件无法删除!即使你通过资源管理器直接强制删除也不行,说"文件正在使用". 为什么会出现这种情况? 说"文件正在使用",说明文件句柄没有清零,还有在使用它的地方---就是被MappedByteBuffer占用镇则了!尽管FileChannel,FileInputStream都已经关闭了,但是在map里还打开着一个文件句柄.但是在外部看不见也无法 *** 作它.那么这个句柄在什么时候才会正常地关闭呢?根据JAVADOC的说明,是在垃圾收集的时候.而众所周知垃圾收集是程序根本无法控制的. 既然MappedByteBuffer是从FileChannel中map()出来的,为什么它又不提供unmap()呢?SUN自己也没有讲清楚为什么.O'Reilly的<>中说是因为"安全"的原因,但是到底unmap()会怎么不安全,作者也没有讲清楚. 在SUN的BUG库中,这个问题在02年就有人提交了BUG报告,但是SUN自己不认为是BUG,而只是一个RFE(Request For Enhancement),有待改进. 好在网上牛人多.在BUG报告( http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4724038)中,有网友提出了一个解决的办法(具体参看上面的URL),可行,至少我在WINDOWS2000下测试是可以的.唯一的不足是并不是每裤培次都能马上生效(文件彻底被删除),有的时候要延迟一会再试. 再抱怨两句.对于网友们的BUG报告,SUN似乎不怎么重视.粗看一下上面的BUG报告,会发现居然上世纪90年代的报告还赫然在列.有兴趣的朋友不妨仔细研究研究. 还有一点忘了说了.ByteBuffer是无法派生的.因为这个抽象类中定义了几个包抽象方法,即实现类只能位于java.nio包中.本来自己实现MappedByteBuffer也不难,只是效率比SUN实现的肯定要低一些.毕竟后者是可以直接与 *** 作系统打交道的.而要是自己实现的化,只能通过一个中间的堆缓冲区进行过渡.欢迎分享,转载请注明来源:内存溢出
评论列表(0条)