内存总是不停的涨,查不到原因,莫名其妙。属于堆外内存,不好查。 做dump,发现有多个FileInputStream和FileOutputStream对象, <1>查看out reference,发现是程序中处理的文件。不过都已经close了,不知为何还存在。 <2>查看in referenct,发现被java.lang.ref.Finalizer持有。 ------------------------------ 搜了半天,整理如下: (1)FileInputStream和FileOutputStream重写了Object的finalize()方法。属于保底动作,关闭流的操作。 (2)创建一个FileInputStream对象(O1)时,同时会创建一个Finalizer对象(F1),F1会引用O1。 在发生GC时,即使O1已经关闭,没用了,仍然不会被直接回收,因为F1在引用它。 然后JVM发现此种情况,会将F1放入Finalizer的static的ReferenceQueue中。 (3)JVM中有一个名为“Finalizer”的线程,负责清理ReferenceQueue中的对象。清理的过程,就是执行O1的finalize方法,并同时从队列中移除。被清理之后,再没有引用指向实现了finalize方法对象了,那么在下一次GC的时候,此对象就可以被回收了 。 (4)即使代码中已经显式close了,仍然要走这个两次GC的流程,非常浪费。特别是大量文件读写的系统,将增加GC的时间。如果两次GC间隔很久,比如老年代,那么这些对象就堆积在内存中,造成内存上涨。 (5)名为“Finalizer”的线程,优先级很低,可能不会及时执行清理,或者干脆不执行清理。 (6)Java9,已经废弃了Object的finalize()方法,因为它可能造成内存回收性能问题,可能造成内存泄漏等问题。 https://docs.oracle.com/javase/9/docs/api/java/lang/Object.html#finalize-- (7)jdk12的新特性中移除了FileInputStream、FileOutputStream、Java.util.ZipFile/Inflator/Deflator的finalize方法。 ------------------------------ 解决方法: Java 7引入了两种创建流的方法, InputStream is = java.nio.file.Files.newInputStream(myfile.toPath()); OutputStream os = java.nio.file.Files.newOutputStream(myfile.toPath());
InputStream in = Files.newInputStream(Paths.get(filePath),StandardOpenOption.READ);
newOutputStream会自动加WRITE参数,所以就不用写了。
public OutputStream newOutputStream(Path path, OpenOption... options) throws IOException { int len = options.length; Set<OpenOption> opts = new HashSet<OpenOption>(len + 3); if (len == 0) { opts.add(StandardOpenOption.CREATE); opts.add(StandardOpenOption.TRUNCATE_EXISTING); } else { for (OpenOption opt: options) { if (opt == StandardOpenOption.READ) throw new IllegalArgumentException("READ not allowed"); opts.add(opt); } } opts.add(StandardOpenOption.WRITE); return Channels.newOutputStream(newByteChannel(path, opts)); }
当然,能升级到JDK12,自然就没问题了。 ------------------------------ 如何正确的关闭流: InputStream,OutputStream,Reader,Write这些都实现了Closeable接口。 (1)传统的finally大法 org.apache.commons.io.IOUtils try{ ....... ........ } finally { IOUtils.closeQuietly(is); IOUtils.closeQuietly(os); } (2)try-with-resources语法糖 Java7开始,可以使用 try-with-resources写法,是语法糖~~ 把这些流对象,放到try的括号里,编译时会自动编译成上面那种final的形式。 try (FileInputStream fis = new FileInputStream("../input/fxrates.txt"); FileOutputStream fos = new FileOutputStream("../output/fxrates.tx")) {
// code for reading contents
.....
} catch (IOException ioex) { ioex.printStackTrace(); }
反正这么改完,内存莫名其妙就不涨了,我也是百思不得其解啊~~ 按理说,调用close了,native内存也就释放了啊。 堆内存里有一些Finalizer对象,也占不了多少内存吧。 莫名其妙……
https://blog.csdn.net/weixin_41771503/article/details/79789086 这个说,嵌套的写法,要每层都显式关闭。 如果不行,回头我也试试。
|