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查看类加载和卸载信息。

相关文章
|
1月前
|
存储 算法 Java
JVM自动内存管理之垃圾收集算法
文章概述了JVM内存管理和垃圾收集的基本概念,提供一个关于JVM内存管理和垃圾收集的基础理解框架。
JVM自动内存管理之垃圾收集算法
|
1月前
|
存储 算法 Java
JVM组成结构详解:类加载、运行时数据区、执行引擎与垃圾收集器的协同工作
【8月更文挑战第25天】Java虚拟机(JVM)是Java平台的核心,它使Java程序能在任何支持JVM的平台上运行。JVM包含复杂的结构,如类加载子系统、运行时数据区、执行引擎、本地库接口和垃圾收集器。例如,当运行含有第三方库的程序时,类加载子系统会加载必要的.class文件;运行时数据区管理程序数据,如对象实例存储在堆中;执行引擎执行字节码;本地库接口允许Java调用本地应用程序;垃圾收集器则负责清理不再使用的对象,防止内存泄漏。这些组件协同工作,确保了Java程序的高效运行。
19 3
|
28天前
|
C# UED 开发者
WPF打印功能实现秘籍:从页面到纸张,带你玩转WPF打印技术大揭秘!
【8月更文挑战第31天】在WPF应用开发中,打印功能至关重要,不仅能提升用户体验,还增强了应用的实用性。本文介绍WPF打印的基础概念与实现方法,涵盖页面元素打印、打印机设置及打印预览。通过具体案例,展示了如何利用`PrintDialog`和`PrintDocument`控件添加打印支持,并使用`PrinterSettings`类进行配置,最后通过`PrintPreviewWindow`实现打印预览功能。
88 0
|
28天前
|
C# UED 开发者
WPF动画大揭秘:掌握动画技巧,让你的界面动起来,告别枯燥与乏味!
【8月更文挑战第31天】在WPF应用开发中,动画能显著提升用户体验,使其更加生动有趣。本文将介绍WPF动画的基础知识和实现方法,包括平移、缩放、旋转等常见类型,并通过示例代码展示如何使用`DoubleAnimation`创建平移动画。此外,还将介绍动画触发器的使用,帮助开发者更好地控制动画效果,提升应用的吸引力。
59 0
|
2月前
|
存储 监控 算法
(六)JVM成神路之GC基础篇:对象存活判定算法、GC算法、STW、GC种类详解
经过前面五个章节的分析后,对于JVM的大部分子系统都已阐述完毕,在本文中则开始对JVM的GC子系统进行全面阐述,GC机制也是JVM的重中之重,调优、监控、面试都逃不开的JVM话题。
|
2月前
|
存储 缓存 算法
(五)JVM成神路之对象内存布局、分配过程、从生至死历程、强弱软虚引用全面剖析
在上篇文章中曾详细谈到了JVM的内存区域,其中也曾提及了:Java程序运行过程中,绝大部分创建的对象都会被分配在堆空间内。而本篇文章则会站在对象实例的角度,阐述一个Java对象从生到死的历程、Java对象在内存中的布局以及对象引用类型。
|
1月前
|
算法 Java 程序员
【JVM的秘密花园】揭秘垃圾收集器的神秘面纱!
【8月更文挑战第25天】在Java虚拟机(JVM)中,垃圾收集(GC)自动管理内存,回收未使用的对象以避免内存泄漏和性能下降。本文深入介绍了JVM中的GC算法,包括串行、并行、CMS及G1等类型及其工作原理。选择合适的GC策略至关重要:小型应用适合串行收集器;大型应用或多核CPU环境推荐并行收集器或CMS;需减少停顿时间时,CMS是好选择;G1适用于大堆且对停顿时间敏感的应用。理解这些能帮助开发者优化程序性能和稳定性。
28 0
|
1月前
|
算法 Java
JVM自动内存管理之垃圾收集器
这篇文章是关于Java虚拟机(JVM)自动内存管理中的垃圾收集器的详细介绍。
|
2月前
|
监控 算法 Java
深入理解Java虚拟机:垃圾收集机制的演变与最佳实践
【7月更文挑战第14天】本文将带领读者穿梭于JVM的心脏——垃圾收集器,探索其设计哲学、实现原理和性能调优。我们将从早期简单的收集算法出发,逐步深入到现代高效的垃圾收集策略,并分享一些实用的调优技巧,帮助开发者在编写和维护Java应用时做出明智的决策。
38 3
|
2月前
|
算法 Java
Java面试题:列举并解释JVM中常见的垃圾收集器,并比较它们的优缺点
Java面试题:列举并解释JVM中常见的垃圾收集器,并比较它们的优缺点
54 3