Java垃圾回收机制的示例详解

目录
  • 一、概述
  • 二、对象已死?
    • 1.引用计数算法
    • 2.可达性分析算法
    • 3.四种引用
    • 4.生存还是死亡?
    • 5.回收方法区
  • 三、垃圾收集算法
    • 1.分代收集理论
    • 2.名词解释
    • 3.标记-清除算法
    • 4.标记-复制算法
    • 5.标记-整理算法

一、概述

说起垃圾收集(Garbage Collection,下文简称GC),有不少人把这项技术当作Java语言的伴生产 物。事实上,垃圾收集的历史远远比Java久远,在1960年诞生于麻省理工学院的Lisp是第一门开始使 用内存动态分配和垃圾收集技术的语言。当Lisp还在胚胎时期时,其作者John McCarthy就思考过垃圾 收集需要完成的三件事情:

  • 哪些内存需要回收?
  • 什么时候回收?
  • 如何回收?

经过半个世纪的发展,今天的内存动态分配与内存回收技术已经相当成熟,一切看起来都进入 了“自动化”时代,那为什么我们还要去了解垃圾收集和内存分配?

答案很简单:当需要排查各种内存 溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就必须对这些“自动 化”的技术实施必要的监控和调节。

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

而Java堆和方法区这两个区域则有着很显著的不确定性:一个接口的多个实现类需要的内存可能 会不一样,一个方法所执行的不同条件分支所需要的内存也可能不一样,只有处于运行期间,我们才 能知道程序究竟会创建哪些对象,创建多少个对象,这部分内存的分配和回收是动态的。垃圾收集器 所关注的正是这部分内存该如何管理,本文后续讨论中的“内存”分配与回收也仅仅特指这一部分内存。

二、对象已死?

在堆里面存放着Java世界中几乎所有的对象实例,垃圾收集器在对堆进行回收前,第一件事情就 是要确定这些对象之中哪些还“存活”着,哪些已经“死去”(“死去”即不可能再被任何途径使用的对 象)了。

这个面试的时候也经常会问到,依靠两个算法来判断对象是否存活:

1、引用计数算法

2、可达分析算法。

1.引用计数算法

判断对象是否存活的算法是这样的:在对象中添加一个引用计数器,每当有一个地方 引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可 能再被使用的。

客观地说,引用计数算法(Reference Counting)虽然占用了一些额外的内存空间来进行计数,但 它的原理简单,判定效率也很高,很多领域都在用,但是,在Java 领域,至少主流的Java虚拟机里面都没有选用引用计数算法来管理内存。原因:单纯的引用计数 就很难解决对象之间相互循环引用的问题。

代码示例:

在下面testGC()方法:对象objA和objB都有字段instance,赋值令 objA.instance=objB及objB.instance=objA,除此之外,这两个对象再无任何引用,实际上这两个对象已 经不可能再被访问,但是它们因为互相引用着对方,导致它们的引用计数都不为零,引用计数算法也 就无法回收它们。

对于objA = null,objB = null应该有的人会不理解为什么要这么做,咱们要测试的是属性出现了互相引用,是否会被gc掉。那么我就得保证其他地方不再引用他,我们用的是main方法测试,所以需要设置为null。正常在java应用是不需要的,线程执行过后,局部变量将被销毁。那也就不存在引用这一说了。

虽然手动的让局部变量不在引用堆中的对象,但是在堆内存当中这两个对象的属性还是相互引用的。如果按照计数算法,他是不应该被gc掉的。

public class ReferenceCountingGC {
    public Object instance = null;
    private static final int _1MB = 1024 * 1024;
    /*** 这个成员属性的唯一意义就是占点内存,以便能在GC日志中看清楚是否有回收过 */
    private byte[] bigSize = new byte[2 * _1MB];

    public static void testGC() {
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
        // 假设在这行发生GC,objA和objB是否能被回收?
        System.gc();
    }

    public static void main(String[] args) {
        testGC();
    }
}

设置启动参数:输出GC的详细日志

-XX:+PrintGCDetails

运行结果:

从运行结果中可以清楚看到内存回收日志中包含“9257K->823K”,意味着虚拟机并没有因为这两 个对象互相引用就放弃回收它们,这也从侧面说明了Java虚拟机并不是通过引用计数算法来判断对象 是否存活的。

2.可达性分析算法

当前主流的商用程序语言(Java、C#,上溯至前面提到的古老的Lisp)的内存管理子系统,都是 通过可达性分析(Reachability Analysis)算法来判定对象是否存活的。这个算法的基本思路就是通过 一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过 程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots间没有任何引用链相连, 或者用图论的话来说就是从GC Roots到这个对象不可达时,则证明此对象是不可能再被使用的。

如图所示,对象object 5、object 6、object 7虽然互有关联,但是它们到GC Roots是不可达的, 因此它们将会被判定为可回收的对象。

在Java技术体系里面,固定可作为GC Roots的对象包括以下几种:

  1. 在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的 参数、局部变量、临时变量等。
  2. 在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
  3. 在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
  4. 在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
  5. Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
  6. 所有被同步锁(synchronized关键字)持有的对象。
  7. 反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
  8. 除了这些固定的GC Roots集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不 同,还可以有其他对象“临时性”地加入,共同构成完整GC Roots集合。譬如后文将会提到的分代收集 和局部回收(Partial GC),如果只针对Java堆中某一块区域发起垃圾收集时(如最典型的只针对新生 代的垃圾收集),必须考虑到内存区域是虚拟机自己的实现细节(在用户视角里任何内存区域都是不 可见的),更不是孤立封闭的,所以某个区域里的对象完全有可能被位于堆中其他区域的对象所引 用,这时候就需要将这些关联区域的对象也一并加入GC Roots集合中去,才能保证可达性分析的正确 性。

目前最新的几款垃圾收集器无一例外都具备了局部回收的特征。如OpenJDK中的G1、Shenandoah、ZGC以及Azul的PGC、C4这些收集器。

3.四种引用

无论是通过引用计数算法判断对象的引用数量,还是通过可达性分析算法判断对象是否引用链可 达,判定对象是否存活都和“引用”离不开关系。在JDK 1.2版之前,Java里面的引用是很传统的定义: 如果reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称该reference数据是代表 某块内存、某个对象的引用。一个对象在 这种定义下只有“被引用”或者“未被引用”两种状态,对于描述一些“食之无味,弃之可惜”的对象就显 得无能为力。譬如我们希望能描述一类对象:当内存空间还足够时,能保留在内存之中,如果内存空 间在进行垃圾收集后仍然非常紧张,那就可以抛弃这些对象——很多系统的缓存功能都符合这样的应 用场景。

在JDK 1.2版之后,Java对引用的概念进行了扩充,将引用分为强引用(Strongly Re-ference)、软 引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)4种,这4种引用强 度依次逐渐减弱。

  • 强引用是最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“Object obj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回 收掉被引用的对象。
  • 软引用是用来描述一些还有用,但非必须的对象。只被软引用关联着的对象,在系统将要发生内 存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存, 才会抛出内存溢出异常。在JDK 1.2版之后提供了SoftReference类来实现软引用。
  • 弱引用也是用来描述那些非必须对象,但是它的强度比软引用更弱一些,被弱引用关联的对象只 能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只 被弱引用关联的对象。在JDK 1.2版之后提供了WeakReference类来实现弱引用。
  • 虚引用也称为“幽灵引用”或者“幻影引用”,它是最弱的一种引用关系。一个对象是否有虚引用的 存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚 引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2版之后提供 了PhantomReference类来实现虚引用。

4.生存还是死亡?

即使在可达性分析算法中判定为不可达的对象,也不是“非死不可”的,这时候它们暂时还处于“缓 刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行可达性分析后发现没 有与GC Roots相连接的引用链,那它将会被第一次标记,随后进行一次筛选,筛选的条件是此对象是 否有必要执行finalize()方法。假如对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用 过,那么虚拟机将这两种情况都视为“没有必要执行”。

finalize()方法是Object的一个方法,所有的对象都是Object的子类。只不过语法上默认没有继承Object。但是实际上是继承的Object类。

如果这个对象被判定为确有必要执行finalize()方法,那么该对象将会被放置在一个名为F-Queue的 队列之中,并在稍后由一条由虚拟机自动建立的、低调度优先级的Finalizer线程去执行它们的finalize() 方法。这里所说的“执行”是指虚拟机会触发这个方法开始运行,但并不承诺一定会等待它运行结束。

这样做的原因是,如果某个对象的finalize()方法执行缓慢,或者更极端地发生了死循环,将很可能导 致F-Queue队列中的其他对象永久处于等待。

如果对 象要在finalize()中成功拯救自己 只要重新与引用链上的任何一个对象建立关联即可,譬如把自己 (this关键字)赋值给某个类变量或者对象的成员变量,那在第二次标记时它将被移出“即将回收”的集 合;如果对象这时候还没有逃脱,那基本上它就真的要被回收了。从下面代码我们可以看到一个 对象的finalize()被执行,但是它仍然可以存活。

一次对象自我拯救的演示:

此代码演示了两点:

1.对象可以在被GC时自我拯救。

2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次

public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;

    public void isAlive() {
        System.out.println("yes, i am still alive :)");
    }

    @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 Throwable {
        SAVE_HOOK = new FinalizeEscapeGC();
        //对象第一次成功拯救自己
        SAVE_HOOK = null;
        System.gc();
        // 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead :(");
        }
        // 下面这段代码与上面的完全相同,但是这次自救却失败了
        SAVE_HOOK = null;
        System.gc();
        // 因为Finalizer方法优先级很低,暂停0.5秒,以等待它
        Thread.sleep(500);
        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead :(");
        }
    }
}

运行结果:

finalize()方法大家尽量避免使用它,它的运行代价高昂,不确定性大,无法保证各个对象的调用顺序,如今已被官方明确声明为 不推荐使用的语法。finalize()能做的所有工作,使用try-finally或者其他方式都可以做得更好、 更及时。

5.回收方法区

方法区的垃圾收集主要回收两部分内容:废弃的常量和不再使用的类型。回收废弃常量与回收 Java堆中的对象非常类似。举个常量池中字面量回收的例子,假如一个字符串“java”曾经进入常量池 中,但是当前系统又没有任何一个字符串对象的值是“java”,换句话说,已经没有任何字符串对象引用 常量池中的“java”常量,且虚拟机中也没有其他地方引用这个字面量。如果在这时发生内存回收,这个“java”常量就将会被系统清理出常量池。常量池中其他类(接 口)、方法、字段的符号引用也与此类似。

判定一个常量是否“废弃”还是相对简单,而要判定一个类型是否属于“不再被使用的类”的条件就 比较苛刻了。需要同时满足下面三个条件:

  • 该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例。
  • 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如 OSGi、JSP的重加载等,否则通常是很难达成的。
  • 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方 法。
  • Java虚拟机被允许对满足上述三个条件的无用类进行回收,这里说的仅仅是“被允许”,而并不是 和对象一样,没有引用了就必然会回收。关于是否要对类型进行回收,HotSpot虚拟机提供了命令参数进行控制,

-Xnoclassgc :关闭虚拟机对class的垃圾回收功能。
-verbose:class XXX :(XXX为程序名)你会在控制台看到加载的类的情况。
-XX:+TraceClassLoading :监控类的加载
-XX:+TraceClassUnLoading : 监控类的卸载

其中-verbose:class和-XX:+TraceClassLoading可以在 Product版的虚拟机中使用,-XX:+TraceClassUnLoading参数需要FastDebug版的虚拟机支持。

在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGi这类频繁自定义类加载 器的场景中,通常都需要Java虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压 力。

三、垃圾收集算法

Java默认的虚拟机HotSpot VM,采用的追踪式垃圾收集,也就是刚刚所说的可达分析,所以本节介绍的所有算法均属于追踪式垃圾收集的范畴。

1.分代收集理论

当前商业虚拟机的垃圾收集器,大多数都遵循了“分代收集”的理论进 行设计,分代收集名为理论,实质是一套符合大多数程序运行实际情况的经验法则,它建立在两个分代假说之上:

1)弱分代假说(Weak Generational Hypothesis):绝大多数对象都是朝生夕灭的。

2)强分代假说(Strong Generational Hypothesis):熬过越多次垃圾收集过程的对象就越难以消 亡

这两个分代假说共同奠定了多款常用的垃圾收集器的一致的设计原则:收集器应该将Java堆划分 出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区 域之中存储。

在Java堆划分出不同的区域之后,垃圾收集器才可以每次只回收其中某一个或者某些部分的区域 ——因而才有了“Minor GC”“Major GC”“Full GC”这样的回收类型的划分;发展出了“标记-复制算法”“标记-清除算 法”“标记-整理算法”等针对性的垃圾收集算法,这一切的出现都始于分代收集理论。

把分代收集理论具体放到现在的商用Java虚拟机里,设计者一般至少会把Java堆划分为新生代 (Young Generation)和老年代(Old Generation)两个区域。顾名思义,在新生代中,每次垃圾收集 时都发现有大批对象死去,而每次回收后存活的少量对象,将会逐步晋升到老年代中存放。分代收集并非只是简单划分一下内存区域那么容易,它至少存在一个明显的困难:对象不 是孤立的,对象之间会存在跨代引用。

假如要现在进行一次只局限于新生代区域内的收集(Minor GC),但新生代中的对象是完全有可 能被老年代所引用的,为了找出该区域中的存活对象,不得不在固定的GC Roots之外,再额外遍历整 个老年代中所有对象来确保可达性分析结果的正确性,反过来也是一样。遍历整个老年代所有对象 的方案虽然理论上可行,但无疑会为内存回收带来很大的性能负担。为了解决这个问题,就需要对分 代收集理论添加第三条经验法则:

3)跨代引用假说(Intergenerational Reference Hypothesis):跨代引用相对于同代引用来说仅占极 少数。

依据这条假说,我们就不应再为了少量的跨代引用去扫描整个老年代,也不必浪费空间专门记录 每一个对象是否存在及存在哪些跨代引用,只需在新生代上建立一个全局的数据结构(该结构被称 为“记忆集”,Remembered Set),这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会 存在跨代引用。此后当发生Minor GC时,只有包含了跨代引用的小块内存里的对象才会被加入到GC Roots进行扫描。虽然这种方法需要在对象改变引用关系(如将自己或者某个属性赋值)时维护记录数 据的正确性,会增加一些运行时的开销,但比起收集时扫描整个老年代来说仍然是划算的。

2.名词解释

部分收集(Partial GC):指目标不是完整收集整个Java堆的垃圾收集,其中又分为:

  • 新生代收集(Minor GC/Young GC):指目标只是新生代的垃圾收集。
  • 老年代收集(Major GC/Old GC):指目标只是老年代的垃圾收集。目前只有CMS收集器会有单 独收集老年代的行为。另外请注意“Major GC”这个说法现在有点混淆,在不同资料上常有不同所指,有的是指老年代的收集、有的是指整堆收集。
  • 混合收集(Mixed GC):指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1收 集器会有这种行为。
  • 整堆收集(Full GC):收集整个Java堆和方法区的垃圾收集。

通常能单独发生收集行为的只是新生代,所以这里“反过来”的情况只是理论上允许,实际上除了 CMS收集器,其他都不存在只针对老年代的收集。

3.标记-清除算法

算法分为“标记”和“清除”两个阶段:首先标记出需要回 收的对象,在标记完成后,统一回收掉所有被标记的对象,也可以反过来,标记存活的对象,统一回 收所有未被标记的对象。标记过程就是对象是否属于垃圾的判定过程,怎么判断是否垃圾,就是刚刚所提到的,引用计数算法和可达分析算法。

它的主要缺点有两个:第一个是执行效率不稳定,如果Java堆中包含大量对 象,而且其中大部分是需要被回收的,这时必须进行大量标记和清除的动作,导致标记和清除两个过 程的执行效率都随对象数量增长而降低;第二个是内存空间的碎片化问题,标记、清除之后会产生大 量不连续的内存碎片,空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找 到足够的连续内存而不得不提前触发另一次垃圾收集动作。标记-清除算法的执行过程如图所示。

4.标记-复制算法

标记-复制算法常被简称为复制算法。为了 解决标记-清除算法面对大量可回收对象时执行效率低 的问题,1969年Fenichel提出了一种称为“半区复制”(Semispace Copying)的垃圾收集算法,它将可用 内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着 的对象复制到另外一块上面,复制的时候不用考虑有 空间碎片的复杂情况,只要移动堆顶指针,按顺序分配即可,然后再把已使用过的内存空间一次清理掉。

缺点:

如果内存中多数对象都是存 活的,这种算法将会产生大量的内存间复制的开销。

回收算法的代价是将可用内存缩小为了原来的一半,空间浪费未免太多了一 点。

在1989年,Andrew Appel针对具备“朝生夕灭”特点的对象,提出了一种更优化的半区复制分代策 略,现在称为“Appel式回收”。HotSpot虚拟机的Serial、ParNew等新生代收集器均采用了这种策略来设 计新生代的内存布局。

Appel式回收的具体做法是把新生代分为一块较大的Eden空间和两块较小的 Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾搜集时,将Eden和Survivor中仍 然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1,也即每次新生代中可用内存空间为整个新 生代容量的90%(Eden的80%加上一个Survivor的10%),只有一个Survivor空间,即10%的新生代是会 被“浪费”的。当然,98%的对象可被回收仅仅是“普通场景”下测得的数据,任何人都没有办法百分百 保证每次回收都只有不多于10%的对象存活,因此Appel式回收还有一个充当罕见情况的“逃生门”的安 全设计,当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(实 际上大多就是老年代)进行分配担保。

内存的分配担保好比我们去银行借款,如果我们信誉很好,在98%的情况下都能按时偿还,于是 银行可能会默认我们下一次也能按时按量地偿还贷款,只需要有一个担保人能保证如果我不能还款 时,可以从他的账户扣钱,那银行就认为没有什么风险了。内存的分配担保也一样,如果另外一块 Survivor空间没有足够空间存放上一次新生代收集下来的存活对象,这些对象便将通过分配担保机制直 接进入老年代,这对虚拟机来说就是安全的。

5.标记-整理算法

针对老年代对象的存亡特征,1974年Edward Lueders提出了另外一种有针对性的“标记-整 理”(Mark-Compact)算法,其中的标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可 回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内 存,“标记-整理”算法的示意图如图所示。

标记-清除算法与标记-整理算法的本质差异在于前者是一种非移动式的回收算法,而后者是移动 式的。是否移动回收后的存活对象是一项优缺点并存的风险决策:

移动则内存回收时会更复杂,不移动则内存分配时会 更复杂。移动虽然复杂点,但是不影响服务器的内存吞吐量。HotSpot虚拟机里面关注吞吐量的Parallel Scavenge收集器是基于标记-整理算法的。

还有一种“和稀泥式”解决方案可以不在内存分配和访问上增加太大额外负担,做法是让虚 拟机平时多数时间都采用标记-清除算法,暂时容忍内存碎片的存在,直到内存空间的碎片化程度已经 大到影响对象分配时,再采用标记-整理算法收集一次,以获得规整的内存空间。前面提到的基于标 记-清除算法的CMS收集器面临空间碎片过多时采用的就是这种处理办法(CMS是老年代垃圾收集器)。

以上就是Java垃圾回收机制的示例详解的详细内容,更多关于Java垃圾回收机制的资料请关注我们其它相关文章!

(0)

相关推荐

  • 快速了解JAVA垃圾回收机制

    说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联系起来.在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,这一切都交给了JVM来处理.顾名思义,垃圾回收就是释放垃圾占用的空间,那么在Java中,什么样的对象会被认定为"垃圾"?那么当一些对象被确定为垃圾之后,采用什么样的策略来进行回收(释放空间)?在目前的商业虚拟机中,有哪些典型的垃圾收集器?下面我们就来逐一探讨这些问题.以下是本文的目录大纲: 如果有不正之处,希望谅解和批评指

  • Java基础之垃圾回收机制详解

    一.GC的作用 进行内存管理 C语言中的内存,申请内存之后需要手动释放:一旦忘记释放,就会发生内存泄漏! 而Java语言中,申请内存后会由GC来释放内存空间,无需手动释放 GC虽然代替了手动释放的操作,但是它也有局限性: 需要消耗更多的资源: 没有手动释放那么及时: STW(Stop The World)会影响程序的执行效率 二.GC主要回收哪些内存 (1)堆:主要回收堆中的内存 (2)方法区:需要回收 (3)栈(包括本地方法栈和JVM虚拟机栈):不需要回收,栈上的内存什么时候释放是明确的(线程

  • Java 垃圾回收机制详解及实例代码

     Java 垃圾回收机制详解 乍一看,垃圾回收所做的事情应当恰如其名--查找并清除垃圾.事实上却恰恰相反.垃圾回收会跟踪所有仍在使用的对象,然后将剩余的对象标记为垃圾.牢记了这点之后,我们再来深入地了解下这个被称为"垃圾回收"的自动化内存回收在JVM中到底是如何实现的. 手动管理内存 在介绍现代版的垃圾回收之前,我们先来简单地回顾下需要手动地显式分配及释放内存的那些日子.如果你忘了去释放内存,那么这块内存就无法重用了.这块内存被占有了却没被使用.这种场景被称之为内存泄露. 下面是用C写

  • Java 中的垃圾回收机制详解

    目录 介绍 重要条款: 使对象符合 GC 条件的方法 请求JVM运行垃圾收集器的方式 定稿 总结 介绍 在 C/C++ 中,程序员负责对象的创建和销毁.通常程序员会忽略无用对象的销毁.由于这种疏忽,在某些时候,为了创建新对象,可能没有足够的内存可用,整个程序将异常终止,导致OutOfMemoryErrors. 但是在 Java 中,程序员不需要关心所有不再使用的对象.垃圾回收机制自动销毁这些对象. 垃圾回收机制是守护线程的最佳示例,因为它始终在后台运行. 垃圾回收机制的主要目标是通过销毁无法访问

  • 浅谈Java垃圾回收机制

    一.什么是垃圾 java中,什么样的对象是垃圾?有人说:没有被引用的对象就是垃圾对象.我一开始对此也是深信不疑的,但是当年我这么回答面试官的时候,得到的是一个大大的白眼. 判断一个对象是否是垃圾,有两种算法,一种是引用计数法,但是,这种方法解决不了循环引用的问题. /**循环问题*/ public class Demo{ public Demo instance; public static void main(String[] args) { Demo a=new Demo(); Demo b

  • Java垃圾回收机制的示例详解

    目录 一.概述 二.对象已死? 1.引用计数算法 2.可达性分析算法 3.四种引用 4.生存还是死亡? 5.回收方法区 三.垃圾收集算法 1.分代收集理论 2.名词解释 3.标记-清除算法 4.标记-复制算法 5.标记-整理算法 一.概述 说起垃圾收集(Garbage Collection,下文简称GC),有不少人把这项技术当作Java语言的伴生产 物.事实上,垃圾收集的历史远远比Java久远,在1960年诞生于麻省理工学院的Lisp是第一门开始使 用内存动态分配和垃圾收集技术的语言.当Lisp

  • Java垃圾回收之标记压缩算法详解

    之前写过的一篇Java垃圾回收之标记清除算法详解 ,这个算法有个缺点就是造成内存碎片,存在不连续的空间,这样会导致申请较大空间的时候,又需要进行垃圾回收.下面介绍一下标记压缩算法,可以避免内存碎片. 空白部分是不连续的. 概述 这个算法的标记清除阶段,跟Java垃圾回收之标记清除算法详解  中的是一样的,而对于压缩阶段,它的工作就是移动所有的可达对象到堆内存的同一个区域中,使他们紧凑的排列在一起,从而将所有非可达对象释放出来的空闲内存都集中在一起,通过这样的方式来达到减少内存碎片的目的.如下图:

  • Java垃圾回收之复制算法详解

    之前的Java垃圾回收之标记清除算法详解 会导致内存碎片.下文的介绍的coping算法可以解决内存碎片问题. 概述 如果jvm使用了coping算法,一开始就会将可用内存分为两块,from域和to域, 每次只是使用from域,to域则空闲着.当from域内存不够了,开始执行GC操作,这个时候,会把from域存活的对象拷贝到to域,然后直接把from域进行内存清理. 应用场景 coping算法一般是使用在新生代中,因为新生代中的对象一般都是朝生夕死的,存活对象的数量并不多,这样使用coping算法

  • Java垃圾回收机制算法详解

    概述 Java GC(Garbage Collection,垃圾回收)机制,是Java与C++/C的主要区别之一,作为Java开发者,一般不需要专门编写内存回收和垃圾清理代码,对内存泄露和溢出的问题,也不需要像C程序员那样战战兢兢.这是因为在Java虚拟机中,存在自动内存管理和垃圾清扫机制.概括地说,该机制对JVM中的内存进行标记,并确定哪些内存需要回收,根据一定的回收策略,自动的回收内存,永不停息的保证JVM中的内存空间,防止出现内存泄露和溢出问题. 在真实工作中的项目中,时不时的会发生内存溢

  • Java 垃圾回收机制详解(动力节点Java学院整理)

    1. 垃圾回收的意义 在C++中,对象所占的内存在程序结束运行之前一直被占用,在明确释放之前不能分配给其它对象:而在Java中,当没有对象引用指向原先分配给某个对象的内存时,该内存便成为垃圾.JVM的一个系统级线程会自动释放该内存块.垃圾回收意味着程序不再需要的对象是"无用信息",这些信息将被丢弃.当一个对象不再被引用的时候,内存回收它占领的空间,以便空间被后来的新对象使用.事实上,除了释放没用的对象,垃圾回收也可以清除内存记录碎片.由于创建对象和垃圾回收器释放丢弃对象所占的内存空间,

  • Java垃圾回收机制简述

    说到垃圾回收(Garbage Collection,GC),很多人就会自然而然地把它和Java联系起来.在Java中,程序员不需要去关心内存动态分配和垃圾回收的问题,这一切都交给了JVM来处理. 顾名思义,垃圾回收就是释放垃圾占用的空间,那么在Java中,什么样的对象会被认定为"垃圾"?那么当一些对象被确定为垃圾之后,采用什么样的策略来进行回收(释放空间)?在目前的商业虚拟机中,有哪些典型的垃圾收集器?下面我们就来逐一探讨这些问题.以下是本文的目录大纲: 如何确定某个对象是"

  • java设计模式策略模式图文示例详解

    目录 策略模式 意图 问题 解决方案 真实世界类比 策略模式结构 伪代码 策略模式适合应用场景 实现方式 策略模式优缺点 策略模式优缺点 与其他模式的关系 策略模式 亦称:Strategy 意图 策略模式是一种行为设计模式,它能让你定义一系列算法,并将每种算法分别放入独立的类中,以使算法的对象能够相互替换. 问题 一天,你打算为游客们创建一款导游程序.该程序的核心功能是提供美观的地图,以帮助用户在任何城市中快速定位. 用户期待的程序新功能是自动路线规划:他们希望输入地址后就能在地图上看到前往目的

  • java 中复合机制的实例详解

    java 中复合机制的实例详解 继承的缺陷 继承的缺陷是由它过于强大的功能所导致的.继承使得子类依赖于超类的实现,从这一点来说,就不符合封装的原则. 一旦超类随着版本的发布而有所变化,子类就有可能遭到破坏,即使它的代码完全没有改变. 为了说明的更加具体,假设我们现在程序中使用到了HashSet,我们需要增加一个功能,去统计这个HashSet自创建以来一共曾经添加过多少元素. 在还不知道继承的缺陷的情况下,我们设计了一个类,继承了HashSet,添加了一个属性addCount来进行统计,并且复写了

  • 深入理解Java垃圾回收机制以及内存泄漏

    前言 在segmentfault上看到一个问题:java有完善的GC机制,那么在java中是否会出现内存泄漏的问题,以及能否给出一个内存泄漏的案例.本问题视图给出此问题的完整答案. 垃圾回收机制简介 在程序运行过程中,每创建一个对象都会被分配一定的内存用以存储对象数据.如果只是不停的分配内存,那么程序迟早面临内存不足的问题.所以在任何语言中,都会有一个内存回收机制来释放过期对象的内存,以保证内存能够被重复利用. 内存回收机制按照实现角色的不同可以分为两种,一种是程序员手动实现内存的释放(比如C语

随机推荐