Slide 1

Slide 1 text

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


Slide 2

Slide 2 text

JVM инженер, Excelsior ○ разрабатываю рантайм 2 Иван Углянский [email protected] @dbg_nsk JUGNsk co-lead

Slide 3

Slide 3 text

3 Автоматическое управление памятью (Сборка мусора)

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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.

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

7 Сборка мусора Ожидание: ○ JVM собирает мусор, как хочет (в рамках корректности) ○ Разработчик не задумывается об управлении памятью

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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) ...

Slide 10

Slide 10 text

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:

Slide 11

Slide 11 text

11 Когда конкретно GC придет за объектом?

Slide 12

Slide 12 text

12 В этом докладе ○ Про политики GC относительно времени жизни объектов на примерах ○ Как это влияет на исполнение? ○ Как не получить OOM это учитывать в коде?

Slide 13

Slide 13 text

13 Разминка

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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: ...

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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"); } Имеем право собирать первый массив?

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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 имеет право собрать первый массив

Slide 26

Slide 26 text

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: ...

Slide 27

Slide 27 text

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 Разминка Интерпретатор не вычисляет время жизни!

Slide 28

Slide 28 text

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"); } Интерпретатор не вычисляет время жизни! (предполагает худший случай)

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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()); }

Slide 40

Slide 40 text

Но ведь это искусственный пример? 40 Объекты-призраки

Slide 41

Slide 41 text

Но ведь это искусственный пример? 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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

44 Реальный пример ○ JDK 1.8.0_212 ○ java.util.ResourceBundle.getBundle(...) ○ Для проявления: -Xcomp + итерации ○ JDK-8209184

Slide 45

Slide 45 text

45 Что с этим делать?

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

47 Что с этим делать? ○ Перед использованием проверять, что ссылка не протухла ○ Продлить жизнь объекта!

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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 ??? }

Slide 50

Slide 50 text

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 ??? } Но какое именно использование добавить?

Slide 51

Slide 51 text

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 ??? } Но какое именно использование добавить? Как убедить компилятор его не выкидывать?

Slide 52

Slide 52 text

52 Использование ○ Передать в метод ○ Записать в поле

Slide 53

Slide 53 text

53 Использование ○ Передать в метод ○ Записать в поле ○ Reference.reachabilityFence(obj) Since Java 9

Slide 54

Slide 54 text

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 ??? }

Slide 55

Slide 55 text

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); }

Slide 56

Slide 56 text

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; }

Slide 57

Slide 57 text

57 Выводы ○ GC имеет право собрать объект после последнего использования ○ Компилятор может выкидывать использования при оптимизации ○ Reference.reachabilityFence для продления жизни объекта

Slide 58

Slide 58 text

58 Ходячие объекты-мертвецы

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

60 Ходячие объекты-мертвецы

Slide 61

Slide 61 text

61 Ходячие объекты-мертвецы Memory Drag - память, которую GC «протаскивает» до следующей* сборки (характеристика алгоритма GC)

Slide 62

Slide 62 text

62 Ходячие объекты-мертвецы Большой Memory Drag ⇒

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

64 Ходячие объекты-мертвецы Большой Memory Drag ⇒ Много зомби-объектов ⇒ Результат: ○ Неожиданные OOM ○ Проблемы с java.lang.ref

Slide 65

Slide 65 text

65 История #2. F-reachables

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

67 История #2. F-reachables Object.finalize() JavaDoc in JDK 9+: The finalization mechanism is inherently problematic. Finalization can lead to performance issues, deadlocks, and hangs.

Slide 68

Slide 68 text

68 История #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.

Slide 69

Slide 69 text

69 История #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.

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

73 История #2. F-reachables Объекты с нетривиальным finalize(): ○ живут дольше

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

76 История #2. F-reachables Java Threads Finalizer Thread STW GC Threads finalize() выполнить не получится

Slide 77

Slide 77 text

77 История #2. F-reachables Объекты с нетривиальным finalize(): ○ живут дольше: для STW как минимум (!) на один цикл GC

Slide 78

Slide 78 text

78 История #2. F-reachables class Foo { Object ref; Foo(Object r) { ref = r; } void finalize() { ref.call(); } }

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

Объекты с нетривиальным finalize(): ○ живут дольше: для STW как минимум (!) на один цикл GC ○ дополнительно удерживают все достижимые из них объекты (f-reachables) 81 История #2. F-reachables

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

Как обнаружить проблему с F-reachables? 84 Developer vs F-reachables jmap -finalizerinfo Unreachable instances waiting for finalization #instances class name ----------------------- 147532 StressTimer$FinalizeBallast 222 java.util.Timer$1 221 StressTimer$Task

Slide 85

Slide 85 text

Как Вы можете бороться с F-reachables? 85 Developer vs F-reachables

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

Как Вы можете бороться с F-reachables? 88 Developer vs F-reachables

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

91 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()

Slide 92

Slide 92 text

92 Developer vs F-reachables Поможет с Memory Drag только в Java 9+! JDK-8071507 Если не finalize(), то кто? java.lang.ref.Cleaner (ранее sun.misc.Cleaner)

Slide 93

Slide 93 text

93 Выводы ○ finalize() увеличивает Memory Drag ○ JVM могут облегчить симптомы (но делают это редко) ○ java.lang.ref.Cleaner since Java 9

Slide 94

Slide 94 text

94 История #3. Непотизм GC Непотизм (кумовство) — вид фаворитизма, предоставляющий привилегии родственникам или друзьям независимо от их профессиональных качеств.

Slide 95

Slide 95 text

95 Пример public class DummyList { private Node first; private Node last; private class Node { E value; Node next; public Node(E value, Node next) { ... } } }

Slide 96

Slide 96 text

96 Пример public void addLast(Node n) { final var l = last; last = n; if (l == null) { first = n; } else { l.next = n; } }

Slide 97

Slide 97 text

97 Пример 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; } }

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

100 История #3. Непотизм Сборка мусора по поколениям: ○ Молодое/старое поколения ○ Minor/Major GC ○ Все достижимое из старого поколения должно переживать Minor GC

Slide 101

Slide 101 text

101 История #3. Непотизм Young Generation Old Generation

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

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

Slide 107

Slide 107 text

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

Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

117 История #3. Непотизм Но ведь это искусственный пример? Tony Printezis о борьбе с непотизмом LinkedHashMap в TwitterJDK: https://youtu.be/M9o1LVfGp2A?t=1391

Slide 118

Slide 118 text

118 Как обнаружить непотизм?

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

120 Что делать?

Slide 121

Slide 121 text

121 Что делать? ○ Ждать Full GC

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

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

Slide 125

Slide 125 text

125 Что делать? void afterNodeRemoval(Node e) { // unlink LinkedHashMap.Entry p = (LinkedHashMap.Entry)e; b = p.before, a = p.after; p.before = p.after = null; ... } java.util.LinkedHashMap:

Slide 126

Slide 126 text

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

Slide 127

Slide 127 text

127 Что делать? ○ Ждать Full GC ○ Занулять ссылки из объектов при удалении ○ Увеличить размер молодого поколения -Xmn -XX:NewRatio -XX:NewSize

Slide 128

Slide 128 text

128 Ходячие объекты-мертвецы А может ли GC вообще не прийти за мертвым объектом?

Slide 129

Slide 129 text

129 Ходячие объекты-мертвецы

Slide 130

Slide 130 text

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

Slide 131

Slide 131 text

131 Точный 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"); }

Slide 132

Slide 132 text

132 Точный 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 понимает, какие локалы живы?

Slide 133

Slide 133 text

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

Slide 134

Slide 134 text

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

Slide 135

Slide 135 text

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

Slide 136

Slide 136 text

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

Slide 137

Slide 137 text

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

Slide 138

Slide 138 text

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

Slide 139

Slide 139 text

139 Точный GC ➢ Поддержка от компилятора ➢ Генерация карт стека и регистров ➢ Карты только в safe-points!

Slide 140

Slide 140 text

140 Консервативный GC А нельзя ли без поддержки от компилятора?

Slide 141

Slide 141 text

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

Slide 142

Slide 142 text

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

Slide 143

Slide 143 text

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

Slide 144

Slide 144 text

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

Slide 145

Slide 145 text

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

Slide 146

Slide 146 text

146 Консервативный GC Зачем такое нужно?

Slide 147

Slide 147 text

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

Slide 148

Slide 148 text

148 Консервативный GC Зачем такое нужно? ➢ Нет карт ⇒ они не занимают место ➢ Нет карт ⇒ нет затрат на генерацию ➢ Нет карт ⇒ можно выкинуть safe-points

Slide 149

Slide 149 text

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

Slide 150

Slide 150 text

150 Safe-points 1 - 2 машинные инструкции Обратные дуги циклов и эпилоги Ухудшают производительность ⇒ Агрессивно оптимизируются

Slide 151

Slide 151 text

151 Мир без safe-points

Slide 152

Slide 152 text

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

Slide 153

Slide 153 text

153 Мир без safe-points Производительность растет Размер кода уменьшается Многое работает через safe-points, поэтому потребует переработки

Slide 154

Slide 154 text

154 Консервативный GC А так вообще делают?

Slide 155

Slide 155 text

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

Slide 156

Slide 156 text

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

Slide 157

Slide 157 text

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

Slide 158

Slide 158 text

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

Slide 159

Slide 159 text

159 Консервативный GC А так вообще делают? Теория: Boehm GC, 1988, conservative Immix, 2008, mostly-precise Практика: MobiVM scala-native Excelsior JET Generation 1 (с 1999 по 2015)

Slide 160

Slide 160 text

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

Slide 161

Slide 161 text

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

Slide 162

Slide 162 text

162 Неограниченный Memory Drag public class Executor extends Thread { public abstract class Task implements Runnable { } final TaskQueue queue = new TaskQueue(); . . . }

Slide 163

Slide 163 text

163 Неограниченный Memory Drag public void mainLoop() { while (true) { synchronized (queue) { while (queue.isEmpty()) { queue.wait(); } Task t = queue.get(); t.run(); } } }

Slide 164

Slide 164 text

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

Slide 165

Slide 165 text

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

Slide 166

Slide 166 text

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

Slide 167

Slide 167 text

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

Slide 168

Slide 168 text

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

Slide 169

Slide 169 text

169 Неограниченный Memory Drag Может это искусственный пример? Нет, это класс java.util.Timer из JDK И там все еще хуже

Slide 170

Slide 170 text

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

Slide 171

Slide 171 text

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 threadReaper

Slide 172

Slide 172 text

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

Slide 173

Slide 173 text

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

Slide 174

Slide 174 text

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

Slide 175

Slide 175 text

175 Неограниченный 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.

Slide 176

Slide 176 text

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

Slide 177

Slide 177 text

177 Выводы Консервативный GC - большая сила, но и большая ответственность Консерва не только в GC (смотри первый пример)

Slide 178

Slide 178 text

178 Разминка 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: ...

Slide 179

Slide 179 text

179 Заключение

Slide 180

Slide 180 text

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

Slide 181

Slide 181 text

181 Заключение Не нужно пытаться предугадать время жизни объекта и рассчитывать на это в коде! Если вы все-таки на это решились: Используйте правильные костыли решения (reachabilityFence, Cleaner, Timer.cancel)

Slide 182

Slide 182 text

182 Q & A @jugnsk [email protected] @dbg_nsk