直接内存的原理 直接内存分配和释放 禁用显式垃圾回收

文章详细解释了直接内存如何用于IO操作以提高效率,避免了传统IO的用户态到内核态转换以及额外的数据复制。直接内存通过Unsafe类进行分配和释放,使用Cleaner(虚引用)确保在对象被垃圾回收时释放内存,防止内存溢出。禁用显式垃圾回收可以影响直接内存的性能管理。

直接内存

如何应用直接内存进行IO操作

使用传统方式和分配直接内存的方式进行IO操作
传统IO使用了大概3s,而byteBuffer只用了不到1s
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

直接内存的原理

传统文件读写的原理如下
文件读写开始,CPU 的状态就从 用户态转换到内核态,内存中也会划出一块区域作为系统缓冲区,用来缓存文件数据。Java想要读写文件,就要把系统缓冲区内的内容再读到Java缓冲区,然后再进行读写。
在这里插入图片描述
直接内存的原理

而当Java调用了ByteBuffer.allocateDiret() 方法时,会直接分配操作系统的内存。和原本的系统内存缓冲区不同的是,这块内存Java可以直接操作,是操作系统代码和Java代码共享的一块区域。
这样需要读写文件的时候,系统代码会将磁盘文件读取到直接内存,而Java程序也可以从直接内存中直接读取文件,少了一步复制的步骤。因此,读写文件的速度就得到了成倍的提升。
在这里插入图片描述

直接内存的内存溢出

直接内存虽然不处于JVM内部,但仍处于 内存内部,也会有内存溢出的危险。

程序中,循环分配直接内存的空间100M,然后使用list进行引用,使其不会成为垃圾
在这里插入图片描述

在这里插入图片描述
循环了36次之后内存溢出

直接内存如何进行分配和释放

现在看一个程序,来展示一下直接内存的分配和释放

程序中,直接使用ByteBuffer.allocateDirect() 函数分配了1GB内存,然后系统等待用户输入,这时我们检查内存占用,发现多了1GB
在这里插入图片描述

释放直接内存时,直接设置变量为null,让直接内存失去引用。过一会查看内存占用,发现之前的1GB占用不见了,说明1GB的资源被释放了。

但是直接内存是不受JVM的管理的,那么直接内存的分配和释放的原理是什么呢

直接内存分配和释放的原理

直接内存的分配和回收其实是通过一个叫Unsafe 的类对象完成的,这个类对象不推荐程序员平时使用。
unsafe
执行效果
在这里插入图片描述

在这里插入图片描述

那么ByteBuffer 和 Unsafe 是怎么联系到一起的呢?
直接查看ByteBuffer的构造器,发现直接调用了Unsafe对象。调用unsafe的allocateMemory() 方法分配内存,而最后使用了Cleaner对象释放了内存。
byteBuffer构造器
Clean 是如何进行释放内存的呢?

看函数,Cleaner创建了一个回调任务,在create方法中,关联了当前对象this(DirectByteBuffer对象),并定义了回调对象Deallocator。这个Deallocator是一个Runnable对象,被调用时会执行run()方法,而在这个方法中,我们可以看到unsafe对象的freeMemory方法。

而Cleaner又是什么呢,它是一个虚引用对象(实现了PhantomReferrence)。虚引用对象的特点是,当关联的对象被垃圾回收之后,自身就会调用clean() 方法。而在这个clean() 方法中,我们看到它调用了Deallocator 的run() 方法。后台有一个线程一直会检查虚引用对象,并使虚引用对象做这些事。

在这里插入图片描述

在这里插入图片描述

总的来说就是

  • 使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
  • ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调用 freeMemory 来释放直接内存

禁用显式垃圾回收

可以在启动Java程序的时候,加上参数-XX:+DisableExplicitGC 就可以禁用显式垃圾回收,也就是 禁用程序员直接使用System.gc() 来触发垃圾回收。
这样做的目的一般是为了检测程序在内存占用的性能。
比如:
在这里插入图片描述
这段代码中,只要Java对象不被垃圾回收,直接内存也不会进行释放。这样就会影响直接内存的性能。这种时候,我们就直接使用unsafe来freeMemory即可。

Buffer内存管理实战技巧 # Java Buffer内存管理:NIO与直接内存的高效I/O优化 在Java应用开发中,Buffer内存管理是提升I/O性能的关键,尤其在处理大文件网络通信时。本文件深入探讨了NIO Buffer、Buffered流系列直接内存的优化技巧,通过实战代码策略分析,帮助开发者减少系统调用、降低内存开销,并提高并发性能。 NIO Buffer用于通道读写,通过直接Buffer分配在堆外内存,减少JVM堆与操作系统内核间的数据复制。优化策略包括选择合适的Buffer大小(通常8KB-64KB)、复用Buffer避免频繁创建、正确使用flip()clear()切换模,以及批量读写减少系统调用。例如,在文件复制场景中,使用ByteBuffer.allocateDirect结合FileChannel,著提升I/O效率。BufferedInputStreamBufferedOutputStream通过内存缓冲区批量处理数据,减少I/O次数,设置合适的缓冲区大小(如32KB),并利用try-with-resources确保资源关闭,避免内存泄漏。 BufferedReaderBufferedWriter优化字符流操作,使用readLine()批量读取行数据,减少字符串拼接开销。Java 7+的Files类提供更简洁的批量API,如readAllLineswrite,适用于中等大小文件处理。直接内存操作中,ByteBuffer.allocateDirect适用于频繁I/O场景,但需释放以避免内存累积,通过sun.nio.ch.DirectBuffer的cleaner()方法手动清理。 通用优化策略包括根据场景选择Buffer类型(字节流用NIO或Buffered流、字符流用BufferedReader、频繁I/O用直接Buffer)、合理设置Buffer大小、减少内存复制(使用slice()或duplicate()共享数据)、复用Buffer及时释放资源。最佳实践强调优先使用NIO Buffer、监控内存使用、避免过度优化,并通过性能测试调整策略。本文件以代码示例对比分析,为Java开发者提供全面的Buffer管理指南,助力构建高性能应用。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值