Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Вы всё ещё используете finalize()? Тогда мы идё...

Вы всё ещё используете finalize()? Тогда мы идём к вам

Финализация существует в Java с самого её появления. В то время как этот механизм может с первого взгляда казаться очень удобным, у него есть существенные проблемы, например, он может замедлить сборку мусора или привести к взаимным блокировкам. Поэтому в Java 9 было принято решение пометить finalize() как deprecated. В докладе рассказано, какие альтернативы есть у финализации и как их правильно использовать. Затронуты такие темы как достижимость, виды ссылок и Cleaner. Доклад рассчитан на широкую аудиторию, включая начинающих разработчиков, но может оказаться полезным и для опытных программистов.

Avatar for Zheka Kozlov

Zheka Kozlov

June 27, 2018
Tweet

More Decks by Zheka Kozlov

Other Decks in Programming

Transcript

  1. 109 имплементаций в JDK 8 • java.io.FileOutputStream • java.io.FileInputStream •

    java.util.zip.{ZipFile, Inflater, Deflater} • java.awt.Graphics • javax.imageio.spi.ServiceRegistry • ... 4/84
  2. FileOutputStream public class FileOutputStream extends OutputStream { public FileOutputStream(String fileName)

    { … } @Override public void close() throws IOException { … } } 6/84
  3. try-finally OutputStream stream = new FileOutputStream(…); try { // Do

    some work … } finally { stream.close(); } 7/84
  4. No warning class MyFile implements Closeable { private OutputStream stream1,

    stream2; MyFile() throws IOException { stream1 = new FileOutputStream(…); stream2 = new FileOutputStream(…); } @Override public void close() throws IOException { if (stream1 != null) stream1.close(); if (stream2 != null) stream2.close(); } } 11/84
  5. No warning class MyFile implements Closeable { private OutputStream stream1,

    stream2; MyFile() throws IOException { stream1 = new FileOutputStream(…); stream2 = new FileOutputStream(…); //BANG! } @Override public void close() throws IOException { if (stream1 != null) stream1.close(); if (stream2 != null) stream2.close(); } } 12/84
  6. No warning class MyFile implements Closeable { private OutputStream stream1,

    stream2; MyFile() throws IOException { stream1 = new FileOutputStream(…); stream2 = new FileOutputStream(…); //BANG! } @Override public void close() throws IOException { if (stream1 != null) stream1.close(); //BANG! if (stream2 != null) stream2.close(); } } 13/84
  7. Object.finalize() public class Object { … /** * Called by

    the garbage collector on an * object when garbage collection determines * that there are no more references to the * object. */ protected void finalize() throws Throwable { } } 14/84
  8. FileOutputStream.finalize() public class FileOutputStream extends OutputStream { … @Override public

    void close() throws IOException { … } @Override protected void finalize() throws IOException { if (…) { close(); } } } 15/84
  9. Deprecated in Java 9+ public class Object { … /**

    * @deprecated The finalization mechanism is * inherently problematic. Finalization can lead * to performance issues, deadlocks, and hangs. * … */ @Deprecated(since="9") protected void finalize() throws Throwable { } } 16/84
  10. Deprecated in Java 9+ The finalization mechanism is inherently problematic.

    Finalization can lead to performance issues, deadlocks, and hangs. Errors in finalizers can lead to resource leaks; there is no way to cancel finalization if it is no longer necessary; and no ordering is specified among calls to {@code finalize} methods of different objects. Furthermore, there are no guarantees regarding the timing of finalization. The {@code finalize} method might be called on a finalizable object only after an indefinite delay, if at all. 17/84
  11. Performance class Test { static final AtomicInteger COUNT = new

    AtomicInteger(); private byte[] array = new byte[1024 * 1024]; void doAction() { COUNT.incrementAndGet(); } } 18/84
  12. Performance public class Main { public static void main(String[] args)

    { for (int i = 0; i < 10_000; i++) { new Test().doAction(); } System.out.println(Test.COUNT); } } 19/84
  13. Performance (with finalize) class Test { static final AtomicInteger COUNT

    = new AtomicInteger(); static final AtomicInteger COUNT2 = new AtomicInteger(); private byte[] array = new byte[1024 * 1024]; void doAction() { COUNT.incrementAndGet(); } @Override protected void finalize() { COUNT2.incrementAndGet(); } } 21/84
  14. Performance (with finalize) public class Main { public static void

    main(String[] args) { for (int i = 0; i < 10_000; i++) { new Test().doAction(); } System.out.println(Test.COUNT); System.out.println(Test.COUNT2); } } 22/84
  15. Comparison 0 1 2 3 4 Without finalize With finalize

    Execution time, s 100% 330% 25/84
  16. Логи GC [0.012s][info][gc] Using Parallel … [0.543s][debug][gc,phases,ref] GC(4) Reference Processing:

    13.5ms [0.543s][debug][gc,phases,ref] GC(4) FinalReference: 13.5ms [0.543s][debug][gc,phases,ref] GC(4) Discovered: 63 [0.543s][debug][gc,phases,ref] GC(4) Cleared: 1 [0.543s][info ][gc ] GC(4) Pause Young (Allocation Failure) 129M->128M(245M) 16.455ms … [0.663s][debug][gc,phases,ref] GC(10) Reference Processing: 12.1ms [0.663s][debug][gc,phases,ref] GC(10) FinalReference: 12.1ms [0.663s][debug][gc,phases,ref] GC(10) Discovered: 63 [0.663s][debug][gc,phases,ref] GC(10) Cleared: 1 [0.664s][info ][gc ] GC(10) Pause Young (Allocation Failure) 129M->128M(245M) 20.798ms … 29/84
  17. Resurrection class Test { static Test test; … @Override protected

    void finalize() { Test.test = this; } } 38/84
  18. Решение • Использовать Files.newOutputStream(…) / Files.newInputStream(…) • Переключиться на Java

    10: public class FileOutputStream extends OutputStream { … @Override @Deprecated(since="9", forRemoval=true) protected void finalize() throws IOException { } } 42/84
  19. Другие недостатки finalize • Ошибки реализации finalize() могут привести к

    катастрофическим последствиям (e.g. OutOfMemoryError) 43/84
  20. Другие недостатки finalize • Ошибки реализации finalize() могут привести к

    катастрофическим последствиям (e.g. OutOfMemoryError) @Override protected void finalize() { … Thread.sleep(100_000); } 44/84
  21. Другие недостатки finalize • В подклассах легко забыть сделать вызов

    super.finalize() @Override protected void finalize() { try { … } finally { super.finalize(); } } 45/84
  22. Другие недостатки finalize • Нельзя отменить финализацию @Override public void

    close() { … System.cancelFinalization(this); } Такого метода нет! 47/84
  23. Другие недостатки finalize • Нельзя сделать finalize() опциональным (он либо

    есть, либо нет) public class FileOutputStream extends OutputStream { private final AltFinalizer altFinalizer; public FileOutputStream(…) { if (needFinalizer) { altFinalizer = new AltFinalizer(this); } } static class AltFinalizer { @Override protected void finalize() {…} } 49/84
  24. Пакет java.lang.ref public abstract class Reference<T> { private T referent;

    // Treated specially by GC … Reference(T referent) { this(referent, null); } Reference(T referent, ReferenceQueue<T> queue) { … } } 51/84
  25. WeakReference public void f() { var obj = new Object();

    var ref = new WeakReference<>(obj); System.gc(); System.out.println(ref.get()); } 55/84
  26. PhantomReference public class Resource implements Closeable { static class ResourceCleaner

    extends PhantomReference<Resource> { ResourceCleaner(Resource res) { super(res, QUEUE); REFERENCES.add(this); } void clean() { // Cleaning action here REFERENCES.remove(this); } } private static final Set<ResourceCleaner> REFERENCES = Collections.newSetFromMap(new ConcurrentHashMap<>()); private static final ReferenceQueue<Resource> QUEUE = new ReferenceQueue<>(); private final ResourceCleaner cleaner = new ResourceCleaner(this); @Override public void close() { cleaner.clean(); } } 59/84
  27. PhantomReference public class Resource implements Closeable { … static {

    Thread thread = new Thread(() -> { while (true) { try { ResourceCleaner cleaner = (ResourceCleaner) QUEUE.remove(); cleaner.clean(); } catch (InterruptedException e) { } } }); thread.setDaemon(true); thread.start(); } } 60/84
  28. PhantomReference public class Resource implements Closeable { … static {

    Thread thread = new Thread(() -> { while (true) { try { ResourceCleaner cleaner = (ResourceCleaner) QUEUE.remove(); cleaner.clean(); } catch (InterruptedException e) { } } }); thread.setDaemon(true); thread.start(); } } 61/84
  29. FinalizablePhantomReference public abstract class FinalizablePhantomReference<T> extends PhantomReference<T> { protected FinalizablePhantomReference(

    T referent, FinalizableReferenceQueue queue) { super(referent, queue.queue); … } public abstract void finalizeReferent(); } 62/84
  30. FinalizablePhantomReference public class Resource implements Closeable { static class ResourceCleaner

    extends FinalizablePhantomReference<Resource> { ResourceCleaner(Resource res) { super(res, QUEUE); REFERENCES.add(this); } @Override public void finalizeReferent() { // Cleaning action here REFERENCES.remove(this); } } private static final Set<ResourceCleaner> REFERENCES = Collections.newSetFromMap(new ConcurrentHashMap<>()); private static final FinalizableReferenceQueue QUEUE = new FinalizableReferenceQueue(); private final ResourceCleaner cleaner = new ResourceCleaner(this); @Override public void close() { cleaner.finalizeReferent(); } } 63/84
  31. FinalizablePhantomReference public class Resource implements Closeable { static class ResourceCleaner

    extends FinalizablePhantomReference<Resource> { ResourceCleaner(Resource res) { super(res, QUEUE); REFERENCES.add(this); } @Override public void finalizeReferent() { // Cleaning action here REFERENCES.remove(this); } } private static final Set<ResourceCleaner> REFERENCES = Collections.newSetFromMap(new ConcurrentHashMap<>()); private static final FinalizableReferenceQueue QUEUE = new FinalizableReferenceQueue(); private final ResourceCleaner cleaner = new ResourceCleaner(this); @Override public void close() { cleaner.finalizeReferent(); } } 64/84
  32. java.lang.ref.Cleaner /** @since 9 */ public final class Cleaner {

    public static Cleaner create() {…} public static Cleaner create(ThreadFactory factory) {…} public Cleanable register(Object obj, Runnable action) { … } public interface Cleanable { void clean(); } } 65/84
  33. java.lang.ref.Cleaner public class Resource implements Closeable { private static final

    Cleaner CLEANER = Cleaner.create(); private final Cleanable cleanable; public Resource() { cleanable = CLEANER.register(this, () -> { // Cleaning action here }); } @Override public void close() { cleanable.clean(); } } 66/84
  34. Cleaner pitfall #1 private final String name; public Resource(String name)

    { this.name = name; CLEANER.register(this, new Runnable() { @Override public void run() { System.out.println(name); } }); } 69/84
  35. Cleaner pitfall #1 private final String name; public Resource(String name)

    { this.name = name; CLEANER.register(this, new Runnable() { @Override public void run() { System.out.println(name); } }); } Runnable passed to Cleaner.register() captures ‘this’ reference more… 70/84
  36. Cleaner pitfall #1 private final String name; public Resource(String name)

    { this.name = name; CLEANER.register(this, () -> { System.out.println(name); } } 71/84
  37. Cleaner pitfall #2 public final class MultiRequest { public MultiRequest(String...

    urls) { HttpRequest[] requests = … setRequests(requests); CLEANER.register(this, () -> { for (HttpRequest request : requests) try { request.close(); } catch (IOException e) {} }); } public void send() { for (HttpRequest request : getRequests()) request.send(); } } interface HttpRequest extends Closeable { void send(); } 72/84
  38. Cleaner pitfall #2 public void send() { for (HttpRequest request

    : getRequests()) request.send(); } 73/84
  39. Cleaner pitfall #2 public void f() { new MultiRequest(url1, url2,

    url3).send(); } public void send() { for (HttpRequest request : getRequests()) request.send(); } 74/84
  40. Cleaner pitfall #2 public void f() { new MultiRequest(url1, url2,

    url3).send(); } public synchronized void send() { for (HttpRequest request : getRequests()) request.send(); } 75/84
  41. reachabilityFence() public abstract class Reference<T> { … /** @since 9

    */ @DontInline public static void reachabilityFence(Object obj) { // Do nothing } } 76/84
  42. Cleaner pitfall #2 public void f() { new MultiRequest(url1, url2,

    url3).send(); } public void send() { try { for (HttpRequest request : getRequests()) request.send(); } finally { Reference.reachabilityFence(this); } } 77/84
  43. Выводы • Закрывайте ресурсы вовремя • Никогда не используйте finalize()

    • Используйте NIO2 (java.nio.file) • Java 8: используйте слабые ссылки 82/84
  44. Выводы • Закрывайте ресурсы вовремя • Никогда не используйте finalize()

    • Используйте NIO2 (java.nio.file) • Java 8: используйте слабые ссылки • Java 9+: используйте Cleaner 83/84
  45. Выводы • Закрывайте ресурсы вовремя • Никогда не используйте finalize()

    • Используйте NIO2 (java.nio.file) • Java 8: используйте слабые ссылки • Java 9+: используйте Cleaner • Переходите на Java 10 Java 10 84/84