登录
注册
开源
企业版
高校版
搜索
帮助中心
使用条款
关于我们
开源
企业版
高校版
私有云
模力方舟
AI 队友
登录
注册
代码拉取完成,页面将自动刷新
捐赠
捐赠前请先登录
取消
前往登录
扫描微信二维码支付
取消
支付完成
支付提示
将跳转至支付宝完成支付
确定
取消
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
分支
未关联
分支 (
-
)
标签 (
-
)
开始日期   -   截止日期
-
置顶选项
不置顶
置顶等级:高
置顶等级:中
置顶等级:低
优先级
不指定
严重
主要
次要
不重要
参与者(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 帐号,请先登录后再操作。
立即登录
没有帐号,去注册