Understand Java garbage collection

  • 2020-05-07 19:43:29
  • OfStack

When the program creates an entity of reference type such as object, array, etc., the system will allocate a block of memory for this object in the heap memory, and the object will be saved in this memory. When this memory is no longer referred to by any reference variables, this memory will become garbage and wait for garbage collection mechanism to recover. The garbage collection mechanism has three characteristics:

The garbage collection mechanism is only responsible for collecting objects in the heap memory, not any physical resources (such as database connections, open file resources, etc.), nor any memory allocated for the object in a way other than the way in which the object was created (such as the memory requested by the object by calling malloc in the local method).
The program cannot precisely control the operation of garbage collection, and can only recommend garbage collection. There are two ways to recommend garbage collection: System.gc () and Runtime.getRuntime ().gc ().
The finalize() method is always called before any object is garbage collected, but the timing of the call to finalize() is also uncertain, as is the timing of the garbage collection.
For the above three characteristics, there are three problems:

1. You must manually clean up and free up memory and other physical resources allocated in ways other than object creation. Also be careful to eliminate out-of-date object references, which might cause OOM.

Manual cleaning usually USES try... finally... This is the code structure.

Here's an example:


import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class ManualClear {

 public static void main(String[] args) {
  FileInputStream fileInputStream = null;
  try {
   fileInputStream = new FileInputStream("./src/ManualClear.java");
  } catch (FileNotFoundException e) {
   System.out.println(e.getMessage());
   e.printStackTrace();
   return;
  }

  try {
   byte[] bbuf = new byte[1024];
   int hasRead = 0;
   try {
    while ((hasRead = fileInputStream.read(bbuf)) > 0) {
     System.out.println(new String(bbuf, 0, hasRead));
    }
   } catch (IOException e) {
    e.printStackTrace();
   }
  } finally {
   try {
    fileInputStream.close();
   } catch (IOException e) {
    e.printStackTrace();
   }
  }
 }

}

There are three common situations in which an OOM is caused by a reference to an expired object. These three situations are usually not easy to detect and will not cause problems in a short time, but over time, the number of leaked objects will eventually cause the program to crash.

When classes manage memory on their own, be wary of memory leaks
Here's an example:


import java.util.Arrays;
import java.util.EmptyStackException;

class Stack{
 private Object[] elements;
 private int size;
 private static final int DEFAULT_INITAL_CAPACITY = 16;
 
 public Stack() {
  elements = new Object[DEFAULT_INITAL_CAPACITY];
 }
 
 public void push(Object e){
  ensureCapacity();
  elements[size++] = e;
 }
 
 public Object pop() {
  if (size == 0) {
   throw new EmptyStackException();
  }
  
  return elements[--size];
 }
 
 private void ensureCapacity() {
  if (elements.length == size) {
   elements = Arrays.copyOf(elements, 2 * size + 1);
  }
 }
}

public class StackDemo {
 
 public static void main(String[] args) {
  Stack stack = new Stack();
  
  for (int i = 0; i < 10000; i++) {
   stack.push(new Object());
  }
  
  for(int i = 0; i < 10000; i++) {
   stack.pop();
  }
 }

}

Will be a memory leak, because the stack object even programs other object reference, not in the class but Stack elements [] array still kept the object reference, lead to these objects will not be garbage collected by recycling, so, when you need to kind of memory management himself, be aware whether these expired references are maintained internally lifted references in a timely manner, in this case, just after the stack, will be displayed

elements [size] = null; Can.

Caching is about being aware of memory leaks
When objects are put into the cache, they are likely to be forgotten if they are not used for a long time. WakeHashMap can usually be used to represent the cache, and they can be automatically deleted after the items in the cache have expired. Or it can be executed periodically by one background thread to clear expired items in the buffer.

Listener or callback registration, preferably can be displayed for unregistration.
2. Do not call finalize() manually; it is for the garbage collector

3. Avoid using the finalize() method unless it is used as a termination condition to find parts of the object that have not been properly cleaned; Used as a safety net to clean up system resources in the case of manual cleaning forgot to call, the late cleaning is always better than never clean up, and if the information of forgetting to clean up resources is recorded at the same time, it is also convenient to find errors later, and timely modify the code that forgot to clean up; The local methods in the release object obtain less critical system resources.

The finalize() method is best not used to release critical resources because its execution time and assurance that it will be executed is not guaranteed, but it can be used in all three cases. The first case is as follows:


class Book {
 boolean checkout = false;
 public Book(boolean checkout) {
  this.checkout = checkout;
 }
 
 public void checkin(){
  checkout = false;
 }
 
 @Override
 protected void finalize() throws Throwable {
  if (checkout) {
   System.out.println("Error: check out");
  }
 }
}

public class FinalizeCheckObjectUse {

 public static void main(String[] args) {
  new Book(true);
  System.gc();
 }

}

Execution results:

Error: check out
The Book object in the
example must be in the state of checkIn before it can be released, otherwise it cannot be released. The implementation in finalize can help to find the illegal object in time, or more directly, directly use a reference variable reference in finalize to make it re-enter the state of reachable, and then process it again.

Another point to note is that if a subclass overrides the finalize method of the parent class, but forgets to call super.finalize manually, or if the finalize procedure of the subclass fails to execute super.finalize due to an exception, then the parent class's finalization method will never be called to super.finalize.

As follows:


class Parent{
  @Override
  protected void finalize() throws Throwable {
    System.out.println(getClass().getName() + " finalize start");
  }
}

class Son extends Parent{
  @Override
  protected void finalize() throws Throwable {
    System.out.println(getClass().getName() + " finalize start");
  }
}
public class SuperFinalizeLost {

  public static void main(String[] args) {
    new Son();
    System.gc();
  }

}

Operation results:

Son finalize start
or


class Parent{
  @Override
  protected void finalize() throws Throwable {
    System.out.println(getClass().getName() + " finalize start");
  }
}

class Son extends Parent{
  @Override
  protected void finalize() throws Throwable {
    System.out.println(getClass().getName() + " finalize start");
    int i = 5 / 0;
    super.finalize();
  }
}
public class SuperFinalizeLost {

  public static void main(String[] args) {
    new Son();
    System.gc();
  }

}

Execution results:

Son finalize start
for the second case, you can use try... finally... Structure solution, but for the first case, it is best to use a method called finalization guardian. The sample is as follows


class Parent2{
  private final Object finalizeGuardian = new Object() {
    protected void finalize() throws Throwable {
      System.out.println(" This is where the logic in the parent class finalization method is executed ");
    };
  };
}

class Son2 extends Parent2{
  @Override
  protected void finalize() throws Throwable {
    System.out.println(getClass().getName() + " finalize start");
    int i = 5 / 0;
    super.finalize();
  }
}

public class FinalizeGuardian {

  public static void main(String[] args) {
    new Son2();
    System.gc();
  }

}

Execution results:

This is where the logic in the parent class finalization method is executed
Son2 finalize start
This ensures that the actions needed in the parent class's finalizing method are executed.

The above is the entire content of this article, I hope to help you with your study.


Related articles: