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

Walking dead objects, or GC is always right (2019)

Walking dead objects, or GC is always right (2019)

Automatic memory management is said to be one of the greatest features of Java as well as other managed programming languages. At the same time, JVM specification says almost nothing about how exactly it should work! As a result, there are so many various algorithms and strategies of Garbage Collection, each with its own benefits, trade-offs and implementation details. For example, different GCs answer the question "when should we reclaim an object that is considered to be garbage?" differently. It is not so obvious, and every answer can dramatically change the behaviour of an application. In this talk we will find out why GC store some dead objects on the heap, how this can influence your code and how to protect an application from zombie apocalypse.

Ivan Ugliansky

November 01, 2019
Tweet

More Decks by Ivan Ugliansky

Other Decks in Programming

Transcript

  1. 5 But what spec says? 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.
  2. 6 Expectation: ◦ Memory is somehow managed by JVM (the

    correctness is guaranteed) Garbage Collector
  3. 7 Expectation: ◦ Memory is somehow managed by JVM (the

    correctness is guaranteed) ◦ A developer doesn’t worry about it Garbage Collector
  4. 8 Garbage Collector Reality: 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
  5. 9 Garbage Collector Reality: 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) ...
  6. 10 Garbage Collector Reality: 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:
  7. 12 Agenda ◦ Different GC policies depending on liveness ◦

    How does it affect the execution? ◦ How to take it into account? (and avoid OOM error)
  8. 14 Tiny example 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
  9. 15 Tiny example 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
  10. 16 Tiny example 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
  11. 17 Tiny example 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
  12. 18 Tiny example 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: ...
  13. 19 Tiny example 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
  14. 20 Tiny example 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
  15. 21 Tiny example 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
  16. 22 Tiny example 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"); } Can GC collect arr1 here?
  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"); } 23 Tiny example Visibility scope of arr1
  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"); } 24 Tiny example Life range of arr1 Visibility scope of arr1
  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"); } 25 Tiny example GC has full right to collect arr1 here
  20. 26 Tiny example 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: ...
  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"); } 27 Tiny example Interpreter has no idea variables’ liveness!
  22. 28 Tiny example 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"); } Interpreter has no idea variables’ liveness! (and has to rely on the worst case)
  23. 31 Story #1. Ghost Objects Weak references: 1. java.lang.ref.* 2.

    Weakly reachable objects can be collected by GC
  24. 32 Story #1. Ghost Objects Weak references: 1. java.lang.ref.* 2.

    Weakly reachable objects can be collected by GC 3. Can get «rotten»
  25. 33 Story #1. Ghost Objects Weak references: 1. java.lang.ref.* 2.

    Weakly reachable objects can be collected by GC 3. Can get «rotten»
  26. 34 Tiny example public static void main(String[] args) { Object

    obj = new Object(); WeakReference ref = new WeakReference<>(obj); System.gc(); System.out.println(ref.get().hashCode()); }
  27. 35 Tiny example public static void main(String[] args) { Object

    obj = new Object(); WeakReference ref = new WeakReference<>(obj); System.gc(); System.out.println(ref.get().hashCode()); }
  28. 36 Tiny example public static void main(String[] args) { Object

    obj = new Object(); WeakReference ref = new WeakReference<>(obj); System.gc(); System.out.println(ref.get().hashCode()); }
  29. 37 Tiny example 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
  30. 38 Tiny example 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()); }
  31. 40 Ghost Objects 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 But that’s not a real code, right?
  32. 41 The real code private static ResourceBundle getBundleImpl(..., ClassLoader loader,

    ...) { ... CacheKey cacheKey = new CacheKey(baseName, locale, loader); ... // use loader via cacheKey ... return bundle; }
  33. 42 The real code private static ResourceBundle getBundleImpl(..., ClassLoader loader,

    ...) { ... CacheKey cacheKey = new CacheKey(baseName, locale, loader); ... // use loader via cacheKey ... return bundle; } actually a weak ref. loader’s last use
  34. 45 What can we do? ◦ Check that a reference

    hasn't been nullified yet before use
  35. 46 What can we do? ◦ Check that a reference

    hasn't been nullified yet before use ◦ Keep an object alive!
  36. 47 What can we do? public static void main(String[] args)

    { Object obj = new Object(); WeakReference ref = new WeakReference<>(obj); System.gc(); System.out.println(ref.get().hashCode()); }
  37. 48 What can we do? 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 ??? }
  38. 49 What can we do? 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 ??? } But how exactly should we use the object here?
  39. 50 What can we do? 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 ??? } But how exactly should we use the object here? Compiler can eliminate or move a use!
  40. 52 Keeping an object alive ◦ Pass as an argument

    ◦ Put into a field ◦ Reference.reachabilityFence(obj) Since Java 9
  41. 53 Treatment 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 ??? }
  42. 54 Treatment 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); }
  43. 55 Treatment 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; }
  44. 56 Summary ◦ GC has full right to collect an

    object just after it’s last use ◦ The compiler has full right to eliminate or move variable uses ◦ Reference.reachabilityFence keeps an object alive
  45. 58 Walking dead objects An opposite problem: truly dead objects

    are not collected by GC by some reason.
  46. 60 Walking dead objects Some memory can be dragged to

    the next* GC cycle and that’s totally OK.
  47. 63 Walking dead objects Huge Memory Drag ⇒ Many «zombie»

    objects ⇒ As a result: ◦ Unexpected OOMs ◦ java.lang.ref works poorly
  48. 65 Story #2. F-reachables Object.finalize() JavaDoc in JDK 9+: The

    finalization mechanism is inherently problematic.
  49. 66 Story #2. F-reachables Object.finalize() JavaDoc in JDK 9+: The

    finalization mechanism is inherently problematic. Finalization can lead to performance issues, deadlocks, and hangs.
  50. 67 Story #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.
  51. 68 Story #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.
  52. 70 Story #2. F-reachables ➢ JDK 8 ㄧ 89 overrides

    of finalize() ➢ JDK 9 ㄧ 87 overrides (Object.finalize() is deprecated since 9)
  53. 71 Story #2. F-reachables ➢ JDK 8 ㄧ 89 overrides

    of finalize() ➢ JDK 9 ㄧ 87 overrides (Object.finalize() is deprecated since 9) ➢ JDK 11 ㄧ 84 overrides
  54. 75 Story #2. F-reachables Java Threads Finalizer Thread STW GC

    Threads finalize() can’t be executed here!
  55. 76 Story #2. F-reachables Objects with nontrivial finalize(): ◦ survive

    longer: at least (!) for one GC cycle in case of STW
  56. 77 Story #2. F-reachables class Foo { Object ref; Foo(Object

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

    r; } void finalize() { ref.call(); } } 78 Story #2. F-reachables Foo ref ref is used in finalize, so it also survives
  58. 79 Story #2. F-reachables ref is used in finalize, so

    it also survives as well as all reachable from ref objects! Foo ref class Foo { Object ref; Foo(Object r) { ref = r; } void finalize() { ref.call(); } }
  59. Objects with nontrivial finalize(): ◦ survive longer: at least (!)

    for one GC cycle in case of STW ◦ retain all reachable objects (f-reachables) 80 Story #2. F-reachables
  60. 83 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 How to notice a problem?
  61. 85 Developer vs F-reachables Foo ref1 ref2 ref3 resource1 resource2

    resource3 finalize() How you can fight F-reachables?
  62. 86 Developer vs F-reachables Foo ref1 ref2 ref3 resource1 resource2

    resource3 State state finalize() How you can fight F-reachables?
  63. 90 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()
  64. 91 Developer vs F-reachables Reduces Memory Drag since Java 9+!

    JDK-8071507 java.lang.ref.Cleaner (old sun.misc.Cleaner) What to use instead of finalize()?
  65. 92 Summary ◦ finalize() increases Memory Drag ◦ JVM can

    handle it (but usually doesn’t care) ◦ java.lang.ref.Cleaner since Java 9
  66. 93 Story #3. Nepotism Nepotism is based on favour granted

    to relatives in various fields, including business, politics, entertainment and other activities (c)
  67. 94 Tiny example 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) { ... } } }
  68. 95 Tiny example public void addLast(Node n) { final var

    l = last; last = n; if (l == null) { first = n; } else { l.next = n; } }
  69. 96 Tiny example 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; } }
  70. public void addLast(Node n) { final var l = last;

    last = n; if (l == null) { first = n; } else { l.next = n; } } 97 Tiny example public void removeFirst() { if (first != null) { var next = first.next; first = next; if (next == null) { last = null; } } }
  71. 99 Story #3. Nepotism Generational GC: ◦ Young/Old generations ◦

    Minor/Major GC ◦ All young objects reachable from old gen should survive Minor GC
  72. 114 Story #3. Nepotism Generational GC consciously increases Memory Drag.

    Old generation - a source of walking dead objects
  73. 116 Story #3. Nepotism Tony Printezis about fighting with nepotism

    of LinkedHashMap in TwitterJDK: https://youtu.be/M9o1LVfGp2A?t=1391 But that’s not a real code, right?
  74. 118 How to notice a nepotism? Signs of nepotism: 1.

    Frequent and long Full GC pauses (use -XX:+PrintGCDetails) 2. A lot of suspicious live objects (see jmap -dump <PID>)
  75. 121 How to fight nepotism? ◦ Keep calm and wait

    for Full GC ◦ Manually nullify refs
  76. 122 /** * 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: How to fight nepotism?
  77. /** * 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 ... } 123 java.util.LinkedList: How to fight nepotism?
  78. 124 How to fight nepotism? ◦ Keep calm and wait

    for Full GC ◦ Manually nullify refs
  79. 125 How to fight nepotism? ◦ Keep calm and wait

    for Full GC ◦ Manually nullify refs ◦ Tune size of young generation -Xmn -XX:NewRatio -XX:NewSize
  80. 129 Precise 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"); }
  81. 130 Precise 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"); } How does GC know, which locales are alive at the moment?
  82. 139 Conservative GC ➢ Consider every value on the stack

    as a potential root ➢ Filter out obviously not references at runtime
  83. 143 Conservative GC ➢ Consider every value on the stack

    as a potential root ➢ Filter out obviously not references at runtime ➢ Mistakes are possible, and it increases Memory Drag
  84. 145 Conservative GC Why do this? ➢ No maps ⇒

    better memory footprint ➢ No maps ⇒ faster code generation
  85. 146 Conservative GC Why do this? ➢ No maps ⇒

    better memory footprint ➢ No maps ⇒ faster code generation ➢ No maps ⇒ no need for safe-points!
  86. 148 Safe-points 1 - 2 instructions Backward branches and epilogues

    Really expensive ⇒ optimized heavily in almost every JVM
  87. 151 World without safe-points Greater performance Smaller code size Many

    mechanisms in JVM should be reworked as they rely on safe-points
  88. 153 Conservative GC Is it actually possible? In theory: Boehm

    GC, 1988, conservative Immix, 2008, mostly-precise
  89. 154 Conservative GC Is it actually possible? In theory: Boehm

    GC, 1988, conservative Immix, 2008, mostly-precise In practice:
  90. 155 Conservative GC Is it actually possible? In theory: Boehm

    GC, 1988, conservative Immix, 2008, mostly-precise In practice: MobiVM
  91. 156 Conservative GC Is it actually possible? In theory: Boehm

    GC, 1988, conservative Immix, 2008, mostly-precise In practice: MobiVM scala-native
  92. 157 Conservative GC Is it actually possible? In theory: Boehm

    GC, 1988, conservative Immix, 2008, mostly-precise In practice: MobiVM scala-native Excelsior JET Generation 1 (1999 - 2015)
  93. 158 Unlimited Memory Drag Conservative mistake: ◦ usually an object

    survives for several additional GC cycles (until «false root» is on the stack)
  94. 159 Unlimited Memory Drag Conservative mistake: ◦ usually an object

    survives for several additional GC cycles (until «false root» is on the stack) ◦ but sometimes an object stucks in the heap forever
  95. 160 Unlimited Memory Drag public class Executor extends Thread {

    public abstract class Task implements Runnable { } final TaskQueue queue = new TaskQueue(); . . . }
  96. 161 Unlimited Memory Drag public void mainLoop() { while (true)

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

    while (queue.isEmpty()) { queue.wait(); } Task t = queue.get(); t.run(); } } } 162 Unlimited Memory Drag stack sp+20h addr t
  98. public void mainLoop() { while (true) { synchronized (queue) {

    while (queue.isEmpty()) { queue.wait(); } Task t = queue.get(); t.run(); } } } 163 Unlimited Memory Drag stack addr t sp+20h
  99. public void mainLoop() { while (true) { synchronized (queue) {

    while (queue.isEmpty()) { queue.wait(); } Task t = queue.get(); t.run(); } } } 164 Unlimited Memory Drag stack addr t sp+20h
  100. public void mainLoop() { while (true) { synchronized (queue) {

    while (queue.isEmpty()) { queue.wait(); } Task t = queue.get(); t.run(); } } } 165 Unlimited Memory Drag stack addr t sp+20h
  101. 167 Unlimited Memory Drag It is. This is java.util.Timer from

    JDK And it is even worse there But that’s not a real code, right?
  102. public void mainLoop() { while (true) { synchronized (queue) {

    while (queue.isEmpty()) { queue.wait(); } Task t = queue.get(); t.run(); } } } 168 Unlimited Memory Drag stack addr t sp+20h
  103. public void mainLoop() { while (true) { synchronized (queue) {

    while (queue.isEmpty()) { queue.wait(); } Task t = queue.get(); t.run(); } } } 169 Unlimited Memory Drag stack addr t sp+20h threadReaper
  104. 173 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. Unlimited Memory Drag
  105. 175 Summary Conservative GC - great power, but also great

    responsibility Not only GC can be conservative! (look at the first tiny example)
  106. 176 Tiny example 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: ...
  107. 178 Conclusion Please do not try to predict an object’s

    life time and rely on it in your code!
  108. 179 Conclusion Please do not try to predict an object’s

    life time and rely on it in your code! And if you do: Use certified workarounds tools for that (reachabilityFence, Cleaner, Timer.cancel)