理解Java垃圾回收

 更新时间:2016年03月27日 10:46:02   作者:赵杰A-124  
这篇文章主要帮助大家理解Java垃圾回收,通过实例学习java垃圾回收,什么是垃圾回收,感兴趣的小伙伴们可以参考一下

当程序创建对象、数组等引用类型的实体时,系统会在堆内存中为这一对象分配一块内存,对象就保存在这块内存中,当这块内存不再被任何引用变量引用时,这块内存就变成垃圾,等待垃圾回收机制进行回收。垃圾回收机制具有三个特征:

垃圾回收机制只负责回收堆内存中的对象,不会回收任何物理资源(例如数据库连接,打开的文件资源等),也不会回收以某种创建对象的方式以外的方式为该对像分配的内存,(例如对象调用本地方法中malloc的方式申请的内存)
程序无法精确控制垃圾回收的运行,只可以建议垃圾回收进行,建议的方式有两种System.gc() 和Runtime.getRuntime().gc()
在垃圾回收任何对象之前,总会先调用它的finalize()方法,但是同垃圾回收的时机一致,调用finalize()方法的时机也不确定。
针对以上三个特征,有三个问题:

1、必须手动的进行清理工作,释放除创建对象的方式以外的方式分配的内存和其它的物理资源。并且要注意消除过期的对象引用,否则可能引起OOM。

手动清理通常用到try...finally...这样的代码结构。

示例如下:

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

}

对于过期对象的引用,引起的OOM通常有三种常见的情况,这三种情况通常都不易发现,短时间内运行也不会有什么问题,但是时间久了后,泄漏的对象增加后终会引起程序崩溃。

类自己管理内存时,要警惕内存泄漏
示例如下:

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

}

之所以会内存泄漏,是因为那些出栈的对象即使程序其它对象不再引用,但是Stack类中的elements[]数组依然保存着这些对象的引用,导致这些对象不会被垃圾回收所回收,所以,当需要类自己管理内存事,要警惕内部维护的这些过期引用是否被及时解除了引用,本例中只需在出栈后,显示的将

elements[size] = null;即可。

缓存是要警惕内存泄漏
出现这样情况通常是一旦将对象放入缓存,很可能长时间不使用很容易遗忘,通常可以用WakeHashMap代表缓存,在缓存中的项过期后,他们可以被自动删除。或者可以由一个后台线程定期执行来清除缓冲中的过期项。

监听器或回调的注册,最好可以显示的取消注册。
2、不要手动调用finalize(),它是给垃圾回收器调用的

3、避免使用finalize()方法,除非用来作为判断终结条件以发现对象中没有被适当清理的部分;用来作为安全网在手动清理忘记调用的情况下清理系统资源,延后清理总别永不清理要强,并且如果同时记录下忘记清理资源的信息的话,也方便后面发现错误,并及时修改忘记清理的代码;释放对象中本地方法获得的不是很关键的系统资源。

finalize()方法由于其执行时间以及是否确定被执行都不能准确确保,所以最好不用来释放关键资源,但是可用于上面所说的三种情况。其中第一种情况,示例如下:

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

}

执行结果:

Error: check out
例子中的Book对象,在释放前必须处于checkIn的状态,否则不能释放,finalize中的实现可以帮助及时发现不合法的对象,或者更直接的,在finalize中直接使用某个引用变量引用,使其重新进入reachable的状态,然后再次对其进行处理。

另一点需要注意的时,子类如果覆盖了父类的finalize方法,但是忘了手工调用super.finalize或者子类的finalize过程出现异常导致没有执行到super.finalize时,那么父类的终结方法将永远不会调到。

如下:

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

}

运行结果:

Son finalize start
或者

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

}

执行结果:

Son finalize start
对于第二种情况,可以使用try...finally...结构解决,但是对于第一种情况,最好使用一种叫终结方法守护者的方式。示例如下

class Parent2{
  private final Object finalizeGuardian = new Object() {
    protected void finalize() throws Throwable {
      System.out.println("在此执行父类终结方法中的逻辑");
    };
  };
}

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

}

执行结果:

在此执行父类终结方法中的逻辑
Son2 finalize start
这样可以保证父类的终结方法中所需做的操作执行到。

以上就是本文的全部内容,希望对大家的学习有所帮助。

相关文章

  • Java实现富文本转markdown

    Java实现富文本转markdown

    这篇文章主要为大家详细介绍了如何通过Java实现富文本转markdown功能,文中的示例代码讲解详细,具有一定的借鉴价值,有需要的小伙伴可以参考下
    2023-12-12
  • Java中的ThreadLocal与ThreadLocalMap详解

    Java中的ThreadLocal与ThreadLocalMap详解

    这篇文章主要介绍了Java中的ThreadLocal与ThreadLocalMap详解,ThreadLocal 是一个线程局部变量,其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,需要的朋友可以参考下
    2023-09-09
  • Spring中配置ContextLoaderListener方式

    Spring中配置ContextLoaderListener方式

    这篇文章主要介绍了Spring中配置ContextLoaderListener方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2025-04-04
  • Java锁机制Lock用法示例

    Java锁机制Lock用法示例

    这篇文章主要介绍了Java锁机制Lock用法,结合具体实例形式分析了Java锁机制的相关上锁、释放锁、隐式锁、显式锁等概念与使用技巧,需要的朋友可以参考下
    2018-08-08
  • Java中的静态代码块与构造代码块详解

    Java中的静态代码块与构造代码块详解

    静态代码块在JVM加载类时执行,仅执行一次,用于初始化静态变量或执行一次性设置,构造代码块在每次创建对象时执行,用于实例变量的初始化或执行创建对象时的公共逻辑,静态代码块按定义顺序执行,构造代码块在构造方法前执行
    2024-11-11
  • elasticsearch中term与match的区别讲解

    elasticsearch中term与match的区别讲解

    今天小编就为大家分享一篇关于elasticsearch中term与match的区别讲解,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-02-02
  • SpringBoot通过请求对象获取输入流无数据

    SpringBoot通过请求对象获取输入流无数据

    这篇文章主要介绍了使用SpringBoot通过请求对象获取输入流无数据,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • java文件操作报错:java.io.FileNotFoundException(拒绝访问)问题

    java文件操作报错:java.io.FileNotFoundException(拒绝访问)问题

    在进行编程时,经常会遇到因疏忽小细节而导致的错误,如忘记在路径后添加文件名,本文通过一个具体的修改前后对比示例,解释了错误原因,并给出了解决方案,这类经验分享对编程学习者具有参考价值
    2024-10-10
  • 浅谈java对象之间相互转化的多种方式

    浅谈java对象之间相互转化的多种方式

    这篇文章主要介绍了浅谈java对象之间相互转化的多种方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-08-08
  • java实现ReadWriteLock读写锁的示例

    java实现ReadWriteLock读写锁的示例

    ReadWriteLock是Java并发包中的接口,定义了读锁和写锁,读锁允许多线程同时访问共享资源,而写锁则要求独占,这种机制适用于读多写少的场景,可以提高并发效率同时保证数据一致性,本文就来详细的介绍一下如何实现,感兴趣的可以了解一下
    2024-09-09

最新评论