登录
注册
开源
企业版
高校版
搜索
帮助中心
使用条款
关于我们
开源
企业版
高校版
私有云
模力方舟
AI 队友
登录
注册
3月21日 深圳|OpenClaw 线下实战沙龙:招聘、资讯、项目协同三大场景实操,VS ZeroClaw 横向对比评测,别再只会装,来现场跑通真实业务!
代码拉取完成,页面将自动刷新
捐赠
捐赠前请先登录
取消
前往登录
扫描微信二维码支付
取消
支付完成
支付提示
将跳转至支付宝完成支付
确定
取消
Watch
不关注
关注所有动态
仅关注版本发行动态
关注但不提醒动态
24
Star
55
Fork
2
Java技术交流
/
Java技术提升库
代码
Issues
56
Pull Requests
0
Wiki
统计
流水线
服务
JavaDoc
PHPDoc
质量分析
Jenkins for Gitee
腾讯云托管
腾讯云 Serverless
悬镜安全
阿里云 SAE
Codeblitz
SBOM
我知道了,不再自动展开
更新失败,请稍后重试!
移除标识
内容风险标识
本任务被
标识为内容中包含有代码安全 Bug 、隐私泄露等敏感信息,仓库外成员不可访问
011、Java有几种文件拷贝方式?
待办的
#I1VYR1
wgy
成员
创建于
2020-09-20 20:15
我在专栏上一讲提到,NIO不止是多路复用,NIO2也不只是异步IO,今天我们来看看Java IO体系中,其他不可忽略的部分。 今天我要问你的问题是,Java有几种文件拷贝方式?哪一种最高效? ### 考点分析! 文件拷贝问题,从面试的角度来看,确实是一个面试考察的点,针对我上面的典型回答,面试官还可能会从实践角度,或者IO底层实现机制等方面进步提问。这一讲的内容从面试题出发,主要还是为了让你进一步加深对 Java IO类库设计和实现的了解。 从实践角度,我前面并没有明确说 NIO transfer的方案一定最快,真实情况也确实未必如此我们可以根据理论分析给出可行的推断,保持合理的怀疑,给出验证结论的思路,有时候面试官考察的就是如何将猜测变成可验证的结论,思考方式远比记住结论重要。 从技术角度展开,下面这些方面值得注意: - 不同的copy方式,底层机制有什么区别? - 为什么零拷贝(zero-copy)可能有性能优势? - Buffer分类与使用。 - Direct Buffer对垃圾收集等方面的影响与实践选择。 接下来,我们一起来分析一下吧。 ### 典型回答! Java有多种比较典型的文件拷贝实现方式,比如:利用 Java.IO类库,直接为源文件构建一个FileInputStream读取,然后再为目标文件构建一个FileOutputStream,完成写入工作。 ``` public static void copyFileByStream(File source, File dest) throws IOException { try (InputStream is = new FileInputStream(source); OutputStream os = new FileOutputStream(dest);) { byte[] buffer = new byte[1024]; int length; while ((length = is.read(buffer)) > 0) os.write(buffer, 0, length); } } ``` 或者,利用java.nio类库提供的 transferTo或 transferFrom方法实现。 ``` public static void copyFileByChannel(File source, File dest) throws IOException { try (FileChannel sourcechannel = new FileInputStream(source).getChannel(); FileChannel targetchannel = new FileOutputStream(dest).getChannel();) { for (long count = sourcechannel.size(); count > 0;) { long transferred = sourcechannel.transferTo( sourcechannel.position(), count, targetchannel); sourcechannel.position(sourcechannel.position() + transferred); count -= transferred; } } } ``` 当然,Java标准类库本身已经提供了几种 Files.copy的实现。 对于Copy的效率,这个其实与操作系统和配置等情況相关,总体上来说,NIO transferTo/From的方式可能更快,因为它更能利用现代操作系统底层机制,避免不必要拷贝和上下文切換。 ### 扩展知识! **1. 拷贝实现机制分析** 先来理解一下,前面实现的不同拷贝方法,本质上有什么明显的区别。 首先,你需要理解用户态空间(User Space)和内核态空间(Kernel Space),这是操作系统层面的基本概念,操作系统内核、硬件驱动等运行在内核态空间,具有相对高的特权;而用户态空间,则是给普通应用和服务使用。 操作系统基本概念: (1)用户态空间(UserSpace):提供普通应用和服务使用。 (2)内核态空间(Kernel Space):操作系统内核、硬件驱动等运行在内核态空间,具有较高特权。 当使用输入流、输出流进行读写时,实际进行多次上下文切换,如应用将读取数据时,执行如下流程: - 读操作:先将数据从磁盘复制到内核态空间,在从内核态空间复制用户态空间缓存 - 写操作:将数据从用户态空间缓存区域复制到内核态空间,再从内核态空间复制到磁盘,完成写入。 缺点:多次数据复制,带来一定额外开销,可能会降低IO效率 - 复制流程  所以,这种方式会带来一定的额外开销,可能会降低IO效率。 而基于 NIO transferTo的实现方式,在 Linux和Unix上,则会使用到零拷贝技术,数据传输并不需要用户态参与,省去了上下文切換的开销和不必要的内存拷贝,进而可能提高应用拷贝性能。注意, transferTo不仅仅是可以用在文件拷贝中,与其类似的,例如读取磁盘文件,然后进行 Socket发送,同样可以享受这种机制带来的性能和扩展性提高。 - transferTo的传输过程是:  **2. Java IO/NIO源码结构** 前面我在典型回答中提了第三种方式,即Java标准库也提供了文件拷贝方法 (java.nio.file.Files.copy)。如果你这样回答,就一定要小心了,因为很少有问题的答案是仅仅调用某个方法。从面试的角度,面试官往往会追问:既然你提到了标准库,那么它是怎么实现的呢?有的公司面试官以喜欢追问而出名,直到追问到你说不知道。 其实,这个句题的答案还真不是那么直观,因为实际上有几个不同的copy方法。 ``` public static Path copy(Path source, Path target, CopyOption...options) throws IOException ``` ``` public static long copy(InputStream in, Path target, CopyOption...options) throws IOException ``` ``` public static long copy(Path source, OutputStream out) throws IOException ``` 可以看到, copy不仅仅是支持文件之间操作,没有人限定输入输出流一定是针对文件的,这是 两个很实用的工具方法。 后面两种copy实现,能够在方法实现里直接看到使用的是 InputStream.transferTo(),你可以 直接看源码,其内部实现其实是stream在用户态的读写;而对于第一种方法的分析过程要相对 麻烦一些,可以参考下面片段。简单起见,我只分析同类型文件系统拷贝过程。 ``` public static Path copy(Path source, Path target, CopyOption... options) throws IOException { FileSystemProvider provider=provider(source); if(provider(target)==provider){ //same provider provider.copy(source, target, options);//这是本文分析的路径 }else{ //different providers Copymovehelper.copyToForeignTarget( source, target, options); } return target; } ``` 我把源码分析过程简单记录如下,JDK的源代码中,内部实现和公共API定义也不是可以能够筒单关联上的,NO部分代码甚至是定义为模板而不是Java源文件,在 build过程自动生成源码,下面顺便介绍一下部分JDK代码机制和如何绕过隐藏障碍。 - 首先,直接跟踪,发现FileSystemProvider只是个抽象类,阅读它的源码能够理解到,原来文件系统实际逻辑存在于JDK内部实现里,公共API其实是通过ServiceLoader机制加载一系列文件系统实现,然后提供服务。 - 我们可以在JDK源码里搜索 FileSystemProvider和nio,可以定位到sun/nio/fs,我们知道NIO底层是和操作系统紧密相关的,所以每个平台都有自己的部分特有文件系统逻辑。  - 省略掉ー些细节,最后我们一步步定位到 UnixFileSystemProvider→ UnixCopyFile.Transfer,发现这是个本地方法。 - 最后,明确定位到UnixCopyFile.c,其内部实现清楚说明竟然只是简单的用户态空间拷贝! 所以,我们明确这个最常见的copy方法其实不是利用 transferto,而是本地技术实现的用户态拷贝。 前面谈了不少机制和源码,我简单从实践角度总结一下,如何提高类似拷贝等1O操作的性能 有一些宽泛的原则: -在程序中,使用缓存等机制,合理减少IO次数(在网络通信中,如TCP传输, window大小也可以看作是类似思路)。 - 使用 transferto等机制,减少上下文切換和额外IO操作。 - 尽量减少不必要的转换过程,比如编解码;对象序列化和反序列化,比如操作文本文件或者网络通信,如果不是过程中需要使用文本信息,可以考虑不要将二进制信息转换成字符串直接传输二进制信息。 **3. 掌握 NIO Buffer** 我在上一讲提到Buffer是NIO操作数据的基本工具,Java为每种原始数据类型都提供了相应的Buffer实现(布尔除外),所以掌握和使用Buffer是十分必要的,尤其是涉及Direct Buffer等使用,因为其在垃圾收集等方面的特殊性,更要重点掌握。  Buffer有几个基本属性: - capcity,它反映这个Buffer到底有多大,也就是数组的长度。 - position,要操作的数据起始位置。 - limit,相当于操作的限额。在读取或者写入时,limit的意义很明显是不ー样的。比如,读取操作时,很可能将limit设置到所容纳数据的上限;而在写入时,则会设置容量或容量以下的可写限度。 - mark,记录上一次 postion的位置,默认是0,算是一个便利性的考虑,往往不是必须的。 前面三个是我们日常使用最频繁的,我简单梳理下Buffer的基本操作: - 我们创建了ー个ByteBuffer,准备放入数据, capcity当然就是缓冲区大小,而 position就是0,limit默认就是 capcity的大小。 - 当我们写入几个字节的数据时, position就会跟着水涨船高,但是它不可能超过 limit的大小。 -如果我们想把前面写入的数据读出来,需要调用flip方法,将 position设置为0,limit设置为以前的position那里。 - 如果还想从头再读一遍,可以调用rewind,让limit不变, position再次设置为0。 更进一步的详细使用,我建议参考相关教程。 **4. Direct Buffer和垃圾收集** 我这里重点介绍两种特别的 Buffer。 - Direct Buffer:如果我们看Buffer的方法定义,你会发现它定义了isDirect()方法;返回当前 Buffer是否是Direct类型。这是因为Java提供了堆内和堆外( Direct) Buffer,我们可 以以它的 allocate或者 allocateDirect方法直接创建。 - MappedByteBuffer:它将文件按照指定大小直接映射为内存区域,当程序访问这个内存区域时将直接操作这块儿文件数据,省去了将数据从内核空间向用户空间传输的损耗。我们可以使用FileChannel.map创建 MappedByteBuffer,它本质上也是种Direct Buffer。 在实际使用中,Java会尽量对 Direct Buffer仅做本地IO操作,对于很多大数据量的IO密集操作,可能会带来非常大的性能优势,因为: - Direct Buffer生命周期内内存地址都不会再发生更改,进而内核可以安全地对其进行访问,很多1O操作会很高效。 - 減少了堆内对象存储的可能额外维护工作,所以访问效率可能有所提高。 但是请注意, Direct Buffer创建和销毁过程中,都会比一般的堆内 Buffer增加部分开销,所以通常都建议用于长期使用、数据较大的场景。 使用 Direct Buffer,我们需要清楚它对内存和JVM参数的影响。首先,因为它不在堆上,所以Xmx之类参数,其实并不能影响Direct Buffer等堆外成员所使用的内存额度,我们可以使用下面参数设置大小: ``` -XX:MaxDirectMemorySize=512M ``` 从参数设置和内存问题排查角度来看,这意味着我们在计算Java可以使用的内存大小的时候,不能只考虑堆的需要,还有 Direct Buffer等一系列堆外因素。如果出现内存不足,堆外内存占用也是一种可能性。 另外,大多数垃圾收集过程中,都不会主动收集Direct Buffer,它的垃圾收集过程,就是基于我在专栏前面所介绍的Cleaner(一个内部实现)和幻象引用(Phantom Reference)机制,其本身不是 public类型,内部实现了ー个Deallocator负责销毁的逻辑。对它的销毁往往要拖到full GC的时候,所以使用不当很容易导致 OutOfMemoryError。 对于Direct Buffer的回收,我有几个建议: - 在应用程序中,显式地调用 System.gc)来强制触发。 - 另外一种思路是,在大量使用 Direct Buffer的部分框架中,框架会自己在程序中调用释放方法,Nett就是这么做的,有兴趣可以参考其实现(PlatformDependent0)。 - 重复使用 Direct Buffer。 **5. 跟踪和诊断Direct Buffer内存占用?** 因为通常的垃扱收集日志等记录,并不包含 Direct Buffer等信息,所以Direct Buffer内存诊断也是个比较头疼的事情。幸好,在JDK8之后的版本,我们可以方便地使用 Native Memory Tracking(NMT)特性来进行诊断,你可以在程序启动时加上下面参数: ``` -XX: NativeMemoryTracking={summary|detail} ``` 注意,激活NMT通常都会导致JyM出现5%~10%的性能下降,请谨慎考虑。 运行时,可以采用下面命令进行交互式对比: ``` //打印NMT信息 cmd <pid> VM.native_ memory detail //进行 baseline,以对比分配内存变化 jcmd <pid> VM.native memory baseline //进行 baseline,以对比分配内存变化 cmd <pid> VM.native_memory detail.diff ``` 我们可以在Internal部分发现 Direct Buffer内存使用的信息,这是因为其底层实际是利用 unsafe_ allocatememory。严格说,这不是JVM内部使用的内存,所以在JDK11以后,其实 它是归类在other部分里。 JDK9的输出片段如下,"+"表示的就是dif命令发现的分配变化: ``` -Internal(reserved=679KB +4KB, committed=679KB +4KB) (ma11oc=615KB+4KB#1571+4) (mmap: reserved=64KB, committed=64KB) ``` 注意:JVM的堆外内存远不止Direct Buffer,NMT输出的信息当然也远不止这些,我在专栏后面有综合分析更加具体的内存结构的主题。 今天我分析了Java IO/NIO底层文件操作数据的机制,以及如何实现零拷贝的高性能操作,梳理了Buffer的使用和类型,并针对Direct Buffer的生命周期管理和诊断进行了较详细的分析。
我在专栏上一讲提到,NIO不止是多路复用,NIO2也不只是异步IO,今天我们来看看Java IO体系中,其他不可忽略的部分。 今天我要问你的问题是,Java有几种文件拷贝方式?哪一种最高效? ### 考点分析! 文件拷贝问题,从面试的角度来看,确实是一个面试考察的点,针对我上面的典型回答,面试官还可能会从实践角度,或者IO底层实现机制等方面进步提问。这一讲的内容从面试题出发,主要还是为了让你进一步加深对 Java IO类库设计和实现的了解。 从实践角度,我前面并没有明确说 NIO transfer的方案一定最快,真实情况也确实未必如此我们可以根据理论分析给出可行的推断,保持合理的怀疑,给出验证结论的思路,有时候面试官考察的就是如何将猜测变成可验证的结论,思考方式远比记住结论重要。 从技术角度展开,下面这些方面值得注意: - 不同的copy方式,底层机制有什么区别? - 为什么零拷贝(zero-copy)可能有性能优势? - Buffer分类与使用。 - Direct Buffer对垃圾收集等方面的影响与实践选择。 接下来,我们一起来分析一下吧。 ### 典型回答! Java有多种比较典型的文件拷贝实现方式,比如:利用 Java.IO类库,直接为源文件构建一个FileInputStream读取,然后再为目标文件构建一个FileOutputStream,完成写入工作。 ``` public static void copyFileByStream(File source, File dest) throws IOException { try (InputStream is = new FileInputStream(source); OutputStream os = new FileOutputStream(dest);) { byte[] buffer = new byte[1024]; int length; while ((length = is.read(buffer)) > 0) os.write(buffer, 0, length); } } ``` 或者,利用java.nio类库提供的 transferTo或 transferFrom方法实现。 ``` public static void copyFileByChannel(File source, File dest) throws IOException { try (FileChannel sourcechannel = new FileInputStream(source).getChannel(); FileChannel targetchannel = new FileOutputStream(dest).getChannel();) { for (long count = sourcechannel.size(); count > 0;) { long transferred = sourcechannel.transferTo( sourcechannel.position(), count, targetchannel); sourcechannel.position(sourcechannel.position() + transferred); count -= transferred; } } } ``` 当然,Java标准类库本身已经提供了几种 Files.copy的实现。 对于Copy的效率,这个其实与操作系统和配置等情況相关,总体上来说,NIO transferTo/From的方式可能更快,因为它更能利用现代操作系统底层机制,避免不必要拷贝和上下文切換。 ### 扩展知识! **1. 拷贝实现机制分析** 先来理解一下,前面实现的不同拷贝方法,本质上有什么明显的区别。 首先,你需要理解用户态空间(User Space)和内核态空间(Kernel Space),这是操作系统层面的基本概念,操作系统内核、硬件驱动等运行在内核态空间,具有相对高的特权;而用户态空间,则是给普通应用和服务使用。 操作系统基本概念: (1)用户态空间(UserSpace):提供普通应用和服务使用。 (2)内核态空间(Kernel Space):操作系统内核、硬件驱动等运行在内核态空间,具有较高特权。 当使用输入流、输出流进行读写时,实际进行多次上下文切换,如应用将读取数据时,执行如下流程: - 读操作:先将数据从磁盘复制到内核态空间,在从内核态空间复制用户态空间缓存 - 写操作:将数据从用户态空间缓存区域复制到内核态空间,再从内核态空间复制到磁盘,完成写入。 缺点:多次数据复制,带来一定额外开销,可能会降低IO效率 - 复制流程  所以,这种方式会带来一定的额外开销,可能会降低IO效率。 而基于 NIO transferTo的实现方式,在 Linux和Unix上,则会使用到零拷贝技术,数据传输并不需要用户态参与,省去了上下文切換的开销和不必要的内存拷贝,进而可能提高应用拷贝性能。注意, transferTo不仅仅是可以用在文件拷贝中,与其类似的,例如读取磁盘文件,然后进行 Socket发送,同样可以享受这种机制带来的性能和扩展性提高。 - transferTo的传输过程是:  **2. Java IO/NIO源码结构** 前面我在典型回答中提了第三种方式,即Java标准库也提供了文件拷贝方法 (java.nio.file.Files.copy)。如果你这样回答,就一定要小心了,因为很少有问题的答案是仅仅调用某个方法。从面试的角度,面试官往往会追问:既然你提到了标准库,那么它是怎么实现的呢?有的公司面试官以喜欢追问而出名,直到追问到你说不知道。 其实,这个句题的答案还真不是那么直观,因为实际上有几个不同的copy方法。 ``` public static Path copy(Path source, Path target, CopyOption...options) throws IOException ``` ``` public static long copy(InputStream in, Path target, CopyOption...options) throws IOException ``` ``` public static long copy(Path source, OutputStream out) throws IOException ``` 可以看到, copy不仅仅是支持文件之间操作,没有人限定输入输出流一定是针对文件的,这是 两个很实用的工具方法。 后面两种copy实现,能够在方法实现里直接看到使用的是 InputStream.transferTo(),你可以 直接看源码,其内部实现其实是stream在用户态的读写;而对于第一种方法的分析过程要相对 麻烦一些,可以参考下面片段。简单起见,我只分析同类型文件系统拷贝过程。 ``` public static Path copy(Path source, Path target, CopyOption... options) throws IOException { FileSystemProvider provider=provider(source); if(provider(target)==provider){ //same provider provider.copy(source, target, options);//这是本文分析的路径 }else{ //different providers Copymovehelper.copyToForeignTarget( source, target, options); } return target; } ``` 我把源码分析过程简单记录如下,JDK的源代码中,内部实现和公共API定义也不是可以能够筒单关联上的,NO部分代码甚至是定义为模板而不是Java源文件,在 build过程自动生成源码,下面顺便介绍一下部分JDK代码机制和如何绕过隐藏障碍。 - 首先,直接跟踪,发现FileSystemProvider只是个抽象类,阅读它的源码能够理解到,原来文件系统实际逻辑存在于JDK内部实现里,公共API其实是通过ServiceLoader机制加载一系列文件系统实现,然后提供服务。 - 我们可以在JDK源码里搜索 FileSystemProvider和nio,可以定位到sun/nio/fs,我们知道NIO底层是和操作系统紧密相关的,所以每个平台都有自己的部分特有文件系统逻辑。  - 省略掉ー些细节,最后我们一步步定位到 UnixFileSystemProvider→ UnixCopyFile.Transfer,发现这是个本地方法。 - 最后,明确定位到UnixCopyFile.c,其内部实现清楚说明竟然只是简单的用户态空间拷贝! 所以,我们明确这个最常见的copy方法其实不是利用 transferto,而是本地技术实现的用户态拷贝。 前面谈了不少机制和源码,我简单从实践角度总结一下,如何提高类似拷贝等1O操作的性能 有一些宽泛的原则: -在程序中,使用缓存等机制,合理减少IO次数(在网络通信中,如TCP传输, window大小也可以看作是类似思路)。 - 使用 transferto等机制,减少上下文切換和额外IO操作。 - 尽量减少不必要的转换过程,比如编解码;对象序列化和反序列化,比如操作文本文件或者网络通信,如果不是过程中需要使用文本信息,可以考虑不要将二进制信息转换成字符串直接传输二进制信息。 **3. 掌握 NIO Buffer** 我在上一讲提到Buffer是NIO操作数据的基本工具,Java为每种原始数据类型都提供了相应的Buffer实现(布尔除外),所以掌握和使用Buffer是十分必要的,尤其是涉及Direct Buffer等使用,因为其在垃圾收集等方面的特殊性,更要重点掌握。  Buffer有几个基本属性: - capcity,它反映这个Buffer到底有多大,也就是数组的长度。 - position,要操作的数据起始位置。 - limit,相当于操作的限额。在读取或者写入时,limit的意义很明显是不ー样的。比如,读取操作时,很可能将limit设置到所容纳数据的上限;而在写入时,则会设置容量或容量以下的可写限度。 - mark,记录上一次 postion的位置,默认是0,算是一个便利性的考虑,往往不是必须的。 前面三个是我们日常使用最频繁的,我简单梳理下Buffer的基本操作: - 我们创建了ー个ByteBuffer,准备放入数据, capcity当然就是缓冲区大小,而 position就是0,limit默认就是 capcity的大小。 - 当我们写入几个字节的数据时, position就会跟着水涨船高,但是它不可能超过 limit的大小。 -如果我们想把前面写入的数据读出来,需要调用flip方法,将 position设置为0,limit设置为以前的position那里。 - 如果还想从头再读一遍,可以调用rewind,让limit不变, position再次设置为0。 更进一步的详细使用,我建议参考相关教程。 **4. Direct Buffer和垃圾收集** 我这里重点介绍两种特别的 Buffer。 - Direct Buffer:如果我们看Buffer的方法定义,你会发现它定义了isDirect()方法;返回当前 Buffer是否是Direct类型。这是因为Java提供了堆内和堆外( Direct) Buffer,我们可 以以它的 allocate或者 allocateDirect方法直接创建。 - MappedByteBuffer:它将文件按照指定大小直接映射为内存区域,当程序访问这个内存区域时将直接操作这块儿文件数据,省去了将数据从内核空间向用户空间传输的损耗。我们可以使用FileChannel.map创建 MappedByteBuffer,它本质上也是种Direct Buffer。 在实际使用中,Java会尽量对 Direct Buffer仅做本地IO操作,对于很多大数据量的IO密集操作,可能会带来非常大的性能优势,因为: - Direct Buffer生命周期内内存地址都不会再发生更改,进而内核可以安全地对其进行访问,很多1O操作会很高效。 - 減少了堆内对象存储的可能额外维护工作,所以访问效率可能有所提高。 但是请注意, Direct Buffer创建和销毁过程中,都会比一般的堆内 Buffer增加部分开销,所以通常都建议用于长期使用、数据较大的场景。 使用 Direct Buffer,我们需要清楚它对内存和JVM参数的影响。首先,因为它不在堆上,所以Xmx之类参数,其实并不能影响Direct Buffer等堆外成员所使用的内存额度,我们可以使用下面参数设置大小: ``` -XX:MaxDirectMemorySize=512M ``` 从参数设置和内存问题排查角度来看,这意味着我们在计算Java可以使用的内存大小的时候,不能只考虑堆的需要,还有 Direct Buffer等一系列堆外因素。如果出现内存不足,堆外内存占用也是一种可能性。 另外,大多数垃圾收集过程中,都不会主动收集Direct Buffer,它的垃圾收集过程,就是基于我在专栏前面所介绍的Cleaner(一个内部实现)和幻象引用(Phantom Reference)机制,其本身不是 public类型,内部实现了ー个Deallocator负责销毁的逻辑。对它的销毁往往要拖到full GC的时候,所以使用不当很容易导致 OutOfMemoryError。 对于Direct Buffer的回收,我有几个建议: - 在应用程序中,显式地调用 System.gc)来强制触发。 - 另外一种思路是,在大量使用 Direct Buffer的部分框架中,框架会自己在程序中调用释放方法,Nett就是这么做的,有兴趣可以参考其实现(PlatformDependent0)。 - 重复使用 Direct Buffer。 **5. 跟踪和诊断Direct Buffer内存占用?** 因为通常的垃扱收集日志等记录,并不包含 Direct Buffer等信息,所以Direct Buffer内存诊断也是个比较头疼的事情。幸好,在JDK8之后的版本,我们可以方便地使用 Native Memory Tracking(NMT)特性来进行诊断,你可以在程序启动时加上下面参数: ``` -XX: NativeMemoryTracking={summary|detail} ``` 注意,激活NMT通常都会导致JyM出现5%~10%的性能下降,请谨慎考虑。 运行时,可以采用下面命令进行交互式对比: ``` //打印NMT信息 cmd <pid> VM.native_ memory detail //进行 baseline,以对比分配内存变化 jcmd <pid> VM.native memory baseline //进行 baseline,以对比分配内存变化 cmd <pid> VM.native_memory detail.diff ``` 我们可以在Internal部分发现 Direct Buffer内存使用的信息,这是因为其底层实际是利用 unsafe_ allocatememory。严格说,这不是JVM内部使用的内存,所以在JDK11以后,其实 它是归类在other部分里。 JDK9的输出片段如下,"+"表示的就是dif命令发现的分配变化: ``` -Internal(reserved=679KB +4KB, committed=679KB +4KB) (ma11oc=615KB+4KB#1571+4) (mmap: reserved=64KB, committed=64KB) ``` 注意:JVM的堆外内存远不止Direct Buffer,NMT输出的信息当然也远不止这些,我在专栏后面有综合分析更加具体的内存结构的主题。 今天我分析了Java IO/NIO底层文件操作数据的机制,以及如何实现零拷贝的高性能操作,梳理了Buffer的使用和类型,并针对Direct Buffer的生命周期管理和诊断进行了较详细的分析。
评论 (
0
)
登录
后才可以发表评论
状态
待办的
待办的
进行中
已完成
已关闭
负责人
未设置
赵伟风
zhao-weifeng
负责人
协作者
+负责人
+协作者
标签
未设置
标签管理
里程碑
01.JavaSE阶段面试题
未关联里程碑
Pull Requests
未关联
未关联
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
未关联
未关联
master
开始日期   -   截止日期
-
置顶选项
不置顶
置顶等级:高
置顶等级:中
置顶等级:低
优先级
不指定
严重
主要
次要
不重要
参与者(1)
1
https://gitee.com/beike-java-interview-alliance/java-interview.git
git@gitee.com:beike-java-interview-alliance/java-interview.git
beike-java-interview-alliance
java-interview
Java技术提升库
点此查找更多帮助
搜索帮助
Git 命令在线学习
如何在 Gitee 导入 GitHub 仓库
Git 仓库基础操作
企业版和社区版功能对比
SSH 公钥设置
如何处理代码冲突
仓库体积过大,如何减小?
如何找回被删除的仓库数据
Gitee 产品配额说明
GitHub仓库快速导入Gitee及同步更新
什么是 Release(发行版)
将 PHP 项目自动发布到 packagist.org
评论
仓库举报
回到顶部
登录提示
该操作需登录 Gitee 帐号,请先登录后再操作。
立即登录
没有帐号,去注册