Java Garbage
Collection
& GC friendly Coding
By Ayub
Garbage Collection
• Weak generational Hypothesis
• Most objects soon become unreachable.
• References from old objects to young objects only exist in
small numbers
• Java Hotspot VM split into areas
• Young Generation
• Old Generation
• Permanent
GC important concepts
• Frequency of minor GC is dictated by
• Rate of Object Allocation
• Size of the Eden space
• Frequency of object promotion into tenured space is dictated by:
• Frequency of minor GCs(how quickly Objects age)
• Size of the survivor spaces
• Tenuring threshold
• Ideally as little data as possible should be promoted
• Involves coping, thus I/O and must be stop the world
• Object retention impacts latency more then object allocation
• GC only visits live Object
• GC time is a function of number of live object and graph complexity
• Object allocation is very cheap(Bump the pointer and TLAB)
• Reclaiming the short live object is very cheap
Garbage Collection
• GC involves
• Stop the World
• Mark
• Sweep/Delete
• Compact
Generational Collection Process
Generational Collection Process
Garbage Collection
• What if an object in the old generation need to
reference an object in the young generation?
• To address this Issue and keep Minor GC small, JVM
use
• Write Barrier (has two types of implementations )
• Card table
• Snapshot at the beginning(SATB) for G1
Card Marking
• The heap is divided into a set of cards, each of
which is usually smaller than a memory page.
The JVM maintains a card table, with One byte,
corresponding to each card in the heap. Each
time a pointer field in an object in the heap is
modified, the corresponding bit in the card
table for that card is set.
• Use of Card table , the Young GC identify live
objects in the young generation that are
reference by Old Generation so quickly without
having to scan the entire (and potentially
larger) old generation.
Fast Allocation
• HotSpot JVM use two techniques
• Bump-the-pointer(Single Thread)
• tracks the last object allocated to the Eden space
• TLAB(Thread Local Allocation Buffers)
• For Multi-threaded application
Garbage Collectors
Garbage Collectors
G1 Young Garbage
Collection
G1 Garbage Collection
Old Generation Collection
with G1
• Initial Mark
(Stop the World Event)
• Root Region Scanning
• Concurrent Marking
• Remark
(Stop the World Event)
• Cleanup
(Stop the World Event and
Concurrent)
• (*) Copying
(Stop the World Event)
G1 process
GC Friendly Code
• Rule of Thumb
• Don’t be afraid to allocate quickly disposed of Object
• Immediate results
• GC love small immutable Object and short-lived objects
• So long as they don’t survive in minor GC
• Try to avoid complex inter-object relationships
• Reduce complexity of graph to be analysed by GC
• Don’t allocate Object needlessly
• Anatomy of a Java object
• Layout of a java.lang.Integer object for a 32-bit Java process
Anatomy of a Java
object
• Layout of an int array object for a 32-bit Java
process
• Anatomy of more-complex data structures
Anatomy of Java Object
• Layout of a java.lang.Integer object and an int array for a 64-
bit Java process
• Compressed References and Compressed Ordinary Object
Pointers (OOPs)
• -XX:+compressedrefs and Compressed OOPs (-
XX:+UseCompressedOops
GC Friendly Code
• Large Object
• Expensive to allocate
• Expensive to initialize
• Large Objects of different size can cause heap fragmentation
• Avoid Large object allocation
• Especially frequent large object allocations during application ‘steady state’
• Not so bad during application warm-up(pooling, pool of large object at the
start-up time)
GC friendly code
• Data Structure
• Mark phase iterate over the Live object
• Some data structures are hard to mark in parallel
• Avoid resizing of array of backed “container objects”
• Use constructor that takes an explicit size
• Stringbuilder /buffer expands cause copying
• ArrayList add/remove at the beginning of the list cause copying
• Object Pooling Issue
• Access to the pool requires locking
• Contributes to live Object to be visited during every the GC
• Inner classes
• Have an implicit reference to the outer instance can potentially increase object
retention and graph complexity
GC Friendly Code
• Reference Objects Sensibly
• Soft, weak, phantom, reference require special
processing during GC
• Finalizers should be avoid(Don’t use them)
• Will keep objects alive for one extra GC cycle(require to
put the object into finalizer queue)
• Detrimental to pause time goal
• Use a method to explicitly free resources and manage
this manually before object is no longer required
• Question ?
Thank you every

Java garbage collection & GC friendly coding

  • 1.
    Java Garbage Collection & GCfriendly Coding By Ayub
  • 2.
    Garbage Collection • Weakgenerational Hypothesis • Most objects soon become unreachable. • References from old objects to young objects only exist in small numbers • Java Hotspot VM split into areas • Young Generation • Old Generation • Permanent
  • 3.
    GC important concepts •Frequency of minor GC is dictated by • Rate of Object Allocation • Size of the Eden space • Frequency of object promotion into tenured space is dictated by: • Frequency of minor GCs(how quickly Objects age) • Size of the survivor spaces • Tenuring threshold • Ideally as little data as possible should be promoted • Involves coping, thus I/O and must be stop the world • Object retention impacts latency more then object allocation • GC only visits live Object • GC time is a function of number of live object and graph complexity • Object allocation is very cheap(Bump the pointer and TLAB) • Reclaiming the short live object is very cheap
  • 4.
    Garbage Collection • GCinvolves • Stop the World • Mark • Sweep/Delete • Compact
  • 5.
  • 6.
  • 7.
    Garbage Collection • Whatif an object in the old generation need to reference an object in the young generation? • To address this Issue and keep Minor GC small, JVM use • Write Barrier (has two types of implementations ) • Card table • Snapshot at the beginning(SATB) for G1
  • 8.
    Card Marking • Theheap is divided into a set of cards, each of which is usually smaller than a memory page. The JVM maintains a card table, with One byte, corresponding to each card in the heap. Each time a pointer field in an object in the heap is modified, the corresponding bit in the card table for that card is set. • Use of Card table , the Young GC identify live objects in the young generation that are reference by Old Generation so quickly without having to scan the entire (and potentially larger) old generation.
  • 9.
    Fast Allocation • HotSpotJVM use two techniques • Bump-the-pointer(Single Thread) • tracks the last object allocated to the Eden space • TLAB(Thread Local Allocation Buffers) • For Multi-threaded application
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
    Old Generation Collection withG1 • Initial Mark (Stop the World Event) • Root Region Scanning • Concurrent Marking • Remark (Stop the World Event) • Cleanup (Stop the World Event and Concurrent) • (*) Copying (Stop the World Event)
  • 15.
  • 17.
    GC Friendly Code •Rule of Thumb • Don’t be afraid to allocate quickly disposed of Object • Immediate results • GC love small immutable Object and short-lived objects • So long as they don’t survive in minor GC • Try to avoid complex inter-object relationships • Reduce complexity of graph to be analysed by GC • Don’t allocate Object needlessly • Anatomy of a Java object • Layout of a java.lang.Integer object for a 32-bit Java process
  • 18.
    Anatomy of aJava object • Layout of an int array object for a 32-bit Java process • Anatomy of more-complex data structures
  • 19.
    Anatomy of JavaObject • Layout of a java.lang.Integer object and an int array for a 64- bit Java process • Compressed References and Compressed Ordinary Object Pointers (OOPs) • -XX:+compressedrefs and Compressed OOPs (- XX:+UseCompressedOops
  • 20.
    GC Friendly Code •Large Object • Expensive to allocate • Expensive to initialize • Large Objects of different size can cause heap fragmentation • Avoid Large object allocation • Especially frequent large object allocations during application ‘steady state’ • Not so bad during application warm-up(pooling, pool of large object at the start-up time)
  • 21.
    GC friendly code •Data Structure • Mark phase iterate over the Live object • Some data structures are hard to mark in parallel • Avoid resizing of array of backed “container objects” • Use constructor that takes an explicit size • Stringbuilder /buffer expands cause copying • ArrayList add/remove at the beginning of the list cause copying • Object Pooling Issue • Access to the pool requires locking • Contributes to live Object to be visited during every the GC • Inner classes • Have an implicit reference to the outer instance can potentially increase object retention and graph complexity
  • 22.
    GC Friendly Code •Reference Objects Sensibly • Soft, weak, phantom, reference require special processing during GC • Finalizers should be avoid(Don’t use them) • Will keep objects alive for one extra GC cycle(require to put the object into finalizer queue) • Detrimental to pause time goal • Use a method to explicitly free resources and manage this manually before object is no longer required
  • 23.
  • 24.

Editor's Notes

  • #2 Welcome ever body, As You Know, Java Garbage Collection is a Big Topics. I could not cover all the things with in 45 Minutes. I only cover some Some basics of garbage collection Garbage Collection process, Generation Garbage collection How the generation Garbage collection works Types of GC used in JVM Serial Parallel CMS(Concurrent Mark and Sweep) And the Most Recent G1 Lastly We will look, how to write GC friendly Code What are things we need to consider during the designing and coding etc.
  • #3 Java garbage collection is created based on the following two hypotheses. Most objects soon become unreachable. References from old objects to young objects only exist in small numbers To preserve the strengths of this hypothesis, Heap is physically divided into three areas - young generation, old generation and permanent- in HotSpot VM. Young generation Typically small, most of the newly created objects located here. Most objects are created in the young generation and soon become unreachable, then disappear we called it Minor GC and occurs frequently. Minor GC are very efficient because it concentrate on a space that usually small and is likely to contain lots of garbage objects. It is also split into 3 parts, Eden and two serviviors Old generation It is bigger in size and its occupancy grows slowly The objects that did not become unreachable and survived from the young generation are copied here the GC occurs less frequently than in the young generation. When objects disappear from the old generation, we say a "major GC" (or a "full GC") has occurred. When they do occur they are quite lengthy Permanent generation it contains metadata required by the JVM to describe the classes and methods used in the application. The permanent generation is populated by the JVM at runtime based on classes in use by the application. In addition, and methods may be stored here.
  • #4 Frequency of minor GC is dictated by Rate of Object Allocation Higher the rate of allocation, Eden space will be filled up quickly, which tiger the minor GC Size of the Eden space If the size of Eden space is small, it will soon filled up which intern run Minor GC frequently and GC pause time will be small, If Eden space is big, then it will take relatively long time to fill up, Minor GC will run less frequently and GC pause time will relatively higher Frequency of object promotion into tenured space is dictated by: Frequency of minor GCs(how quickly Objects age) Size of the survivor spaces Tenuring threshold(8) Ideally as little data as possible should be promoted promotion involves coping, thus IO , must be stop the world Object retention impacts latency more then object allocation GC only visits live Object GC time is a function of number of live object and graph complexity Graph is the relationship between the variables in your application ‘s objects that have live data. The complexity of the graph is depends on in terms of relationship. Object allocation is very cheap(Eden space treated as stack. Using Bump pointer and Thread allocation buffer, object allocation is very fast, 10 cycles common case, we will see latter) Reclaiming the short live object is very cheap Reclaiming the short object is very cheap. JVM only look at the live object and copy that object to another place. So, anything that is garbage JVM does not do anything, It just ignor. When copy the Object, It just reset the bump pointer.
  • #5 GC involves Stop the World The term is "stop-the-world." Stop-the-world will occur no matter which GC algorithm you choose. Stop-the-world means that the JVM is stopping the application from running to execute a GC. When stop-the-world occurs, every thread except for the threads needed for the GC will stop their tasks Mark Star from the root node of application(main) and walks the Objects graph and mark the object that are reachable Delete/Sweep: Delete unreachable objects Compacting: Compact the memory by moving around the objects and making the allocation contiguous than fragmented
  • #6 This is the generational garbage collection look like Any new objects are allocated to the Eden space. Both survivor spaces are empty at the start up. When the Eden space fills up, a minor garbage collection is triggered. Referenced objects are moved to the first survivor space. Unreferenced objects are deleted when the Eden space is cleared. At the next minor GC, the same thing happens in the eden space. Unreferenced objects are deleted and referenced objects are moved to a survivor space. However, in this case, they are moved to the second survivor space (S1). In addition, objects from the last minor GC on the first survivor space (S0) have their age incremented and get moved to S1. Once all surviving objects have been moved to S1, both S0 and Eden are cleared. Notice we now have differently aged object in the survivor space.
  • #7  At the next minor GC, the same process repeats. However this time the survivor spaces switch. Referenced objects are moved to S0. Surviving objects are aged. Eden and S1 are cleared. This slide demonstrates promotion. After a minor GC, when aged objects reach a certain age threshold (8 in this example) they are promoted from young generation to old generation. As minor GCs continue to occur, objects will continue to be promoted to the old generation space. A major GC will be performed on the old generation which cleans up and compacts that space.
  • #8 What if an object in the old generation need to reference an object in the young generation? [Then Minor GC needs to visit Old generation to find that type of live objects, which will increase Minor GC time] To address this Issue and keep Minor GC small, JVM use Write Barrier: to maintain card table. it is small fragment of code that sets an entity of the card table[see later]. The interpreter executes a write barrier every time it executes a bytecode that updates a reference field. The JIT compiler emits the write barrier after emitting the code that updates a reference field. Although write barriers have an impact performance on the execution a bit, their presence allows for much faster minor collections, which typically improves the end-to-end throughput of an application Two types implementations Card table Snapshot at the beginning(SATB) for G1
  • #9 The heap is divided into a set of cards, each of which is usually smaller than a memory page typically 512 bytes, The JVM maintains a card map, with One byte, corresponding to each card in the heap. Each time a pointer field in an object in the heap is modified, the corresponding bit in the card map for that card is set. Use of Card table , the Young GC identify live objects in the young generation that are reference by Old Generation so quickly without having to scan the entire (and potentially larger) old generation. http://psy-lob-saw.blogspot.com/2014/10/the-jvm-write-barrier-card-marking.html So setting a reference throws in the overhead of a few instructions, which boil down to: CARD_TABLE [this address >> 9] = 0; The tradeoff here is between the benefit of card marking (limiting the scope of required old generation scanning on young generation collection) vs. the fixed operation overhead for all reference writes. The associated write to memory for card marking can sometimes cause performance issues for highly concurrent code. This is why in OpenJDK7 we have a new option called UseCondCardMark. [UPDATE: as JP points out in the comments, the (>> 9) is converting the address to the relevant card index. Cards are 512 bytes in size so the shift is in fact address/512 to find the card index. ]
  • #10 JVM Object allocation need to be very fast. For Fast allocations, HotSpot JVM use two techniques Bump-the-pointer(Single Thread) The end of the last allocated object being tracked (usually called top) and when a new allocation request needs to be satisfied, the allocator needs only to check whether it will fit between top and the end of the Eden. If it does, top is bumped to the end of the newly allocated object TLAB: The most of the Java applications are multi-threaded and their allocation operations need to be multi-threaded safe. If they simply used global locks to ensure this, then allocation into Eden would become a bottleneck and degrade performance. Instead, the Java HotSpot VM has adopted a technique called Thread-Local Allocation Buffers (TLABs), which improves multi-threaded allocation throughput by giving each thread its own buffer (i.e., a small chunk of the Eden) from which to allocate. Since only one thread can be allocating into each TLAB, allocation can take place quickly with the bump-the-pointer technique (and without any locking). When a thread fills up its TLAB and needs to get a new one (an infrequent operation), however, it needs to do so in a multi-threaded safe way .
  • #11 Serial Collector Basic Garbage collector that runs in single thread can be used for basic application Minor GC is same as Describe previously Major GC is “mark-sweep-compact.” Parallel Collector Minor GC Take place in parallel, using all available CPUs Major GC Serially CMS Collector Minor GC Same as parallel or serial(Bcz most of the GC pause due to old Old generation) Major GC CMS
  • #12 Ideal Scenario : After application initialization phase, only experience minor GCs and old generation growth is negligible. Minor GC as genrally faster Start with Parallel GC Parallel GC offer the fastes Minor GCs times when you have multiple cores Move to CMS if Old generation collection is needed Minor GC time will be slower due to promotion into free lists Hopefully this with avoid full compacting collection of gen
  • #13 G1 Heap Structure: The heap is one memory area split into many fixed sized regions. Region size is chosen by the JVM at startup. The JVM generally targets around 2000 regions varying in size from 1 to 32Mb. G1 Heap Allocation : these regions are mapped into logical representations of Eden, Survivor, and old generation spaces. The colors in the picture shows which region is associated with which role. Live objects are evacuated (i.e., copied or moved) from one region to another. Regions are designed to be collected in parallel with or without stopping all other application threads. As shown regions can be allocated into Eden, survivor, and old generation regions. In addition, there is a fourth type of object known as Humongous regions. These regions are designed to hold objects that are 50% the size of a standard region or larger. They are stored as a set of contiguous regions. Finally the last type of regions would be the unused areas of the heap. Young Generation in G1 Blue regions hold old generation objects and green regions hold young generation objects. Note that the regions are not required to be contiguous like the older garbage collectors. A Young GC in G1 Live objects are evacuated (i.e., copied or moved) to one or more survivor regions. If the aging threshold is met, some of the objects are promoted to old generation region This is a stop the world (STW) pause. Eden size and survivor size is calculated for the next young GC. Accounting information is kept to help calculate the size. Things like the pause time goal are taken into consideration. This approach makes it very easy to resize regions, making them bigger or smaller as needed.
  • #14 The heap is a single memory space split into regions. Young generation memory is composed of a set of non-contiguous regions. This makes it easy to resize when needed. Young generation garbage collections, or young GCs, are stop the world events. All application threads are stopped for the operation. The young GC is done in parallel using multiple threads. Live objects are copied to new survivor or old generation regions.
  • #15 Initial Mark: This is a stop the world event. With G1, it is piggybacked on a normal young GC. Mark survivor regions (root regions) which may have references to objects in old generation. Root Region Scanning: Scan survivor regions for references into the old generation. This happens while the application continues to run. The phase must be completed before a young GC can occur. Concurrent Marking: Find live objects over the entire heap. This happens while the application is running. This phase can be interrupted by young generation garbage collections.  Remark(Stop the World Event): Completes the marking of live object in the heap. Uses an algorithm called snapshot-at-the-beginning (SATB) which is much faster than what was used in the CMS collector. Cleanup(Stop the World Event and Concurrent): Performs accounting on live objects and completely free regions. (Stop the world) Scrubs the Remembered Sets. (Stop the world) Reset the empty regions and return them to the free list. (Concurrent) Copying(Stop the World Event) unused These are the stop the world pauses to evacuate or copy live objects to new regions. This can be done with young generation regions which are logged as [GC pause (young)]. Or both young and old generation regions which are logged as [GC Pause (mixed)].
  • #16 Initial marking of live object is piggybacked on a young generation garbage collection. In the logs this is noted as GC pause (young)(inital-mark). Concurrent Marking Phase Liveness information is calculated concurrently while the application is running. This liveness information identifies which regions will be best to reclaim during an evacuation pause. If empty regions are found (as denoted by the "X"), they are removed immediately in the Remark phase. Also, "accounting" information that determines liveness is calculated. Uses the Snapshot-at-the-Beginning (SATB) algorithm which is much faster then what was used with CMS. Empty regions are removed and reclaimed. Region liveness is now calculated for all regions. Copying/Cleanup Phase G1 selects the regions with the lowest "liveness", those regions which can be collected the fastest. Then those regions are collected at the same time as a young GC. This is denoted in the logs as [GC pause (mixed)]. So both young and old generations are collected at the same time.
  • #17 Copying/Cleanup Phase Young generation and old generation are reclaimed at the same time. Old generation regions are selected based on their liveness.
  • #18 1. If You know Object will be allocated for short period of time. Like Object creating in inner loop is short lived, s. As Object creating and reclaims is cheap 2. This is rule of thumb, it is easy to say, it not easy to implement, bcz GC has to analyze full graph of live object’s referrence. More the graph of complex relationship more the time to analyse. So it is something you have to thing about it when you design an application Don’t allocate Object needlessly: - More frequent allocations means more frequent GCs - More frequent GCs implies faster object aging - Faster Object aging means faster promotion to old generation - when means more frequent concurrent collections or full compacting collections o old generation Those are not hard and fast rule but we may consider those thing in designing application http://www.ibm.com/developerworks/library/j-codetoheap/ 128 bits of data are used to store the 32 bits of int value, because the object metadata uses the rest of those 128 bits.
  • #19 1. 160 bits of data used to store 32 bits of Single Array int value, because the array metadata, it uses 160 bits. For primitives such as byte, int, and long, a single-entry array is more expensive in terms of memory than the corresponding wrapper object (Byte,Integer, or Long) for the single field. Good object-oriented design and programming encourage the use of encapsulation (providing interface classes that control access to data) and delegation (the use of helper objects to carry out tasks). Encapsulation and delegation cause the representation of most data structures to involve multiple objects. 2 . A simple example is a java.lang.String object. The data in a java.lang.String object is an array of characters that is encapsulated by a java.lang.String object that manages and controls access to the character array. The layout of a java.lang.String object for a 32-bit Java process might look like Figure 4: java.lang.String object contains — in addition to the standard object metadata — some fields to manage the string data. Typically, these fields are a hash value, a count of the size of the string, the offset into the string data, and an object reference to the character array itself. This means that to have a string of 8 characters (128 bits of char data), 256 bits of data are for the character array and 224 bits of data are for the Java.lang.String object that manages it, making a total of 480 bits (60 bytes) to represent 128 bits (16 bytes) of data. In general, the more complex a data structure becomes, the greater its overhead
  • #20 It shows that for a 64-bit Integer object, 224 bits of data are now being used to store the 32 bits used for the int field — an overhead ratio of 7:1. For a 64-bit single-element int array, 288 bits of data are used to store the 32-bit int entry — an overhead of 9:1. The effect of this on real applications is that the Java heap memory usage of an application that previously ran on a 32-bit Java runtime increases dramatically when it's moved to a 64-bit Java runtime. Typically, the increase is on the order of 70 percent of the original heap size. For example, a Java application using 1GB of Java heap with the 32-bit Java runtime will typically use 1.7GB of Java heap with the 64-bit Java runtime. Compressed References and Compressed Ordinary Object Pointers (OOPs) IBM and Oracle JVMs both provide object-reference compression capabilities via the Compressed References (-Xcompressedrefs) and Compressed OOPs (-XX:+UseCompressedOops) options, respectively. Use of these options enables the object fields and the object metadata values to be stored in 32 bits rather than 64 bits. This has the effect of negating the 70 percent Java-heap memory increase when an application is moved from a 32-bit Java runtime to a 64-bit Java runtime. Note that the options have no effect on the memory usage of the native heap; it is still higher with the 64-bit Java runtime than with the 32-bit Java runtime.
  • #21 Large Objects Expensive to allocate(May not be allocated in Eden or survivors space, straight in old Gen, if you use CMS then you have to find out the free location that fits the large Object in Old. So it may not use fast path. Expensive to initialize(Java Specc, Requires Zeroing, JVM ensures every bytes need to be Zeros) Large Objects of different size can cause heap fragmentation For non-compacting or partially compacting GCs Avoid Large object allocation(if you can, Not hard & fast rule) Especially frequent large object allocations during application ‘steady state’ Not so bad during application warm-up(pooling, pool of large object at the start-up time)
  • #22  Mark phase iterate over the Live object Some data structures are hard to mark in parallel Avoid resizing of array of backed “container objects” Use constructor that takes an explicit size Object Pooling Issue[it is very good technique, it works well for certain type of object like JDBC connection objects, you need frequently, but you need to ware of that, if you have large number of object in the pool but not necessarily using any more, you are adding complexity graph and live data adding more object that GC need to visit every time] Contributes to live objects visited during GC GC pause is function of number of live objects Access to the pool requires locking Scalability issue Weigh against the benefit of large object allocation at start-up Inner classes Have an implicit reference to the outer instance Can potentially increase object retention and graph complexity [Java 8, Lot on inner class in replace by lamda expression, lamda expression invoke bytecode dynamically in JVM]   Net affect is the potential for increased GC duration
  • #23 Soft :The objects which are softly referenced will not be garbage collected (even though they are available for garbage collection) until JVM badly needs memory. These objects will be cleared from the memory only if JVM runs out of memory. You can create a soft reference to an existing object by using  java.lang.ref.SoftReference class. his SoftReference type object is internally referring to A-type object to which ‘a’ is also pointing. When ‘a’ is made to point to null, object to which ‘a’ is pointing earlier becomes eligible for garbage collection. But, it will be garbage collected only when JVM needs memory. Because, it is softly referenced by ‘softA’ object. his method returns reference to the object if object is not cleared from the memory. If object is cleared from the memory, it will return null. Waek:JVM ignores the weak references. That means objects which has only week references are eligible for garbage collection. They are likely to be garbage collected when JVM runs garbage collector thread. JVM doesn’t show any regard for weak references. Phantom:The objects which are being referenced by phantom references are eligible for garbage collection. But, before removing them from the memory, JVM puts them in a queue called‘reference queue’ . They are put in a reference queue after calling finalize() method on them. You can’t retrieve back the objects which are being phantom referenced. That means calling get() method on phantom reference always returns null.