JVM-04垃圾收集Garbage Collection(上)【垃圾对象的判定】

简介: JVM-04垃圾收集Garbage Collection(上)【垃圾对象的判定】

思维导图


20180729090114401.png

概述

谈起垃圾收集 (Garbage Collection ,GC),有3个问题是无法回避的

1. 哪些内存需要回收

2. 什么时候回收

3. 如何回收

这就引出了我们这边博文需要讨论的话题

1. 如何判断对象为垃圾对象

2. 何时回收垃圾对象(垃圾收集算法)

3. 如何回收垃圾对象(垃圾收集器)


我们前面的博文中讨论了Java的内存自动管理机制,我们知道java内存运行时区域可以分为两大部分: 线程共享区域和线程独占区域 。


20180728094852444.png


线程共享区主要包括Java堆(存储对象实例)和方法区(即我们常说的永久代【JDK7之后逐步去永久代,使用元数据区代替】)


线程独占区主要包括:程序计数器、Java虚拟机栈、本地方法栈。 这3个区域以为是线程独占区,因此生命周期同线程相同,随线程而生而灭。 栈中的帧随着方法的进入和退出有条不紊的执行着出栈和入栈操作。 每一个栈帧中分配多少内存基本上在类结构确定下来的时候就已知的,因此线程独享区的内存分配和回收都具备确定性,这几个区域就不需要过多考虑回收的问题,因为方法结束或者线程结束的时候,内存就跟着回收了。


而线程共享区(Java堆和方法区)则不一样,一个接口中的多个实现类需要的内存可能不一样,一个方法中的多个分支需要的内存也可能不一样,我们只有在程序运行期间才能知道会创建哪些对象,这部分内存的分配和回收都是动态的,垃圾收集器所关注的也是Java堆和方法区。我们这里讨论的内存分配与回收也指的是这一部分.


如何判断对象为垃圾对象


对象的存活还是死亡


Java堆内存中存放着几乎所有的对象实例。


垃圾收集器在对堆内存进行回收之前,需要确定哪些对象是存活或者死去(即不可能再被任何途径使用的对象)


判断对象死亡的算法一:引用计数算法Reference Counting

原理


通过在对象头中分配一个空间来保存该对象被引用的次数。如果该对象被其它对象引用,则它的引用计数加一,如果删除对该对象的引用,那么它的引用计数就减一,当该对象的引用计数为0时,那么该对象就会被回收。


示意图


20180729102428764.png


优点

  • 实现简单
  • 判断效率高

缺点

  • 无法解决对象之间相回循环引用的问题,易引起内存泄露

实验

package com.artisan.gc;
/**
 * 
 * 
 * @ClassName: ReferenceCountingGC
 * 
 * @Description: VM Args
 * 
 * @author: Mr.Yang
 * 
 * @date: 2018年7月29日 上午10:31:32
 */
public class ReferenceCountingGC {
  private Object instance;
  private static final int _1M = 1024 * 1024;
  // 设置个成员变量,在堆中占点内存,以便观察GC是否回收相互引用的情况
  private byte[] bigByte = new byte[2 * _1M];
  public static void main(String[] args) {
    ReferenceCountingGC rc = new ReferenceCountingGC();
    ReferenceCountingGC rc2 = new ReferenceCountingGC();
    // 设置相互引用
    rc.instance = rc2;
    rc2.instance = rc;
    // 将对象置为空
    rc.instance = null;
    rc2.instance = null;
    // 垃圾回收,观察rc 和 rc2能否被回收
    System.gc();
  }
}


虚拟机参数设置: -verbose:gc -XX:+PrintGCDetails


20180729104104575.png


日志信息:


20180729104152335.png


可以看到确实被回收了,这也侧面验证了我们现在使用的hotspot虚拟机不是采用该算法进行垃圾回收。


判断对象死亡的算法一:可达性分析算法Reachability Analysis(hotspot采用该算法)


原理


通过一系列的称为“GCsRoots”的对象作为起始点,从这些节点向下开始搜索,搜索所走过的路径称为引用链(Reference Chain)。 当一个对象到GC Roots没有任何引用链相连(即从GC Roots到这个对象不可达)时,则证明该对象是不可用的。


示意图


2018072911003427.png

可作为GC Roots的对象


1. 虚拟机栈(栈帧中的本地变量表)中引用的对象

2. 方法区中类静态属性引用的对象

3. 方法区中常量引用的对象

4. 本地方法栈中JNI(即native方法)引用的对象


对象引用的分类


JDK1.2之后,Java对引用的概念进行了扩充。

引用强度 强引用 Strong Reference > 软引用 Soft Reference > 弱引用 Weak Reference > 虚引用 Phantom Reference


一个对象的生命周期:


20180729145047284.gif



如果有软引用指向这些对象,则只有在JVM需要内存时才回收这些对象。


如果一个对象只有弱引用指向它,垃圾回收器会立即回收该对象,这是一种急切回收方式。


弱引用和软引用的特殊行为使得它们在某些情况下非常有用。


例如:软引用可以很好的用来实现缓存,当JVM需要内存时,垃圾回收器就会回收这些只有被软引用指向的对象。


而弱引用非常适合存储元数据,例如:存储ClassLoader引用。如果没有类被加载,那么也没有指向ClassLoader的引用。一旦上一次的强引用被去除,只有弱引用的ClassLoader就会被回收


强引用


我们 new 出来的对象 “Object obj = new Object();”或者 String s=”abc”中变量s就是字符串对象”abc”的一个强引用,任何被强引用指向的对象都不能被垃圾回收器回收,这些对象都是在程序中需要的


软引用


如果该对象含有软引用,Counter对象不会立即被回收,除非JVM需要内存。

Java中的软引用使用java.lang.ref.SoftReference类来表示


Counter prime = new Counter(); // prime holds a strong reference 
SoftReference soft = new SoftReference(prime) ; //soft reference variable has SoftReference to Counter Object created at line 2
prime = null; // now Counter object is eligible for garbage collection but only be collected when JVM absolutely needs memory


强引用置空之后,代码的第二行为对象Counter创建了一个软引用,该引用同样不能阻止垃圾回收器回收对象,但是可以延迟回收,与弱引用中急切回收对象不同。


弱引用


只需要给强引用对象counter赋空值null,该对象就可以被垃圾回收器回收。因为该对象此时不再含有其他强引用,即使指向该对象的弱引用weakCounter也无法阻止垃圾回收器对该对象的回收。


弱引用使用java.lang.ref.WeakReference class 类来表示

Counter counter = new Counter(); // strong reference 
WeakReference<Counter> weakCounter = new WeakReference<Counter>(counter); //weak reference
counter = null; // now Counter object is eligible for garbage collection


另一个使用弱引用的例子是WeakHashMap,它是除HashMap和TreeMap之外,Map接口的另一种实现。WeakHashMap有一个特点:map中的键值(keys)都被封装成弱引用,也就是说一旦强引用被删除,WeakHashMap内部的弱引用就无法阻止该对象被垃圾回收器回收。


虚引用


虚引用是java.lang.ref package包中第三种可用的引用,使用java.lang.ref.PhantomReference类来表示。拥有虚引用的对象可以在任何时候被垃圾回收器回收。

通过如下代码创建虚引用:


DigitalCounter digit = new DigitalCounter(); // digit reference variable has strong reference – line 3
PhantomReference phantom = new PhantomReference(digit); // phantom reference to object created at line 3
digit = null;

一旦移除强引用,第三行的DigitalCounter对象可以在任何时候被垃圾回收器回收。因为只有一个虚引用指向该对象,而虚引用无法阻止垃圾回收器回收对象.


finalize 逃逸

原理

20180729152407138.png


在使用可达性分析算法的虚机中,比如我们常用的hotspot, 当对象不可达时,需要至少经历两次标记过程,才能确定是否要回收。


实验

package com.artisan.gc;
public class FinalizeEscapeGC {
  public static FinalizeEscapeGC SAVE_HOOK = null;
  public void isAlive() {
    System.out.println("yes, I am still alive :) -- " + SAVE_HOOK);
  }
  // 重写finalize方法,该方法只被调用一次,但并不是调用后立刻被回收
  @Override
  protected void finalize() throws Throwable {
    super.finalize();
    System.out.println("finalize method executed!");
    FinalizeEscapeGC.SAVE_HOOK = this;
  }
  public static void main(String[] args) throws InterruptedException {
    SAVE_HOOK = new FinalizeEscapeGC();
    /*
     * 拯救成功
     */
    SAVE_HOOK = null;
    // 提醒虚拟机进行垃圾回收,但是虚拟机具体什么时候进行回收就不知道了
    System.gc();
    Thread.sleep(500);
    if (SAVE_HOOK != null) {
      SAVE_HOOK.isAlive();
    } else {
      System.out.println("No, I am dead :(");
    }
    /*
     * 拯救失败
     */
    SAVE_HOOK = null;
    System.gc();
    // finalize方法的优先级比较低所以等待它0.5秒
    Thread.sleep(500);
    if (SAVE_HOOK != null) {
      SAVE_HOOK.isAlive();
    } else {
      System.out.println("No, I am dead :(");
    }
  }
}


输出:

finalize method executed!
yes, I am still alive :) -- com.artisan.gc.FinalizeEscapeGC@5d888759
No, I am dead :(


任何对象的finalize()方法只会被系统自动调用一次。


第一次逃脱成功,原因在于对象重写了finalize()方法,在手动调用System.gc()时触发垃圾回收,在执行finalize()方时, 在其中将 SAVE_HOOK重新用this关键字挂上和当前对象关系,所以在第二次标记时该对象已经不再“待回收”的队列中了,所以此时对象还是存活的;


但是第二次逃亡的时候,不再执行了finalize()方法了(之前执行过一次,对象的finalize()方法必定只执行一次),在SAVE_HOOK至为null后不再可达,finalize()方法也是没有必要执行的情况,所以它就直接为null了,没有指向任何对象,此时对象已死。


注意事项

  • 避免使用finalize(),操作不慎可能导致错误。
  • 优先级低,何时被调用,不确定
  • 何时发生GC不确定,自然也就不知道finalize方法什么时候执行
  • 如果要使用finalize去释放资源,我们可以使用try-catch-finally来替代它


回收方法区

很多人认为方法区(或者Hopspot虚机中的永久代)是没有垃圾收集的,HotSpot虚拟机的设计团队选择把GC分代收集扩展至方法区 ,主要回收

  • 废弃常量
  • 无用的类


废弃常量的回收


常量池中除了包含代码中所定义的各种基本类型(如int、long等等)和对象型(如String及数组)的常量值外,还包含一些以文本形式出现的符号引用,比如:

  • 类和接口的全限定名;
  • 字段的名称和描述符;
  • 方法和名称和描述符。

2018072915440211.png



回收废弃常量和回收Java堆中的对象非常类似。 以常量池中的字面量的回收为例。


假设有一个字符串“abc”已经进入了常量池中,但当前系统中没有任何一个String对象叫做“abc”的,换就话说就是没有任何String对象引用常量池中的“abc”常量,也没有其他地方引用了这个字面量,如果这时发生了内不曾能回收,而且有必要的话,这个“abc”就会被系统清理出常量池。 常量池中的其他类(接口)、方字段的符号引用也与此类似。


无用的类的回收


必须同时满足如如下3个条件才能算是“无用的类”

  • 该类所有的实例都已经被回收,也就是Java堆中不存在该类的任何实例
  • 加载该类的ClassLoader已经被回收
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

即使同时满足了如上3个条件,hotspot虚机也不一定必然回收,hotspot虚机提供了-Xnoclassgc参数进行控制。 还可以使用-verbose:class 以及-XX:+TraceClassLoading 、-XX:+TraceClassUnLoading查看类加载和卸载信息。

相关文章
|
2月前
|
存储 算法 Java
散列表的数据结构以及对象在JVM堆中的存储过程
本文介绍了散列表的基本概念及其在JVM中的应用,详细讲解了散列表的结构、对象存储过程、Hashtable的扩容机制及与HashMap的区别。通过实例和图解,帮助读者理解散列表的工作原理和优化策略。
43 1
散列表的数据结构以及对象在JVM堆中的存储过程
|
12天前
|
缓存 Java
JVM对象引用
本次课程聚焦JVM对象引用,涵盖强引用、软引用、弱引用和虚引用。强引用是最常见的引用类型,确保对象不会被垃圾回收器回收,适用于需要确保对象存活的场景;软引用在内存不足时会被优先回收,常用于缓存;弱引用的对象随时可能被回收,适合临时对象;虚引用最弱,主要用于接收对象回收通知,进行资源清理。通过合理选择引用类型,可优化内存管理,避免内存泄露。
|
3月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
110 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
3月前
|
存储 监控 算法
JVM调优深度剖析:内存模型、垃圾收集、工具与实战
【10月更文挑战第9天】在Java开发领域,Java虚拟机(JVM)的性能调优是构建高性能、高并发系统不可或缺的一部分。作为一名资深架构师,深入理解JVM的内存模型、垃圾收集机制、调优工具及其实现原理,对于提升系统的整体性能和稳定性至关重要。本文将深入探讨这些内容,并提供针对单机几十万并发系统的JVM调优策略和Java代码示例。
68 2
|
3月前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
66 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
3月前
|
存储 算法 Java
【JVM】垃圾释放方式:标记-清除、复制算法、标记-整理、分代回收
【JVM】垃圾释放方式:标记-清除、复制算法、标记-整理、分代回收
74 2
|
3月前
|
算法 Java
JVM进阶调优系列(3)堆内存的对象什么时候被回收?
堆对象的生命周期是咋样的?什么时候被回收,回收前又如何流转?具体又是被如何回收?今天重点讲对象GC,看完这篇就全都明白了。
|
5月前
|
存储 算法 Java
JVM自动内存管理之垃圾收集算法
文章概述了JVM内存管理和垃圾收集的基本概念,提供一个关于JVM内存管理和垃圾收集的基础理解框架。
JVM自动内存管理之垃圾收集算法
|
5月前
|
存储 Java 程序员
Java中对象几种类型的内存分配(JVM对象储存机制)
Java中对象几种类型的内存分配(JVM对象储存机制)
100 5
Java中对象几种类型的内存分配(JVM对象储存机制)
|
5月前
|
存储 算法 Java
JVM组成结构详解:类加载、运行时数据区、执行引擎与垃圾收集器的协同工作
【8月更文挑战第25天】Java虚拟机(JVM)是Java平台的核心,它使Java程序能在任何支持JVM的平台上运行。JVM包含复杂的结构,如类加载子系统、运行时数据区、执行引擎、本地库接口和垃圾收集器。例如,当运行含有第三方库的程序时,类加载子系统会加载必要的.class文件;运行时数据区管理程序数据,如对象实例存储在堆中;执行引擎执行字节码;本地库接口允许Java调用本地应用程序;垃圾收集器则负责清理不再使用的对象,防止内存泄漏。这些组件协同工作,确保了Java程序的高效运行。
37 3