Java虚拟机(4)-GC概述,如何回收对象


1.垃圾回收概述

随着程序的不断运行,程序所产生的对象必将越来越多,而系统的内存则是有限的,所以,将没有用的对象进行清除是程序长期稳定运行的关键.

垃圾回收主要关注三个问题

  • 什么对象应该被回收?
    当然是没有用的对象.当对象不再被引用时,我们认为该对象应该被回收.如何判断对象是否还被引用,会在后面详述.
  • 对象应该在什么时间被回收?
    程序在运行过程中,对象的引用关系是一直变化的,如何选择合适的时机开始GC,也是一个重要的问题,后面详述.
  • 应该怎样回收?
    当我们知道是无用对象后,如何将无用对象清除,保留有用对象,将是垃圾回收算法主要关注的问题,将在下一节详述.

2.判断对象是否被引用

主要有两种办法判断对象是否被引用.

  • 引用计数器法
    顾名思义,为每个对象定义一个计数器,每当对象被引用一次时,程序计数器+1,当引用被销毁一次,计数器-1,如果计数器值为0的时,则对象无用.
    此方法有一个缺点,就是无法解决对象之间的相互引用问题.
    如:objA.instance = objB; objB.instance = objA;
    则这两个对象将永远无法回收.

  • 可达性分析法
    选择一些根节点(GC Roots)对象,向下进行引用查找,查找走过的路径则称为引用链,如果一个对象没有一条引用链可以达到它,则它为无用对象.如图所示.

如GCRoot引用到了A对象。
A对象引用到了B对象。
则A、B对象都不可以回收。


3.选择合适的时间开始GC

  1. 由于程序运行过程中,对象的引用关系一直在发生变化,所以我们需要等到所有线程停止执行(stop the world)才能开始进行.

  1. JVM准备GC之前,会将一个标志位设置为真,每个线程都会去检查这个标志位,如果发现为真,则停止往下执行,并将当前线程栈中引用(局部变量)的对象作为GC Roots,向下查找引用链,并存入一个oopMap中.

注:哪些对象可以作为GC Roots?

  • 静态变量
  • 常量
  • 线程栈中引用(局部变量)引用的对象

  1. 如果线程每执行一条指令就去检查标志位,显然太过浪费,此时就让线程多走几步到某些位置上,再开始进行可达性分析,这些位置称为安全点.

注:一般将哪些位置作为安全点?

  • 方法return之后
  • 循环单次结束后
  • 发生异常准备跳转catch前.

  1. 当线程正在sleep()时,引用关系是不会发生变化的,这段时间称为安全区域.线程进入安全区域和走到安全点效果是一样的.当线程sleep()结束后,会先通过标志位判断GC是否结束,没有结束则会继续等待.

4.其他

  • 对象不可达时,如何避免被回收?
    实现finalize()后,有且仅有一次执行finalize()以逃脱回收的机会,但一般不建议使用.
public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;

    public void isAlive() {
        System.out.println("对象仍然活着!!!");
    }

    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize()方法被执行!!!");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws Throwable {
        new FinalizeEscapeGC(); // 对象被创建
        SAVE_HOOK = null;
        System.gc(); // 回收,会执行finalize()
        Thread.sleep(500);

        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("对象已死!!!");
        }

        SAVE_HOOK = null;
        System.gc(); // 回收,finalize()已经被执行过,不会再被执行
        Thread.sleep(500);

        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("对象已死!!!");
        }
    }
}
finalize()方法被执行!!!
对象仍然活着!!!
对象已死!!!

  • 关于引用
    如果reference类型的数据中存储的是另一块内存的地址,则次reference对象为一个引用.
    按照强弱程度,引用可以分为强引用,软引用,弱引用,虚引用.

1.强引用

Object obj = new Object();  

除非栈帧清空,强引用不会被GC,即使发生OOM.

2.软引用

Object obj = new Object();
SoftReference<Object> sf = new SoftReference<Object>(obj);
obj = null;
sf.get();//有时候会返回null

软引用在发生内存不足时会被回收,内存足够时何以通过get()获取.
软引用通常被用于实现缓存.

3.弱引用

Object obj = new Object();
WeakReference<Object> wf = new WeakReference<Object>(obj);
obj = null;
wf.get();//有时候会返回null
wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收

弱引用是在第二次垃圾回收时回收,即使内存足够.短时间内通过弱引用取对应的数据,可以取到,当执行过第二次垃圾回收时,将返回null

4.虚引用

Object obj = new Object();
PhantomReference<Object> pf = new PhantomReference<Object>(obj);
obj=null;
pf.get();//永远返回null
pf.isEnQueued();//返回是否从内存中已经删除

在垃圾回收时,虚引用一定会被回收,就和没有引用一样.


  • 方法区的垃圾回收
    垃圾回收的主要区域是堆,但方法区一样也会发生垃圾回收.一次对年轻代的回收可以回收70%-95%的空间,但永久代的回收效率要低很多.
    永久代的垃圾回收主要针对废弃常量和废弃类.

废弃常量

如"abc"如果没有任何引用,则会被回收.

废弃类
对废弃类的回收需要满足一下三个条件:
1.该类的所有实例已被回收
2.该类的ClassLoader已被回收
3.该类对应的java.lang.Class对象无任何地方被引用

注:可以通过参数-Xnoclassgc,设置是否对类进行回收.

在大量使用反射,动态代理,CGLib等ByteCode框架, 动态生成JSP以及OSGi这类频繁
自定义ClassLoader的场景都需要虚拟机具备类卸载的功能,以保证永久代不会溢出。



Java虚拟机(4)-GC概述,如何回收对象

原文地址:https://www.cnblogs.com/guan-li/p/11505597.html

时间: 2024-10-08 17:43:07

Java虚拟机(4)-GC概述,如何回收对象的相关文章

Java虚拟机内存模型及垃圾回收监控调优

Java虚拟机内存模型及垃圾回收监控调优 如果你想理解Java垃圾回收如果工作,那么理解JVM的内存模型就显的非常重要.今天我们就来看看JVM内存的各不同部分及如果监控和实现垃圾回收调优. JVM内存模型         正如你上图所看到的,JVM内存可以划分为不同的部分,广义上,JVM堆内存可以划分为两部分:年轻代和老年代(Young Generation and Old Generation) 年轻代(Young Generation) 年轻代用于存放由new所生成的对象.当年轻代空间满时,

探秘Java虚拟机——内存管理与垃圾回收

详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt106 1.Java虚拟机运行时的数据区 2.常用的内存区域调节参数 -Xms:初始堆大小,默认为物理内存的1/64(<1GB):默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制 -Xmx:最大堆大小,默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小

Java虚拟机:GC算法深度解析

版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! 在前面的文章里介绍了可达性分析算法,它为我们解决了判定哪些对象可以回收的问题,接下来就该我们的垃圾收集算法出场了.不同的垃圾收集算法有各自不同的优缺点,在JVM实现中,往往不是采用单一的一种算法进行回收,而是采用几种不同的算法组合使用,来达到最好的收集效果.接下来详细介绍几种垃圾收集算法的思想及发展过程.  最基础的收集算法 -- 标记/清除算法        之所以说标记/清除算法是几种GC算法中最基础的算法,是因为后续的收集算法都

探秘Java虚拟机——内存管理与垃圾回收(转)

本文主要是基于Sun JDK 1.6 Garbage Collector(作者:毕玄)的整理与总结,原文请读者在网上搜索. 1.Java虚拟机运行时的数据区 2.常用的内存区域调节参数 -Xms:初始堆大小,默认为物理内存的1/64(<1GB):默认(MinHeapFreeRatio参数可以调整)空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制 -Xmx:最大堆大小,默认(MaxHeapFreeRatio参数可以调整)空余堆内存大于70%时,JVM会减少堆直到 -Xms的最小限制

java虚拟机之GC(转)

垃圾回收主要内容: 1. 那些内存需要回收? 2. 什么时候回收? 3. 如何回收? 垃圾回收主要针对运行时数据区那些区域? 运行时数据区的线程私有区域有:虚拟机栈,本地方法栈,程序计数器等: 栈中的栈帧随着方法的进入和退出执行入栈和出栈,每个栈帧的内存分配在编译期就已经确定: 随着线程或方法的结束,内存也随着回收: 运行时数据区的线程共享区域有:方法区,堆: 方法区和堆只有程序处于运行期才能确定创建那些对象,因此这部分内存分配和回收都是动态的: 垃圾回收的重点区域: 一,对象存活判断 1,引用

深入理解java虚拟机笔记(二)-垃圾回收

ps:文中的图片都来自网络.部分图片来源 1. 前言 作为一种高级语言,比起c和c++来,很进步的一点就是垃圾回收机制.这省去来了我们很多的工作,不过,我们仍然需要了解垃圾回收,这对我们的成长很有帮助. 2. 引用计数法 引用计数法在很多高级语言都有,如python,java也不例外.对象内部维护有一个被其他对象引用的引用计数,当这个引用计数为0的时候,表示对象可以被回收. 引用计数法存在一个问题,就是循环引用,加入a引用b,b同时也引用a,那么就存在ab的引用计数都不为0的情况. 3. 可达性

深入理解Java虚拟机笔记---内存分配与回收策略

Java技术体系中的自动内存管理最终可以归结为自动化地解决了两个问题:给对象分配内存以及回收分配给对象的内存.对象的内存分配往大的方向上讲,就是在堆上分配,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲(-XX:+UseTLAB,默认已开启),将按线程优先在TLAB上分配.少数情况下也可能会直接分配在老年代中,分配的规则并不是百分之百固定的,其细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数设置. 下面是几条主要的最普遍的内存分配规则: 1.对象优先在Ed

java虚拟机(十一)--GC日志分析

打印日志相关参数: -XX:+PrintGCDetails -XX:PrintGCTimestamps -XX:PrintGCDateStamps -Xloggc:$CATALINA_HOME/logs/gc.log -XX:+PrintHeapAtGC -XX:+PrintTenuringDistriution:发生GC的时候,把Young区中对象的年龄打印出来 其余的参数应该能直接看懂的,就不解释了. Parallel日志: 默认情况下,都是使用Parallel收集器 JAVA_OPTS="

java虚拟机优化--gc日志

加入 wrapper.java.additional.5=-XX:+PrintGC wrapper.java.additional.6=-XX:+PrintGCDetails #wrapper.java.additional.7=-XX:+PrintGC:PrintGCTimeStamps wrapper.java.additional.7=-Xloggc:gc.log wrapper.java.additional.8=-XX:+PrintGCApplicationStoppedTime wr