Finalizer 线程等待时 java.util.ref.Finalizer 的内存泄漏

Memory leak of java.util.ref.Finalizer while Finalizer thread is waiting

分析堆转储我寻找 java.lang.ref.Finalizer class 的实例。 java.lang.ref.Finalizer有'next'和'prev'成员字段,用于维护链表。我总是将 FileInputStream 作为列表的尾部,将 FileOutputStream 作为它的前一个条目(分析了几个堆转储)。 FileInputStream 和 FileOutputStream 的文件描述符分别始终为 0 和 1:

+---[Pending Finalization] java.lang.ref.Finalizer           
| |                                                          
| +---queue  java.lang.ref.ReferenceQueue [Stack Local]      
| |                                                          
| +---referent  java.io.FileInputStream                     
| | |                                                        
| | +---closed = boolean false                               
| | |                                                        
| | +---closeLock  java.lang.Object                          
| | |                                                        
| | +---fd  java.io.FileDescriptor                           
| |   |                                                      
| |   +---closed = boolean false                             
| |   |                                                      
| |   +---fd = int 0                                         
| |   |                                                       
| |   +---parent  java.io.FileInputStream                    
| |                                                          
| +---prev  [Pending Finalization] java.lang.ref.Finalizer   
|   |                                                        
|   +---queue  java.lang.ref.ReferenceQueue [Stack Local]    
|   |                                                        
|   +---next  [Pending Finalization] java.lang.ref.Finalizer 
|   |                                                        
|   +---referent  java.io.FileOutputStream                   
|   | |                                                      
|   | +---append = boolean false                             
|   | |                                                      
|   | +---closed = boolean false                             
|   | |                                                       
|   | +---closeLock  java.lang.Object                        
|   | |                                                     
|   | +---fd  java.io.FileDescriptor                         
|   |   |                                                    
|   |   +---closed = boolean false                           
|   |   |                                                    
|   |   +---fd = int 1  0x00000001                           
|   |   |                                                    
|   |   +---parent  java.io.FileOutputStream                 
|   |                                                         
|   +---prev  [Pending Finalization] java.lang.ref.Finalizer 
  1. 为什么FileInputStream和FileOutputStream总是在ReferenceQueue的尾部?
  2. 它们不是由垃圾收集器收集的,因为我只观察到分配失败 GC 而不是 Full GC 发生吗?
  3. 为什么描述符总是 0 和 1?

也许下面的测试程序会对此有所启发:

Field fd = FileDescriptor.class.getDeclaredField("fd");
fd.setAccessible(true);
System.out.println("stdin:  "+fd.get(FileDescriptor.in));
System.out.println("stdout: "+fd.get(FileDescriptor.out));
System.out.println("stderr: "+fd.get(FileDescriptor.err));
stdin:  0
stdout: 1
stderr: 2

Ideone,请注意,对于 JDK 8,这仅适用于类 Unix 系统

换句话说,您正在查看由 System.inSystem.out 封装的文件流,当然,这些文件流永远不会被垃圾收集,通常您也不会调用close() 在他们身上。

终结器不支持任何类型的选择退出,因此任何具有“非平凡 finalize() 方法”的 class 实例都将在构造时获得终结器引用,即使创建者知道该对象永远不会最终确定。

最新的 JDK 版本为此目的使用了 Cleaner,当使用现有的 FileInputStreamFileOutputStream 构造时,允许不注册清洁器FileDescriptor,stdin 和 stdout 就是这种情况。它还允许立即清理并因此在 close() 方法中注销,不需要任何 post- 运行良好的程序的 mortem 清理。

因此,对于最新的 Java 版本,您应该只会看到堆转储中实际使用的流清理器。