Иван Углянский — Ходячие объекты-мертвецы или GC всегда прав

Иван Углянский — Ходячие объекты-мертвецы или GC всегда прав

Автоматическое управление памятью — одна из основных особенностей Java и других managed языков. При этом в спецификации про GC написано очень мало: как именно собирать мусор каждой конкретной реализации JVM, предлагается решать самостоятельно. В результате для сборки мусора существует огромное количество стратегий и степеней свободы. Например, когда именно GC должен приходить за мертвым объектом? Ответ не так очевиден, а любое решение может повлиять на ход исполнения пользовательской программы.

В докладе обсудим, зачем коллектору оставлять мертвые объекты в памяти, как это сказывается на вашем приложении и как выжить во время нашествия ходячих объектов-мертвецов.

3fc5b5eb32bd3b48d7810fd67b37f9a1?s=128

Moscow JUG

May 30, 2019
Tweet

Transcript

  1. Иван Углянский Excelsior LLC Ходячие объекты-мертвецы или GC всегда прав


  2. JVM инженер, Excelsior ◦ разрабатываю рантайм 2 Иван Углянский ivan.ugliansky@gmail.com

    @dbg_nsk JUGNsk co-lead
  3. 3 Автоматическое управление памятью (Сборка мусора)

  4. 4 Сборка мусора

  5. 5 А что в спеке? The Java Virtual Machine Specification,

    (§2.5.3): Heap storage for objects is reclaimed by an automatic storage management system (known as a garbage collector); objects are never explicitly deallocated. The Java Virtual Machine assumes no particular type of automatic storage management system, and the storage management technique may be chosen according to the implementor's system requirements.
  6. 6 Сборка мусора Ожидание: ◦ JVM собирает мусор, как хочет

    (в рамках корректности)
  7. 7 Сборка мусора Ожидание: ◦ JVM собирает мусор, как хочет

    (в рамках корректности) ◦ Разработчик не задумывается об управлении памятью
  8. 8 Сборка мусора Реальность: java.lang.NullPointerException at java.util.ResourceBundle$Control.newBundle(ResourceBundle.java:2640) at java.util.ResourceBundle.loadBundle(ResourceBundle.java:1501) at

    java.util.ResourceBundle.findBundle(ResourceBundle.java:1465) at java.util.ResourceBundle.findBundle(ResourceBundle.java:1419) at java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1361) ... 3 more
  9. 9 Сборка мусора Реальность: java.lang.NullPointerException at java.util.ResourceBundle$Control.newBundle(ResourceBundle.java:2640) at java.util.ResourceBundle.loadBundle(ResourceBundle.java:1501) at

    java.util.ResourceBundle.findBundle(ResourceBundle.java:1465) at java.util.ResourceBundle.findBundle(ResourceBundle.java:1419) at java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1361) ... 3 more Exception in thread "1" java.lang.OutOfMemoryError: Java heap space at StressTimer$Task.run(StressTimer.java:10) at java.util.TimerThread.mainLoop(Unknown Source) at java.util.TimerThread.run(Unknown Source) Exception in thread "2" java.lang.OutOfMemoryError: Java heap space at StressTimer$Task.run(StressTimer.java:10) at java.util.TimerThread.mainLoop(Unknown Source) ...
  10. 10 Сборка мусора Реальность: java.lang.NullPointerException at java.util.ResourceBundle$Control.newBundle(ResourceBundle.java:2640) at java.util.ResourceBundle.loadBundle(ResourceBundle.java:1501) at

    java.util.ResourceBundle.findBundle(ResourceBundle.java:1465) at java.util.ResourceBundle.findBundle(ResourceBundle.java:1419) at java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1361) ... 3 more Exception in thread "1" java.lang.OutOfMemoryError: Java heap space at StressTimer$Task.run(StressTimer.java:10) at java.util.TimerThread.mainLoop(Unknown Source) at java.util.TimerThread.run(Unknown Source) Exception in thread "2" java.lang.OutOfMemoryError: Java heap space at StressTimer$Task.run(StressTimer.java:10) at java.util.TimerThread.mainLoop(Unknown Source) ... Java HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000e0000000, 257949696, 0) failed; error='Not enough space' (errno=12) # # There is insufficient memory for the Java Runtime Environment to continue. # Native memory allocation (mmap) failed to map 257949696 bytes for committing reserved memory. # An error report file with more information is saved as:
  11. 11 Когда конкретно GC придет за объектом?

  12. 12 В этом докладе ◦ Про политики GC относительно времени

    жизни объектов на примерах ◦ Как это влияет на исполнение? ◦ Как не получить OOM это учитывать в коде?
  13. 13 Разминка

  14. 14 Разминка public static void main(String[] args) { final int

    size = 512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); } 512mb
  15. 15 Разминка public static void main(String[] args) { final int

    size = 512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); } 512mb
  16. 16 Разминка public static void main(String[] args) { final int

    size = 512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); } 512mb 512mb
  17. 17 Разминка public static void main(String[] args) { final int

    size = 512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); } 512mb 512mb java -Xmx768m Test
  18. 18 Разминка public static void main(String[] args) { final int

    size = 512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); } 512mb 512mb java -Xmx768m Test arr1 ([I@4aa8f0b4) allocated Exception in thread "main" java.lang.OutOfMemoryError: ...
  19. 19 Разминка public static void main(String[] args) { final int

    size = 512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); } 512mb 512mb java -Xcomp -Xmx768m Test
  20. 20 Разминка public static void main(String[] args) { final int

    size = 512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); } 512mb 512mb java -Xcomp -Xmx768m Test arr1 ([I@4aa8f0b4) allocated arr2 ([I@9e89d68) allocated
  21. 21 Разминка public static void main(String[] args) { final int

    size = 512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); } 512mb 512mb java -Xcomp -Xmx768m Test arr1 ([I@4aa8f0b4) allocated arr2 ([I@9e89d68) allocated
  22. 22 Разминка public static void main(String[] args) { final int

    size = 512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); } Имеем право собирать первый массив?
  23. public static void main(String[] args) { final int size =

    512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); } 23 Разминка Область видимости переменной arr1
  24. public static void main(String[] args) { final int size =

    512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); } 24 Разминка Область видимости переменной arr1 Область жизни переменной arr1
  25. public static void main(String[] args) { final int size =

    512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); } 25 Разминка Здесь GC имеет право собрать первый массив
  26. 26 Разминка public static void main(String[] args) { final int

    size = 512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); } 512mb 512mb java -Xmx768m Test arr1 ([I@4aa8f0b4) allocated Exception in thread "main" java.lang.OutOfMemoryError: ...
  27. public static void main(String[] args) { final int size =

    512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); } 27 Разминка Интерпретатор не вычисляет время жизни!
  28. 28 Разминка public static void main(String[] args) { final int

    size = 512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); } Интерпретатор не вычисляет время жизни! (предполагает худший случай)
  29. 29 История #1. Объекты-призраки

  30. 30 История #1. Объекты-призраки Слабые ссылки:

  31. 31 История #1. Объекты-призраки Слабые ссылки: 1. java.lang.ref.* 2. Не

    останавливают GC от сборки референта
  32. 32 История #1. Объекты-призраки Слабые ссылки: 1. java.lang.ref.* 2. Не

    останавливают GC от сборки референта 3. «Протухают»
  33. 33 История #1. Объекты-призраки Слабые ссылки: 1. java.lang.ref.* 2. Не

    останавливают GC от сборки референта 3. «Протухают»
  34. 34 Пример public static void main(String[] args) { Object obj

    = new Object(); WeakReference ref = new WeakReference<>(obj); System.gc(); System.out.println(ref.get().hashCode()); }
  35. 35 Пример public static void main(String[] args) { Object obj

    = new Object(); WeakReference ref = new WeakReference<>(obj); System.gc(); System.out.println(ref.get().hashCode()); }
  36. 36 Пример public static void main(String[] args) { Object obj

    = new Object(); WeakReference ref = new WeakReference<>(obj); System.gc(); System.out.println(ref.get().hashCode()); }
  37. 37 Пример public static void main(String[] args) { Object obj

    = new Object(); WeakReference ref = new WeakReference<>(obj); System.gc(); System.out.println(ref.get().hashCode()); } java Test 1252585652
  38. 38 Пример public static void main(String[] args) { Object obj

    = new Object(); WeakReference ref = new WeakReference<>(obj); System.gc(); System.out.println(ref.get().hashCode()); } java Test 1252585652 java -Xcomp Test Exception in thread "main" java.lang.NullPointerException
  39. 39 Пример java Test 1252585652 java -Xcomp Test Exception in

    thread "main" java.lang.NullPointerException public static void main(String[] args) { Object obj = new Object(); WeakReference ref = new WeakReference<>(obj); System.gc(); System.out.println(ref.get().hashCode()); }
  40. Но ведь это искусственный пример? 40 Объекты-призраки

  41. Но ведь это искусственный пример? 41 Объекты-призраки java.lang.NullPointerException at java.util.ResourceBundle$Control.newBundle(ResourceBundle.java:2640)

    at java.util.ResourceBundle.loadBundle(ResourceBundle.java:1501) at java.util.ResourceBundle.findBundle(ResourceBundle.java:1465) at java.util.ResourceBundle.findBundle(ResourceBundle.java:1419) at java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1361) ... 3 more
  42. 42 Реальный пример private static ResourceBundle getBundleImpl(..., ClassLoader loader, ...)

    { ... CacheKey cacheKey = new CacheKey(baseName, locale, loader); ... // обращение к loader через cacheKey ... return bundle; }
  43. 43 Реальный пример private static ResourceBundle getBundleImpl(..., ClassLoader loader, ...)

    { ... CacheKey cacheKey = new CacheKey(baseName, locale, loader); ... // обращение к loader через cacheKey ... return bundle; } Слабая ссылка Последнее использование
  44. 44 Реальный пример ◦ JDK 1.8.0_212 ◦ java.util.ResourceBundle.getBundle(...) ◦ Для

    проявления: -Xcomp + итерации ◦ JDK-8209184
  45. 45 Что с этим делать?

  46. 46 Что с этим делать? ◦ Перед использованием проверять, что

    ссылка не протухла
  47. 47 Что с этим делать? ◦ Перед использованием проверять, что

    ссылка не протухла ◦ Продлить жизнь объекта!
  48. 48 Что с этим делать? public static void main(String[] args)

    { Object obj = new Object(); WeakReference ref = new WeakReference<>(obj); System.gc(); System.out.println(ref.get().hashCode()); }
  49. 49 Что с этим делать? public static void main(String[] args)

    { Object obj = new Object(); WeakReference ref = new WeakReference<>(obj); System.gc(); System.out.println(ref.get().hashCode()); // use obj ??? }
  50. 50 Что с этим делать? public static void main(String[] args)

    { Object obj = new Object(); WeakReference ref = new WeakReference<>(obj); System.gc(); System.out.println(ref.get().hashCode()); // use obj ??? } Но какое именно использование добавить?
  51. 51 Что с этим делать? public static void main(String[] args)

    { Object obj = new Object(); WeakReference ref = new WeakReference<>(obj); System.gc(); System.out.println(ref.get().hashCode()); // use obj ??? } Но какое именно использование добавить? Как убедить компилятор его не выкидывать?
  52. 52 Использование ◦ Передать в метод ◦ Записать в поле

  53. 53 Использование ◦ Передать в метод ◦ Записать в поле

    ◦ Reference.reachabilityFence(obj) Since Java 9
  54. 54 Лечение public static void main(String[] args) { Object obj

    = new Object(); WeakReference ref = new WeakReference<>(obj); System.gc(); System.out.println(ref.get().hashCode()); // use obj ??? }
  55. 55 Лечение public static void main(String[] args) { Object obj

    = new Object(); WeakReference ref = new WeakReference<>(obj); System.gc(); System.out.println(ref.get().hashCode()); Reference.reachabilityFence(obj); }
  56. 56 Лечение private static ResourceBundle getBundleImpl(...) { ... CacheKey cacheKey

    = new CacheKey(baseName, locale, module, callerModule); ... // keep callerModule and module reachable for as long // as we are operating with WeakReference(s) to them // (in CacheKey) ... Reference.reachabilityFence(callerModule); Reference.reachabilityFence(module); return bundle; }
  57. Подумаешь, один пример! 57 Объекты-призраки

  58. 58 Объекты-призраки JDK-8212178

  59. 59 Объекты-призраки public static BufferAllocator getBufferAllocator() { SoftReference<BufferAllocator> bAllocatorRef =

    tlba.get(); if (bAllocatorRef == null || bAllocatorRef.get() == null) { bAllocatorRef = new SoftReference(new BufferAllocator()); tlba.set(bAllocatorRef); } return bAllocatorRef.get(); } com.sun.xml.internal.stream.util.ThreadLocalBufferAllocator:
  60. 60 Объекты-призраки public static BufferAllocator getBufferAllocator() { SoftReference<BufferAllocator> bAllocatorRef =

    tlba.get(); if (bAllocatorRef == null || bAllocatorRef.get() == null) { bAllocatorRef = new SoftReference(new BufferAllocator()); tlba.set(bAllocatorRef); } return bAllocatorRef.get(); } com.sun.xml.internal.stream.util.ThreadLocalBufferAllocator:
  61. 61 Выводы ◦ GC имеет право собрать объект после последнего

    использования ◦ Компилятор может выкидывать использования при оптимизации ◦ Reference.reachabilityFence для продления жизни объекта
  62. 62 Ходячие объекты-мертвецы

  63. 63 Ходячие объекты-мертвецы Обратная проблема: объекты мертвы, но GC за

    ними приходить не торопится.
  64. 64 Ходячие объекты-мертвецы

  65. 65 Ходячие объекты-мертвецы Memory Drag - память, которую GC «протаскивает»

    до следующей* сборки (характеристика алгоритма GC)
  66. 66 Ходячие объекты-мертвецы Большой Memory Drag ⇒

  67. 67 Ходячие объекты-мертвецы Большой Memory Drag ⇒ Много зомби-объектов ⇒

  68. 68 Ходячие объекты-мертвецы Большой Memory Drag ⇒ Много зомби-объектов ⇒

    Результат: ◦ Неожиданные OOM ◦ Проблемы с java.lang.ref
  69. 69 История #2. F-reachables

  70. 70 История #2. F-reachables Object.finalize() JavaDoc in JDK 9+: The

    finalization mechanism is inherently problematic.
  71. 71 История #2. F-reachables Object.finalize() JavaDoc in JDK 9+: The

    finalization mechanism is inherently problematic. Finalization can lead to performance issues, deadlocks, and hangs.
  72. 72 История #2. F-reachables Object.finalize() JavaDoc in JDK 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 finalize methods of different objects. Furthermore, there are no guarantees regarding the timing of finalization.
  73. 73 История #2. F-reachables Object.finalize() JavaDoc in JDK 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 finalize methods of different objects. Furthermore, there are no guarantees regarding the timing of finalization.
  74. 74 История #2. F-reachables ➢ JDK 8 ㄧ 89 реализаций

    finalize()
  75. 75 История #2. F-reachables ➢ JDK 8 ㄧ 89 реализаций

    finalize() ➢ JDK 9 ㄧ 87 реализаций (Object.finalize() is deprecated since 9)
  76. 76 История #2. F-reachables ➢ JDK 8 ㄧ 89 реализаций

    finalize() ➢ JDK 9 ㄧ 87 реализаций (Object.finalize() is deprecated since 9) ➢ JDK 11 ㄧ 84 реализации
  77. 77 История #2. F-reachables Объекты с нетривиальным finalize(): ◦ живут

    дольше
  78. 78 История #2. F-reachables Java Threads Finalizer Thread

  79. 79 История #2. F-reachables Java Threads Finalizer Thread STW GC

    Threads
  80. 80 История #2. F-reachables Java Threads Finalizer Thread STW GC

    Threads finalize() выполнить не получится
  81. 81 История #2. F-reachables Объекты с нетривиальным finalize(): ◦ живут

    дольше: для STW как минимум (!) на один цикл GC
  82. 82 История #2. F-reachables class Foo { Object ref; Foo(Object

    r) { ref = r; } void finalize() { ref.call(); } }
  83. class Foo { Object ref; Foo(Object r) { ref =

    r; } void finalize() { ref.call(); } } 83 История #2. F-reachables ref используется из finalize, значит он тоже выживет Foo ref
  84. 84 История #2. F-reachables ref используется из finalize, значит он

    тоже выживет как и все достижимое из ref Foo ref class Foo { Object ref; Foo(Object r) { ref = r; } void finalize() { ref.call(); } }
  85. Объекты с нетривиальным finalize(): ◦ живут дольше: для STW как

    минимум (!) на один цикл GC ◦ дополнительно удерживают все достижимые из них объекты (f-reachables) 85 История #2. F-reachables
  86. Как обнаружить проблему с F-reachables? 86 Developer vs F-reachables

  87. Как обнаружить проблему с F-reachables? 87 Developer vs F-reachables jmap

    -finalizerinfo <PID>
  88. Как обнаружить проблему с F-reachables? 88 Developer vs F-reachables jmap

    -finalizerinfo <PID> Unreachable instances waiting for finalization #instances class name ----------------------- 147532 StressTimer$FinalizeBallast 222 java.util.Timer$1 221 StressTimer$Task
  89. Как Вы можете бороться с F-reachables? 89 Developer vs F-reachables

  90. 90 Developer vs F-reachables Foo ref1 ref2 ref3 resource1 resource2

    resource3 finalize() Как Вы можете бороться с F-reachables?
  91. 91 Developer vs F-reachables Foo ref1 ref2 ref3 resource1 resource2

    resource3 State state finalize() Как Вы можете бороться с F-reachables?
  92. Как Вы можете бороться с F-reachables? 92 Developer vs F-reachables

  93. Если не finalize(), то кто? 93 Developer vs F-reachables

  94. Если не finalize(), то кто? 94 Developer vs F-reachables java.lang.ref.Cleaner

    (ранее sun.misc.Cleaner)
  95. 95 Developer vs F-reachables Foo ref1 ref2 ref3 resource1 resource2

    resource3 State state static class State implements Runnable { ... } static final Cleaner cleaner = Cleaner.create(); ... var foo = new Foo(); cleaner.register(foo, foo.getState()); finalize()
  96. 96 Developer vs F-reachables Поможет с Memory Drag только в

    Java 9+! JDK-8071507 Если не finalize(), то кто? java.lang.ref.Cleaner (ранее sun.misc.Cleaner)
  97. 97 Выводы ◦ finalize() увеличивает Memory Drag ◦ JVM могут

    облегчить симптомы (но делают это редко) ◦ java.lang.ref.Cleaner since Java 9
  98. 98 История #3. Непотизм GC Непотизм (кумовство) — вид фаворитизма,

    предоставляющий привилегии родственникам или друзьям независимо от их профессиональных качеств.
  99. 99 Пример public class DummyList<E> { private Node<E> first; private

    Node<E> last; private class Node<E> { E value; Node<E> next; public Node(E value, Node<E> next) { ... } } }
  100. 100 Пример public void addLast(Node n) { final var l

    = last; last = n; if (l == null) { first = n; } else { l.next = n; } }
  101. 101 Пример public void removeFirst() { if (first != null)

    { var next = first.next; first = next; if (next == null) { last = null; } } } public void addLast(Node n) { final var l = last; last = n; if (l == null) { first = n; } else { l.next = n; } }
  102. public void addLast(Node n) { final var l = last;

    last = n; if (l == null) { first = n; } else { l.next = n; } } 102 Пример public void removeFirst() { if (first != null) { var next = first.next; first = next; if (next == null) { last = null; } } }
  103. 103 История #3. Непотизм Сборка мусора по поколениям: ◦ Молодое/старое

    поколения ◦ Minor/Major GC
  104. 104 История #3. Непотизм Сборка мусора по поколениям: ◦ Молодое/старое

    поколения ◦ Minor/Major GC ◦ Все достижимое из старого поколения должно переживать Minor GC
  105. 105 История #3. Непотизм Young Generation Old Generation

  106. 106 История #3. Непотизм new DummyList() DummyList first last Young

    Generation Old Generation
  107. 107 История #3. Непотизм DummyList first last Young Generation Old

    Generation
  108. 108 История #3. Непотизм DummyList first last Young Generation Old

    Generation
  109. 109 История #3. Непотизм DummyList first last DummyList.addLast(...) Young Generation

    Old Generation
  110. 110 История #3. Непотизм DummyList first last DummyList.addLast(...) Young Generation

    Old Generation
  111. 111 История #3. Непотизм DummyList first last DummyList.addLast(...) Young Generation

    Old Generation
  112. 112 История #3. Непотизм DummyList first last Young Generation Old

    Generation
  113. 113 История #3. Непотизм DummyList first last Young Generation Old

    Generation
  114. 114 История #3. Непотизм DummyList first last DummyList.removeFirst(...) Young Generation

    Old Generation
  115. 115 История #3. Непотизм DummyList first last DummyList.removeFirst(...) Young Generation

    Old Generation
  116. 116 История #3. Непотизм DummyList first last Young Generation Old

    Generation
  117. 117 История #3. Непотизм DummyList first last Young Generation Old

    Generation
  118. 118 История #3. Непотизм Сборка мусора по поколениям сознательно увеличивает

    Memory Drag.
  119. 119 История #3. Непотизм Сборка мусора по поколениям сознательно увеличивает

    Memory Drag. Старое поколение - источник ходячих мертвецов
  120. 120 История #3. Непотизм Но ведь это искусственный пример?

  121. 121 История #3. Непотизм Но ведь это искусственный пример? Tony

    Printezis о борьбе с непотизмом LinkedHashMap в TwitterJDK: https://youtu.be/M9o1LVfGp2A?t=1391
  122. 122 Как обнаружить непотизм?

  123. 123 Как обнаружить непотизм? По косвенным признакам: 1. Частые и

    долгие Full GC (проверить по -XX:+PrintGCDetails) 2. Много подозрительных живых объектов (смотреть jmap -dump <PID>)
  124. 124 Что делать?

  125. 125 Что делать? ◦ Ждать Full GC

  126. 126 Что делать? ◦ Ждать Full GC ◦ Занулять ссылки

    из объектов при удалении
  127. 127 /** * Unlinks non-null first node f. */ private

    E unlinkFirst(Node<E> f) { final E element = f.item; final Node<E> next = f.next; f.item = null; f.next = null; // help GC ... } java.util.LinkedList: Что делать?
  128. /** * Unlinks non-null first node f. */ private E

    unlinkFirst(Node<E> f) { final E element = f.item; final Node<E> next = f.next; f.item = null; f.next = null; // help GC ... } 128 java.util.LinkedList: Что делать?
  129. 129 Что делать? void afterNodeRemoval(Node<K,V> e) { // unlink LinkedHashMap.Entry<K,V>

    p = (LinkedHashMap.Entry<K,V>)e; b = p.before, a = p.after; p.before = p.after = null; ... } java.util.LinkedHashMap:
  130. 130 Что делать? ◦ Ждать Full GC ◦ Занулять ссылки

    из объектов при удалении
  131. 131 Что делать? ◦ Ждать Full GC ◦ Занулять ссылки

    из объектов при удалении ◦ Увеличить размер молодого поколения -Xmn -XX:NewRatio -XX:NewSize
  132. 132 Ходячие объекты-мертвецы А может ли GC вообще не прийти

    за мертвым объектом?
  133. 133 Ходячие объекты-мертвецы

  134. 134 История #4. Консерватизм

  135. 135 Точный GC public static void main(String[] args) { final

    int size = 512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); }
  136. 136 Точный GC public static void main(String[] args) { final

    int size = 512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); } Как GC понимает, какие локалы живы?
  137. 137 Точный GC stack sp+30h sp+0h registers rax rcx ...

    rdx r14
  138. 138 Точный GC stack sp+30h sp+0h registers rax rcx ...

    rdx r14
  139. 139 Точный GC stack sp+30h sp+0h registers rax rcx ...

    rdx r14
  140. 140 Точный GC stack sp+30h sp+0h registers rax rcx ...

    rdx r14
  141. 141 Точный GC ➢ Поддержка от компилятора

  142. 142 Точный GC ➢ Поддержка от компилятора ➢ Генерация карт

    стека и регистров
  143. 143 Точный GC ➢ Поддержка от компилятора ➢ Генерация карт

    стека и регистров ➢ Карты только в safe-points!
  144. 144 Консервативный GC А нельзя ли без поддержки от компилятора?

  145. 145 Консервативный GC ➢ Предполагаем худшее: каждое значение может быть

    указателем ➢ Отсеиваем лишнее в рантайме
  146. 146 Консервативный GC stack sp+30h sp+0h registers rax rcx ...

    rdx r14
  147. 147 Консервативный GC stack sp+30h sp+0h registers rax rcx ...

    rdx r14
  148. 148 Консервативный GC stack sp+30h sp+0h registers rax rcx ...

    rdx r14
  149. 149 Консервативный GC ➢ Предполагаем худшее: каждое значение может быть

    указателем ➢ Отсеиваем лишнее в рантайме ➢ Иногда ошибаемся ⇒ увеличиваем Memory Drag на величину ошибки
  150. 150 Консервативный GC Зачем такое нужно?

  151. 151 Консервативный GC Зачем такое нужно? ➢ Нет карт ⇒

    они не занимают место ➢ Нет карт ⇒ нет затрат на генерацию
  152. 152 Консервативный GC Зачем такое нужно? ➢ Нет карт ⇒

    они не занимают место ➢ Нет карт ⇒ нет затрат на генерацию ➢ Нет карт ⇒ можно выкинуть safe-points
  153. 153 Safe-points 1 - 2 машинные инструкции Обратные дуги циклов

    и эпилоги
  154. 154 Safe-points 1 - 2 машинные инструкции Обратные дуги циклов

    и эпилоги Ухудшают производительность ⇒ Агрессивно оптимизируются
  155. 155 Мир без safe-points

  156. 156 Мир без safe-points Производительность растет Размер кода уменьшается

  157. 157 Мир без safe-points Производительность растет Размер кода уменьшается Многое

    работает через safe-points, поэтому потребует переработки
  158. 158 Консервативный GC А так вообще делают?

  159. 159 Консервативный GC А так вообще делают? Теория: Boehm GC,

    1988, conservative Immix, 2008, mostly-precise
  160. 160 Консервативный GC А так вообще делают? Теория: Boehm GC,

    1988, conservative Immix, 2008, mostly-precise Практика:
  161. 161 Консервативный GC А так вообще делают? Теория: Boehm GC,

    1988, conservative Immix, 2008, mostly-precise Практика: MobiVM
  162. 162 Консервативный GC А так вообще делают? Теория: Boehm GC,

    1988, conservative Immix, 2008, mostly-precise Практика: MobiVM scala-native
  163. 163 Консервативный GC А так вообще делают? Теория: Boehm GC,

    1988, conservative Immix, 2008, mostly-precise Практика: MobiVM scala-native Excelsior JET Generation 1 (с 1999 по 2015)
  164. 164 Неограниченный Memory Drag Консервативная ошибка: ◦ обычно продлевает жизнь

    объекта на один или несколько циклов GC (пока ложный корень на стеке)
  165. 165 Неограниченный Memory Drag Консервативная ошибка: ◦ обычно продлевает жизнь

    объекта на один или несколько циклов GC (пока ложный корень на стеке) ◦ иногда продлевает навсегда
  166. 166 Неограниченный Memory Drag public class Executor extends Thread {

    public abstract class Task implements Runnable { } final TaskQueue queue = new TaskQueue(); . . . }
  167. 167 Неограниченный Memory Drag public void mainLoop() { while (true)

    { synchronized (queue) { while (queue.isEmpty()) { queue.wait(); } Task t = queue.get(); t.run(); } } }
  168. public void mainLoop() { while (true) { synchronized (queue) {

    while (queue.isEmpty()) { queue.wait(); } Task t = queue.get(); t.run(); } } } 168 Неограниченный Memory Drag stack sp+20h addr t
  169. public void mainLoop() { while (true) { synchronized (queue) {

    while (queue.isEmpty()) { queue.wait(); } Task t = queue.get(); t.run(); } } } 169 Неограниченный Memory Drag stack addr t sp+20h
  170. public void mainLoop() { while (true) { synchronized (queue) {

    while (queue.isEmpty()) { queue.wait(); } Task t = queue.get(); t.run(); } } } 170 Неограниченный Memory Drag stack addr t sp+20h
  171. public void mainLoop() { while (true) { synchronized (queue) {

    while (queue.isEmpty()) { queue.wait(); } Task t = queue.get(); t.run(); } } } 171 Неограниченный Memory Drag stack addr t sp+20h
  172. 172 Неограниченный Memory Drag Может это искусственный пример?

  173. 173 Неограниченный Memory Drag Может это искусственный пример? Нет, это

    класс java.util.Timer из JDK И там все еще хуже
  174. public void mainLoop() { while (true) { synchronized (queue) {

    while (queue.isEmpty()) { queue.wait(); } Task t = queue.get(); t.run(); } } } 174 Неограниченный Memory Drag stack addr t sp+20h
  175. public void mainLoop() { while (true) { synchronized (queue) {

    while (queue.isEmpty()) { queue.wait(); } Task t = queue.get(); t.run(); } } } 175 Неограниченный Memory Drag stack addr t sp+20h threadReaper
  176. 176 Неограниченный Memory Drag Как Вы можете бороться с консервой?

  177. 177 Неограниченный Memory Drag Как Вы можете бороться с консервой?

  178. 178 Неограниченный Memory Drag Как Вы можете бороться с консервой?

  179. 179 Неограниченный Memory Drag java.util.Timer javadoc: After the last live

    reference to a Timer object goes away and all outstanding tasks have completed execution, the timer's task execution thread terminates gracefully (and becomes subject to garbage collection). However, this can take arbitrarily long to occur. By default, the task execution thread does not run as a daemon thread, so it is capable of keeping an application from terminating. If a caller wants to terminate a timer's task execution thread rapidly, the caller should invoke the timer's cancel method.
  180. 180 Выводы Консервативный GC - большая сила, но и большая

    ответственность
  181. 181 Выводы Консервативный GC - большая сила, но и большая

    ответственность Консерва не только в GC (смотри первый пример)
  182. 182 Разминка public static void main(String[] args) { final int

    size = 512*1024*1024 / Integer.BYTES; int[] arr1 = new int[size]; System.out.println("arr1 (" + arr1 + ") allocated"); System.gc(); int[] arr2 = new int[size]; System.out.println("arr2 (" + arr2 + ") allocated"); } 512mb 512mb java -Xmx768m Test arr1 ([I@4aa8f0b4) allocated Exception in thread "main" java.lang.OutOfMemoryError: ...
  183. 183 История #5. MMM

  184. 184 История #5. Manual Memory Management Oh no

  185. 185 Внезапный развал: История #5. Manual Memory Management Oh no

  186. 186 История #5. Manual Memory Management Oh no Внезапный развал:

    ◦ проявляется иногда (чаще всего при закрытии)
  187. 187 Oh no Внезапный развал: ◦ проявляется иногда (чаще всего

    при закрытии) ◦ судя по логу, в ntdll.dll История #5. Manual Memory Management
  188. 188 Oh no Внезапный развал: ◦ проявляется иногда (чаще всего

    при закрытии) ◦ судя по логу, в ntdll.dll ◦ проявляется только на одной JVM История #5. Manual Memory Management
  189. 189 История #5. Manual Memory Management Отладка показала: ◦ Проблема

    в вызове GlobalFree
  190. 190 История #5. Manual Memory Management Отладка показала: ◦ Проблема

    в вызове GlobalFree ◦ Вызывается для плохой памяти
  191. 191 История #5. Manual Memory Management Отладка показала: ◦ Проблема

    в вызове GlobalFree ◦ Вызывается для плохой памяти ◦ Зовем ее не мы (а кто?)
  192. 192 История #5. Manual Memory Management Библиотека proxy-vole Очень хочет

    позвать системный метод WinHttpDetectAutoProxyConfigUrl
  193. 193 История #5. Manual Memory Management Библиотека proxy-vole Очень хочет

    позвать системный метод WinHttpDetectAutoProxyConfigUrl Но как, мы ведь в Java?
  194. 194 История #5. Manual Memory Management Библиотека proxy-vole Очень хочет

    позвать системный метод WinHttpDetectAutoProxyConfigUrl Но как, мы ведь в Java? JNI/JNA
  195. 195 История #5. Manual Memory Management WinHttpDetectAutoProxyConfigUrl:

  196. 196 История #5. Manual Memory Management WinHttpDetectAutoProxyConfigUrl: 1. Аргумент -

    указатель на LPWSTR 2. Если не удалось ⇒ записываем NULL 3. Если удалось ⇒ записываем значение, от которого потом нужно позвать GlobalFree
  197. 197 История #5. Manual Memory Management WinHttpDetectAutoProxyConfigUrl В Java версии

    создается аналог LPWSTR*: LPWSTRByReference
  198. 198 История #5. Manual Memory Management LPWSTRByReference Memory

  199. 199 История #5. Manual Memory Management WinHttpDetectAutoProxyConfigUrl В Java версии

    создается аналог LPWSTR*: LPWSTRByReference Где позвать GlobalFree?
  200. 200 История #5. Manual Memory Management WinHttpDetectAutoProxyConfigUrl В Java версии

    создается аналог LPWSTR*: LPWSTRByReference Где позвать GlobalFree? Конечно в finalize!
  201. 201 История #5. Manual Memory Management protected void finalize() throws

    Throwable { try { // Free the memory occupied // by the string returned from the Win32 function. Pointer strPointer = getPointerToString(); if (strPointer != null) { Pointer result = Kernel32.INSTANCE.GlobalFree(strPointer); ... } } finally { // This will free the memory of the pointer-to-pointer super.finalize(); } }
  202. 202 История #5. Manual Memory Management protected void finalize() throws

    Throwable { try { // Free the memory occupied // by the string returned from the Win32 function. Pointer strPointer = getPointerToString(); if (strPointer != null) { Pointer result = Kernel32.INSTANCE.GlobalFree(strPointer); ... } } finally { // This will free the memory of the pointer-to-pointer super.finalize(); } }
  203. 203 История #5. Manual Memory Management protected void finalize() throws

    Throwable { try { // Free the memory occupied // by the string returned from the Win32 function. Pointer strPointer = getPointerToString(); if (strPointer != null) { Pointer result = Kernel32.INSTANCE.GlobalFree(strPointer); ... } } finally { // This will free the memory of the pointer-to-pointer super.finalize(); } }
  204. 204 История #5. Manual Memory Management public Memory(long size) {

    this.size = size; ... peer = malloc(size); ... } protected void finalize() { ... free(peer); peer = null; ... }
  205. 205 История #5. Manual Memory Management public Memory(long size) {

    this.size = size; ... peer = malloc(size); ... } protected void finalize() { ... free(peer); peer = null; ... } После данного finalize метод getPointerToString() вернет null
  206. 206 История #5. Manual Memory Management LPWSTRByReference Memory

  207. 207 История #5. Manual Memory Management LPWSTRByReference Memory Time to

    die!
  208. 208 История #5. Manual Memory Management LPWSTRByReference Memory finalize() finalize()

    Argh... later
  209. 209 История #5. Manual Memory Management LPWSTRByReference Memory finalize() finalize()

    if (addr != null) { GlobalFree(addr); } free(addr); addr = null;
  210. 210 История #5. Manual Memory Management LPWSTRByReference Memory finalize() finalize()

    if (addr != null) { GlobalFree(addr); } free(addr); addr = null; Чей finalize() первее?
  211. 211 История #5. Manual Memory Management LPWSTRByReference Memory finalize() finalize()

    if (addr != null) { GlobalFree(addr); } free(addr); addr = null;
  212. 212 История #5. Manual Memory Management LPWSTRByReference Memory finalize() finalize()

    if (addr != null) { GlobalFree(addr); } free(addr); addr = null;
  213. 213 История #5. Manual Memory Management LPWSTRByReference Memory finalize() finalize()

    if (addr != null) { GlobalFree(addr); } free(addr); addr = null;
  214. 214 История #5. Manual Memory Management LPWSTRByReference Memory finalize() finalize()

    if (addr != null) { GlobalFree(addr); } free(addr); addr = null; А если в addr мусор...
  215. 215 История #5. Manual Memory Management LPWSTRByReference Memory finalize() finalize()

    if (addr != null) { GlobalFree(addr); } free(addr); addr = null; А если в addr мусор...
  216. 216 Выводы ◦ Смешивать ручное и автоматическое управление памятью опасно

    haha, cute
  217. 217 Выводы ◦ Смешивать ручное и автоматическое управление памятью опасно

    ◦ Порядок финализации не определен!
  218. 218 Выводы ◦ Смешивать ручное и автоматическое управление памятью опасно

    ◦ Порядок финализации не определен! ◦ В proxy-vole добавили workaround
  219. 219 Заключение

  220. 220 Заключение Не нужно пытаться предугадать время жизни объекта и

    рассчитывать на это в коде!
  221. 221 Заключение Не нужно пытаться предугадать время жизни объекта и

    рассчитывать на это в коде! Если вы все-таки на это решились: Используйте правильные костыли решения (reachabilityFence, Cleaner, Timer.cancel)
  222. 222 Q & A @jugnsk ivan.ugliansky@gmail.com @dbg_nsk @mskjug

  223. 223