Утечка памяти в java.util.ref.Finalizer во время ожидания потока финализатора - PullRequest
0 голосов
/ 22 февраля 2019

Анализируя дамп кучи, я ищу экземпляры класса java.lang.ref.Finalizer.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?

1 Ответ

0 голосов
/ 22 февраля 2019

Возможно, следующая тестовая программа прояснит это:

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-подобным системамonly

Другими словами, вы смотрите на потоки файлов, инкапсулированные в System.in и System.out, и, конечно, они никогда не будут собираться мусором, и обычно вы тоже этого не делаетевызовите close() для них.

Финализация не поддерживает какой-либо отказ, поэтому любой экземпляр класса с «нетривиальным finalize() методом» получит ссылку на финализатор при построении, дажекогда создатель знает, что объект никогда не будет завершен.

В самых последних версиях JDK для этой цели используется Cleaner, что позволяет не регистрировать очиститель при FileInputStreamили FileOutputStream создается с использованием существующего FileDescriptor, как в случае с stdin и stdout.Он также позволяет немедленную очистку и, следовательно, отмену регистрации в методе close(), не требуя какой-либо посмертной очистки для программ с хорошим поведением.

Так что в новейших версиях Java вы должны видеть только очистители для реально используемых потоковна свалке.

...