1.垃圾回收

1.1概念

在Java语言中,垃圾回收(Garbage Collection,GC)是一个非常重要的概念。

它的主要作用是回收程序中不再被使用的内存,Java提供的GC功能可以自动监测对象是否已经超过作用域从而达到自动回收内存的目的。即垃圾回收的是无任何引用的对象占据的内存空间而不是对象本身。

1.2任务

Java语言提供了垃圾回收器来自动检测对象的作用域,可自动地把不再被使用的存储空间释放掉。

具体而言,垃圾回收器主要负责三个事情

  • 分配内存;
  • 保证不再被使用的内存被释放掉;
  • 保证被引用的对象不会被回收。

1.3方式

垃圾回收器通常是作为一个单独的低优先级的线程运行,不可预知的情况下对内存堆中已经死亡或者长时间没有使用的对象进行清除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。

2.垃圾回收

要回收的垃圾:不可能在被任何途径使用的对象

找到这些对象的方法:

2.1引用计数法

给对象中添加一个引用计数器。

  • 每当一个地方一用这个对象时,计数器值+1;
  • 当引用失效时,计数器值-1。

任何计数值为0的对象就是不可能再被使用的对象。

Java中没有使用这种算法,因为这种算法很难解决对象之间相互引用的情况。

举例:

/*** 虚拟机参数:-verbose:gc*/
public class ReferenceCountingGC
{private Object instance = null;private static final int _1MB = 1024 * 1024;/** 这个成员属性唯一的作用就是占用一点内存 */private byte[] bigSize = new byte[2 * _1MB];public static void main(String[] args){ReferenceCountingGC objectA = new ReferenceCountingGC();ReferenceCountingGC objectB = new ReferenceCountingGC();objectA.instance = objectB;objectB.instance = objectA;objectA = null;objectB = null;System.gc();}
}

运行结果:

GC 4417K->288K(61440K), 0.0013498 secs]
[Full GC 288K->194K(61440K), 0.0094790 secs]

两个对象相互引用着,但是虚拟机还是把两个对象回收掉了。

这说明虚拟机并不是通过引用计数法来判定对象是否存活的。

2.2可达性分析

这个算法的思想是:用过一系列成为“GC Root”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径成为引用链。

当一个对象到GC Root没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。

Java中的GC Roots对象包括:

  • 虚拟机栈(栈帧中的局部变量区,也叫作局部变量表)中引用的对象;
  • 方法去中的类静态属性引用的对象;
  • 方法区中常量引用的对象;
  • 本地方法栈中JNI(Native方法)引用的对象。

GC Roots举例:

下图为GC Roots的引用链

由图可知,obj8、obj9、obj10都没有到GC Roots对象的引用链。

即便obj9和obj10之间有引用链,它们还是会被当成垃圾处理,可以进行回收。

2.2.1 GC停顿(的原因)

判断对象是否存活的可达性分析对时间的敏感还体现在GC停顿上。

因为可达性分析工作必须在一个能确保一致性的快照中进行,

  • “一致性”的意思是指在整个分析期间整个执行系统看起来就像被冻结在某个时间点上,不可以出现分析过程中对象引用关系还在不断变化的情况。
  • 不然的话可达性分析的结果就无法得到保证,这是导致GC进行时必须停顿所有Java执行线程的一个重要原因。

3.四种引用状态

JDK1.2之前,Java中引用的定义很传统:

如果引用类型的数据中存储的数值代表的是另一块内存的起始地址,就称这块内存代表着一个引用。

(定义很纯粹,但过于狭隘——一个对象只有被引用或者没被引用两种状态)

我们希望描述这样一类对象:

当内存的空间还足够时,则能保留在内存中;

如果内存空间再进行垃圾收集后还是非常紧张,则可以抛弃这些对苍。

JDK1.2之后,Java对引用的概念进行了扩展,将引用分为:强引用、软引用、弱引用、虚引用四种,这四种引用强度一次减弱。

1.强引用

Object obj=new Object();这类引用;

如果一个对象具有强引用,那垃圾回收器当内存不够时,宁愿抛出outofmemory错误也不会程序终止。

只要强引用还存在,垃圾回收器永远不会回收掉被引用的对象。

2.软引用

有些还有用但并非必需的对象。

如果一个对象具有软引用,如内存空间足够,垃圾回收器就不会回收它,如果内存不足,就会回收这些对象的内存(可以实现内存敏感的高速缓存,软引用可以和一个引用队列(ReferenceQueue)联合使用)

如果这次回收还没有足够的内存,才会抛出内存溢出异常。

Java中SoftReference表示软引用。

主要特点:

  1. 具有较强的引用功能。

    1. 只有当内存不够的时候,才进行回收这类内存;
    2. 因此在内存足够的时候,它们通常不被回收。
  2. 这些引用对象还能保证在Java抛出OutOfMemory 异常之前,被设置为null。
    1. 它可以用于实现一些常用图片的缓存,实现Cache的功能,保证最大限度的使用内存而不引起OutOfMemory。

3.弱引用

非必需对象。

被弱引用关联的对象智能生存到下一次垃圾回收之前。

垃圾回收器工作之后,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。

Java中的类WeakReference表示弱引用。

4.虚引用

这个引用在内存中存在的唯一目的就是:在这个对象被收集器回收时收到一个系统通知。跟踪垃圾回收过程,清理被销毁对象的相关资源。

被虚引用关联的对象,和其生存时间完全没关系,无法通过虚引用来取得一个对象实例。

Java中的类PhantomReference表示虚引用。

对于可达性分析算法而言,未到达的对象并非是“非死不可”的,若要宣判一个对象死亡,至少需要经历两次标记阶段:

  1. 如果对象在进行可达性分析后发现,没有与GC Roots相连 的引用链,则该对象被第一次标记并进行筛选。

    1. 筛选条件为:是否有必要执行该对象的finalize方法。
    2. 若对象没有覆盖finalize方法或者改对象否认finalize方法是否已经被虚拟机执行过了,则均视作不必要执行该对象的finalize方法,即该对象将会被回收;
    3. 反之,若该对象覆盖了finalize并且该finalize方法并没有被执行过,那么,这个对象会被放置在一个叫F-Queue的队列中,之后会有虚拟机自动建立的、优先级低的Finalizer线程去执行,而虚拟机不必要等待该线程执行结束。即虚拟机只负责建立线程,其他的事情交给此线程去处理。
  2. 对F-Queue中的对象进行第二次标记。
    1. 如果对象在finalize方法中拯救了自己(即关联上了GC Roots引用链),如把this关键字赋值给其他变量,那么在第二次标记的时候该对象将从“即将回收”的集合中移除;
    2. 如果对象还是没有拯救自己,那就会被回收。
    3. 但是他只能拯救自己一次,第二次就被回收了,具体示例代码如下:
package com.demo;/** 此代码演示了两点:* 1.对象可以再被GC时自我拯救* 2.这种自救的机会只有一次,因为一个对象的finalize()方法最多只会被系统自动调用一次* */
public class FinalizeEscapeGC {public String name;public static FinalizeEscapeGC SAVE_HOOK = null;public FinalizeEscapeGC(String name) {this.name = name;}public void isAlive() {System.out.println("yes, i am still alive :)");}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("finalize method executed!");System.out.println(this);FinalizeEscapeGC.SAVE_HOOK = this;}@Overridepublic String toString() {return name;}public static void main(String[] args) throws InterruptedException {SAVE_HOOK = new FinalizeEscapeGC("leesf");System.out.println(SAVE_HOOK);// 对象第一次拯救自己SAVE_HOOK = null;System.out.println(SAVE_HOOK);System.gc();// 因为finalize方法优先级很低,所以暂停0.5秒以等待它Thread.sleep(500);if (SAVE_HOOK != null) {SAVE_HOOK.isAlive();} else {System.out.println("no, i am dead : (");}// 下面这段代码与上面的完全相同,但是这一次自救却失败了// 一个对象的finalize方法只会被调用一次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 : (");}}
}

运行结果如下:

leesfnullfinalize method executed!leesf
yes, i am still alive :)
no, i am dead : (

由结果可知,该对象拯救了自己一次,第二次没有拯救成功。

因为对象的finalize方法至多只被系统调用一次。

此外,从结果我们可以得知:一个堆对象的this(放在局部变量表中的第一项)引用会永远存在,在方法体内可以将this吟咏风赋值给其他变量,这样读一中的对象就可以被其他变量所引用,即不会被回收。

4.方法区的垃圾回收

方法区的垃圾回收主要回收两部分内容:

  1. 废弃常量
  2. 无用的类

4.1判断废弃常量

没有其他地方引用的常量。

以字面量回收为例,如果一个字符串“abc”已经进入了常量池,但是当前系统并没有一个String对象引用了叫做“abc”的字面量,那么,如果发生垃圾回收并且有必要时,“abc”就会被系统移出常量池。

常量池中的其他类(接口)、方法、字段的符号引用也与此类似。

4.2判断无用的类

需要满足以下三个条件:

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

满足以上三个条件的类可以进行垃圾回收。但是并不是无用就被回收,虚拟机提供了一些参数供我们配置。

5.垃圾收集算法

5.1标记—清除算法

最基础的算法。

分为“标记”和“清除”两个阶段:

  1. 标记处所有需要回收的对象
  2. 标记完成后,统一回收所有被标记的对象。

不足:

  • 效率:标记和清除两个阶段的效率都不高;
  • 空间:标记清除后会产生大量不连续的内存碎片。内存碎片太多可能导致以后程序运行过程中需要分配大对象时,无法找到足够的连续内存而不得不提前出发一次垃圾收集动作。

执行过程如图:

5.2复制(Copying)算法

为了解决效率问题。

将可用的内存分为两块,每次只用其中一块,当这一块内存用完了,就酱还存活着的对象复制到另一块上面,然后再把已经是用过的内存空间一次性清理掉。

这样每次就只需要对整个半区进行内存回收。

内存分配时也不需要考虑内存碎片等复杂情况。只需要移动指针,按照顺序分配即可。

缺点:

内存缩小为了原来的一半,代价较高。

现在的商用虚拟机都采用这种算法来回收新生代。

不过研究表明1:1的比例非常不科学。因此新生代的内存被划分为一块较大的Eden空间和两块较小的Survivor空间。每次使用Eden和其中的一块Survivor。

每次回收时,将Eden和Survivor中还存活的对象一次性复制到另外一块Survivor空间上。最后清理掉Eden和刚才用过Survivor空间。

HotSpot虚拟机默认Eden区和Survivor区的比例为8:1,意思是,每次新生代中可用内存空间为整个新生代容量的90%.

当然,我们没法保证每次回收的都只有不读偶10%的对象存活,当Survivor空间不够用时,需要依赖老年代进行分配担保(Handle Promotion)。

5.3标记—整理(Mark-Compact)算法

复制算法在对象询货率较高的场景下需要进行大量的复制操作,小效率较低。

万一对象存活率为100%,那么需要由额外的空间进行分配担保。

老年代都是不易被回收的对象,对象存活率较高,因此一般不能直接选用复制算法。

根据老年代的特点,有人提出了另外一种标记—整理算法。

过程与标记—清除算法一样,不过不是直接对可回收的对象进行整理,而是让所有存活对象都向一段移动,然后直接清理掉边界以外的内存。

工作过程如图:

5.4分代收集算法

根据上面的内容,用一张图概括一下堆内存的布局:

现代商用虚拟机基本都采用分代收集算法来进行垃圾回收。

根据对象的生命周期的不同将内存划分为几块,然后根据各块的特点采用最适当的收集算法。

大批对象死去,商量对象存活的(新生代),使用复制算法,复制成本低;对象存活率高,没有额外的空间进行分配担保的(老年代),采用标记—清除算法或者标记—整理算法。

6.垃圾收集器

垃圾收集器就是上面讲的理论知识的具体实现了。

不同虚拟机提供的垃圾收集器可能会有很大的差别。

我们使用的是HotSpot,HotSpot所包含的收集器如图:

上图展示了7种作用域不同分代的收集器。

如果两个收集器之间有连线,说明它们可以搭配使用。

虚拟机所处的区域说明它是属于新生代收集器还是老年代收集器。

必须明确一个观点:没有最好的收集器,更加没有万能的收集器,只能选择对具体应用最合适的收集器。

6.1Serial收集器

最基本、发展历史最悠久的收集器。

采用复制算法的单线程的收集器。

  • 单线程意味着它只会使用一个CPU或一条线程去完成垃圾收集工作;
  • 另一方面也意味着它进行垃圾收集时必须暂停其它线程的所有工作,直到它收集结束为止。(在用户不可见的情况下要把用户正常工作的线程全部停掉)——这对很多应用是难以接受的。

目前为止,Serial收集器依然是虚拟机运行在Client模式下默认的新生代收集器,因为它简单而高效。

用户桌面应用场景中,分配给虚拟机管理的内存一般来说不会很大,收集几十兆甚至一两百兆的新生代停顿时间在几十毫秒到一百毫秒,只要不是频繁地发生,这点停顿是完全可以接受的。

Serial收集器运行过程如下图所示:

说明:

  1. 需要STW(Stop The World),停顿时间长;
  2. 简单高效,对于单个CPU环境而言,Serial收集器由于没有线程交互开销,可以获取最高单线程收集效率。

6.2ParNew收集器

其实是Serial的多线程版本。输了使用多条线程进行垃圾收集之外,其余行为和Serial收集器完全一样,包括使用的也是复制算法。

它是Server模式下的虚拟机首选的新生代收集器,其中一个很重要的和性能无关的原因是,处理Serial收集器外,目前只有它能与CMS收集器配合工作。

CMS收集器是一块几乎可以认为有划时代意义的垃圾收集器。因为它第一次实现了让垃圾收集线程与用户线程基本上同时工作。

ParNew收集器在但CPU环境中绝对不会有比Serial收集器更好的效果,甚至由于线程交互的开销,该收集器在两个CPU的环境中都不能百分之百保证可以超越Serial收集器。

当然,随着可用CPU数量的增加,它对于GC时系统资源的有效利用还是很有好处的。

它默认开启的收集线程数与CPU相同,在CPU数量非常多的情况下,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。

6.3 Parallel Scavenge收集器

也是一个新生代收集器,也是使用复制算法,也是并行的多线程收集器。

特点:关注的点和其他收集器不同。

  • CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间;
  • Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。

吞吐量:CPU用于运行用户代码时间与CPU总消耗时间的比值。

即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)

此外,Parallel Scavenge收集器是虚拟机运行在Server模式下的默认垃圾收集器。

  • 停顿时间短适合需要与用户交互的程序,良好的响应速度能提升用户体验;
  • 高吞吐量则可以高效率利用CPU时间,尽快完成运算任务,主要是和在后台运算而不需要太多交互的任务。

虚拟机提供了-XX:MaxGCPauseMillis和-XX:GCTimeRatio两个参数来精确控制最大垃圾收集停顿时间和吞吐量大小。

  • GC停顿时间的缩短是以牺牲吞吐量和新生代空间换取的,所以第一个参数也不是越小越好;

由于与吞吐量密切相关,Parallel Scavenge收集器也称作“吞吐量有限收集器”。

Parallel Scavenge收集器有一个-XX:UseAdaptiveSizePolicy参数,这是一个开关参数:

  • 这个参数打开后,就不需要手动指定新生代的大小、Eden区和Survivor区参数等细节参数了;
  • 虚拟机会根据这些参数以提供最合适的停顿时间或者最大的吞吐量。
  • 适用于对收集器运作原理不是很了解,以至于在优化比较困难时。使用此收集器配合自适应调节策略,把内存管理的调优任务交给虚拟机来完成。

6.4Serial Old收集器

Serial 收集的老年版本,单线程,使用“标记—整理”算法,主要意义是给Client模式下的虚拟机使用。

6.5Parallel Old收集器

Parallel Scavenge收集器的老年版本,使用多线程和“标记—整理”算法。

此收集器在JDK1.6之后出现,“吞吐量优先收集器”终于有了比较名副其实的应用组合。

在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge收集器+Parallel Old收集器的组合。

运行过程如图:

6.6CMS收集器

Concurrent Mark Sweep收集器以互殴最短回收停顿时间为目标,使用“标记—清除”算法,收集过程分为以下四步:

  1. 初始标记:标记GC Roots能直接关联到的对象,时间很短;
  2. 并发标记:进行GC Roots Tracing(可达性分析)过程,时间很长;
  3. 重新标记:修正并发标记期间因用户程序继续运作而导致标记陈升变动的那一部分对象的标记记录,时间较长;
  4. 并发清除:回收内存空间,时间很长。

其中,2和4耗时最长,但是可以与用户线程兵法执行。

运行过程如下:

说明:

  1. 对CPU资源非常敏感,可能会导致应用程序变慢,吞吐量下降;
  2. 无法处理浮动垃圾。
    1. 因为在并发清理阶段用户线程还在运行,自然就会产生新的垃圾,而在此次收集中无法收集它们,只能留到下次收集,这部分垃圾为浮动垃圾。
    2. 同时由于用户线程并发执行,所以需要预留一部分老年代空间提供并发收集时程序运行使用。
  3. 由于采用“标记—清除”算法,会产生大量的内存碎片,不利于大对象的分配,可能会提前出发一次Full GC.
    1. 虚拟机提供了-XX:UseCompactAtFullCollection参数来进行碎片的合并整理过程,这样会使得停顿时间过长;
    2. 虚拟机还提供了一个参数配置-XX:CMSFullGCsBeforeCompaction,用于设置执行多少次不压缩的Full GC后,接着来一次带压缩的GC。

6.7 G1收集器

G1是目前技术发展的最前沿成果之一。

HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。

与其他GC收集器相比,G1收集器有以下特点:

  1. 并行和并发:使用多个CPU来多段Stop The World停段时间,与用户线程并发执行;
  2. 分代收集:独立管理整个堆,但是能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的就对象,以获取更好的收集效果;
  3. 空间整合:基于“标记—整理”算法,无内存碎片便产生;
  4. 可预测的停顿:能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,小号在垃圾收集上的时间不得超过N毫秒;

在G1之前的收集器,收集范围都是整个新生代或者老年代。

使用G1收集器时,Java堆的内存布局与其他的收集器有很大的额差别,他将整个Java堆划分为多个大小相等的独立区域(Region)。虽然还保留由新生代和老年代的概念,但是新生代和老年代不再是物理隔离的了,它们都是一部分(可以不连续)Region的结合。

6.8常用的收集器组合

7.理解GC日志

每种收集器的日志形式都是由它们自身的实现决定的。即每种收集器的日志格式都可不一样。

不过虚拟机为了方便用户阅读,将各个收集器的日志都维持了一定的共享。

看下面一段GC日志:

[GC [DefNew: 310K->194K(2368K), 0.0269163 secs] 310K->194K(7680K), 0.0269513 secs] [Times: user=0.00 sys=0.00, real=0.03 secs]
[GC [DefNew: 2242K->0K(2368K), 0.0018814 secs] 2242K->2241K(7680K), 0.0019172 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
[Full GC (System) [Tenured: 2241K->193K(5312K), 0.0056517 secs] 4289K->193K(7680K), [Perm : 2950K->2950K(21248K)], 0.0057094 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
Heapdef new generation   total 2432K, used 43K [0x00000000052a0000, 0x0000000005540000, 0x0000000006ea0000)eden space 2176K,   2% used [0x00000000052a0000, 0x00000000052aaeb8, 0x00000000054c0000)from space 256K,   0% used [0x00000000054c0000, 0x00000000054c0000, 0x0000000005500000)to   space 256K,   0% used [0x0000000005500000, 0x0000000005500000, 0x0000000005540000)tenured generation   total 5312K, used 193K [0x0000000006ea0000, 0x00000000073d0000, 0x000000000a6a0000)the space 5312K,   3% used [0x0000000006ea0000, 0x0000000006ed0730, 0x0000000006ed0800, 0x00000000073d0000)compacting perm gen  total 21248K, used 2982K [0x000000000a6a0000, 0x000000000bb60000, 0x000000000faa0000)the space 21248K,  14% used [0x000000000a6a0000, 0x000000000a989980, 0x000000000a989a00, 0x000000000bb60000)
No shared spaces configured.
  1. 日志的开头“GC”、“Full GC”表示这次垃圾收集的挺短类型,而不是用来区分新生代GC还是老年代GC的

    1. 如果有Full说明本次GC停止了替她所有工作线程(Stop-The-World);
    2. Full GC的写法是Full gc(System)说明是调用System.gc()方法所触发的GC。
  2. “GC”中接下来的“[DefNew”表示GC发生的区域,这里显示的区域名称与使用的GC收集器密切相关
    1. 上面样例中所使用的Serial收集器中的新生代名为“Default New Generation”,所显示的是“[DefNew”
    2. 如果是ParNew收集器,新生代名称为“[ParNew”,意为“Parllel New Generation”
    3. Pallel Scavenge收集器的新生代名为“PSYoungGen”
    4. 老年代和永久代同理,名称也是由收集器决定的
  3. 后面方括号内部的“310K->194K(2368K)”、“2242K->0K(2368K)”指的是该区域已使用的容量->GC后该内存区域已使用的容量(该内存区域总容量)。方括号外面的“310K->194K(7680K)“、”2242K->2241K(7680K)“则指的是GC前Java堆已使用的容量->GC后Java堆已使用的容量(Java堆总容量)
  4. 再往后“0.0269163 secs”表示该内存区域GC所占用的时间,单位是秒。最后的“[Times:user=0.00 sys=0.00 real=0.03 secs]”则更具体了,user表示用户态消耗的CPU时间、内核态消耗的CPU时间、操作从开始到结束经过的墙钟时间。后面两个的区别是,墙钟时间包括各种费运算的等待消耗,比如等待磁盘I/O、等待线程阻塞,而CPU时间不包括这些耗时,担当系统由多CPU或者多核时,多想爱你城操作会叠加这些CPU时间,所以如果看到user或sys时间超过real时间是完全正常的
  5. :Heap:后面就列举出堆内存目前各个年代的区域的内存情况

7.1 通过日志看到,系统的请求数目很少,每个请求资源也不多,但是系统堆内存占用非常高,可能出现了什么问题?怎样定位问题在哪里

可能是发生了堆溢出或内存泄露,要解决堆内存异常的情况一般的手段是通过内存映像分析工具(如Eclipse Memory Analyzer)对Dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是有必要的,也就是要先分清楚是内存泄露还是内存溢出。

  • 如果是内存泄露,可进一步通过工具查看泄露对象到GC Roots的引用链。于是就能找到泄露的对象是怎么与GC Roots相连接导致进行垃圾回收时没能回收泄露对象所占的内存,有了泄露对象的信息和GC Roots引用链的信息,就可以准确地定位出泄露代码的位置
  • 如果没有发出内存泄露,也是说,内存中的对象确实还活着,那就应该去检查虚拟机的堆参数(-Xmx与-Xms),与物理机器对比看是否还可以进一步扩大,从代码上检查是否存在某些对象声明周期过长、持有状态时间过长等,尝试减少程序运行内存消耗。

8.垃圾回收利弊

8.1优点

垃圾回收器的存在一方面把开发人员从释放内存的复杂工作中解脱出来,提高了开发人员的生产效率;

另一方面,对开发人员屏蔽了释放内存的方法,可以避免因开发人员错误地操作内存而导致应用程序的崩溃,保证了程序的稳定性。

8.2缺点

但是垃圾回收也带来了问题,为了实现垃圾回收,垃圾回收器必须跟踪内存的使用情况,释放没用的对象,在完成内存的释放后还需要处理堆中的碎片,这些操作必定会增加JVM的负担,从而降低程序的执行效率。

9.垃圾回收存在的原因

在程序开发过程中如果忘记或者错误的释放内存,往往会导致程序运行不正常甚至是导致程序崩溃。

为了减轻程序开发人员的工作和保证程序的安全与稳定,Java语言提供了垃圾回收器来自动检测对象的作用域,可自动地回收不可能再被引用的对象所占用的内存。

要请求垃圾收集,可以调用下面的方法之一:System.gc() 或Runtime.getRuntime().gc() ,但JVM可以屏蔽掉显式的垃圾回收调用

10.垃圾回收的时机

垃圾回收可能发生的时间是堆可用空间不足或CPU空闲。

对于HotSpot虚拟机,用OopMap数据结构存储 “程序中哪些地方存放着对象引用” 的信息。在类加载完成的时候,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会在特定的位置上记录下栈和寄存器中哪些位置是引用。这样GC在扫描时就可以直接得到这些信息了。

10.1安全点

程序执行时并不是在所有地方都能停顿下来开始GC,只有到达安全点时才能暂停。其中安全点指的是记录OopMap数据结构的位置。

  • 安全点的选择基本上是以程序“是否能让程序长时间执行的特征”为标准进行选定的。
  • “长时间执行”的最明显的特征就是指令序列复用,例如方法调用、循环跳转、异常跳转等,所有具有这些功能的指令才会产生safepoint。
  • 对于safepoint,另一个需要考虑的问题就是如何在GC发生时让所有线程都“跑”到最近的安全点上再停顿下来。
    • 有两种方案可以选择:抢先式中断和主动式中断。

10.1.1抢先式中断

在GC发生时,首先把所有线程全部中断,如果发现有些线程中断的地方不在安全点上,就恢复线程,让它“跑”到安全点上。

现在几乎没有虚拟机采用这种中断方式应GC事件。

10.1.2主动式中断

当GC需要中断线程的时候,不直接对线程操作,仅仅简单地设置一个标志,各个线程执行时主动轮询这个标志,发现中断标志为真时,就自己中断挂起。轮询标志的地方和安全点是重合的,另外再加上创建对象需要分配内存的地方。

10.2安全区域

安全点机制保证了程序执行时,在不太长的时间内就会遇到可进入GC的安全点。但当线程处于sleep状态或者blocked状态时,这时候线程可能无法响应JVM的中断请求,“走到”安全的地方中断挂起。

对于这种情况,就需要安全区域来解决。

安全区域是指在一段代码片段之中,引用关系不会发生变化,在这个区域的任何地方开始GC都是安全的。因此也可以把安全区域看做被扩展了的安全点。

部分整理自微信公众号“Java编程精选”与《深入理解Java虚拟机——JVM高级特性与最佳实践》

在线程执行到Safe Region中的代码时,首先标识自己已经进入到了Safe Region,那样,当在这段时间里JVM要发起GC时,就不用管标识自己为Safe Region状态的线程了。在线程要离开Safe Region时,要检查系统是否已经完成了根结点的枚举(或者整个GC过程),如果完成了,那线程就继续执行,否则它就继续等待直到收到可以安全离开Safe Region的信号为止。

总之,程序并非是时时刻刻都可以去执行GC操作,只有程序运行到安全区域的时候才可以发起GC的操作。

Java垃圾回收(GC)、找垃圾的方式、GC Root、GC停顿、引用、垃圾收集算法、收集器、GC日志、安全点、安全区域相关推荐

  1. JVM3--垃圾回收机制:垃圾回收概述、垃圾回收算法及分类

    一.垃圾回收概述 1.垃圾对象:运行程序中没有任何指针指向的对象 2.垃圾回收区域: 方法区+堆空间        频繁回收Young区        较少回收old区        基本不动Perm ...

  2. 53.垃圾回收算法的实现原理、启动Java垃圾回收、Java垃圾回收过程、垃圾回收中实例的终结、对象什么时候符合垃圾回收的条件、GC Scope 示例程序、GC OutOfMemoryError的示例

    53.垃圾回收算法的实现原理 53.1.目录 53.2.启动Java垃圾回收 53.3.Java垃圾回收过程 53.4.垃圾回收中实例的终结 53.5.对象什么时候符合垃圾回收的条件? 53.5.1. ...

  3. java垃圾回收菜鸟_java垃圾回收机制

    1:对象可能不被垃圾回收 2:垃圾回收并不等于"析构" 3:垃圾回收只与内存有关,为了回收程序不再使用的内存 java虚拟机采用了"自适应"的垃圾回收机制,即& ...

  4. java 垃圾回收 新生代_Java垃圾回收

    一.概述 Java垃圾回收器实现内存的自动分配和回收,这两个操作都发生在Java堆上(还包括方法区,即永久代).垃圾回收操作不是实时的发生(对象死亡不会立即释放),当内存消耗完或者是达到某一指标(th ...

  5. 垃圾回收概述(垃圾回收算法)

    垃圾回收概述 Java 和 C++语言的区别,就在于垃圾收集技术和内存动态分配上,C++语言没有垃圾收集技术,需要程序员手动的收集. 垃圾收集,不是Java语言的伴生产物.早在1960年,第一门开始使 ...

  6. 简介三种垃圾回收机制:分代复制垃圾回收,标记垃圾回收,增量垃圾回收

    一.分代复制垃圾回收 不同的对象的生命周期是不一样的.因此,不同生命周期的对象可以采取不同的收集方式,以便提高回收效率. 在Java程序运行的过程中,会产生大量的对象,其中有些对象是与业务信息相关,比 ...

  7. Axure高保真web端后台管理系统/垃圾回收分类系统/垃圾回收高保真原型设计 /垃圾分类后台管理系统/垃圾回收分类平台//垃圾回收分类智慧管理系统/订单管理/财务管理/系统管理/库存管理/设备管理

    Axure高保真web端后台管理系统/垃圾回收分类系统/垃圾回收高保真原型设计 /垃圾分类后台管理系统/垃圾回收分类平台//垃圾回收分类智慧管理系统/订单管理/财务管理/系统管理/库存管理/设备管理 ...

  8. system.gc会立即执行垃圾回收吗_JVM垃圾回收系列之 垃圾回收器

    java垃圾收集器的分类以及特点如下所示: 1.串行垃圾回收器.这是一个单线程的收集器,就是说在gc回收的时候,无论是年轻代还是老年代,都是一个线程去执行,并且在执行期间,挂起用户线程,直到收集工作完 ...

  9. java 什么时候进行垃圾回收_Java中垃圾回收有什么目的?什么时候进行垃圾回收?...

    仅提供一个大致的思路: 垃圾回收(gc)的目的是释放堆中不需要保存的对象,达到内存的充分利用. 1.回收哪些对象的判定 垃圾回收最简单的思路是采用引用计数的方式,即记录对象被引用的次数,直到一段时间内 ...

  10. Java对象垃圾回收调用,JVM垃圾回收之哪些对象可以被回收

    1.背景 Java语言相比于C和C++,一个最大的特点就是不需要程序员自己手动去申请和释放内存,这一切交由JVM来完成.在Java中,运行时的数据区域分为程序计数器.Java虚拟机栈.本地方法栈.方法 ...

最新文章

  1. Linux下的Silverlight:Moonlight 1.0 Beta 1发布了
  2. 万万没想到,1200 年前老祖宗们就知道大数据了
  3. Swish激活 hswish激活
  4. java自定义日志级别_自定义log4j日志级别
  5. ML之FE:pandas库中数据分析利器之groupby分组函数、agg聚合函数、同时使用groupby与agg函数组合案例之详细攻略
  6. TypeScript Generics(泛型)
  7. 2019-03-06-算法-进化(三数之和)
  8. Windows安装Python包下载工具pip遇到的问题
  9. java数组的四个要素_Java零基础系列教程04Java数组
  10. php fs 上传文件,PHP操作GridFS存储文件到MongoDB的三种方式
  11. Oracle 并行执行SQL
  12. Python基础(while循环/赋值运算符)
  13. SMTP Error: Could not connect to SMTP host.
  14. Thread 编程:简明(1) - 协作式取消 VS 线程终止
  15. 您有一份Microsoft Office 365技能宝典等待签收
  16. TFTP服务器的使用
  17. Eclipse中离线安装ADT插件详细教程
  18. Internet上的Linux资源
  19. 免费文本转语音(在线文本转语音)
  20. ASCII:字符集与字符编码的起源

热门文章

  1. NOIP2008 双栈排序
  2. 【mysql】[error]group_concat造成的sql语法错误
  3. linux编译安装git
  4. Zabbix 监控TCP的SYN,establised
  5. c#下各种数据库操作的封装!(支持ACCESS,SQLSERVER,DB2,ORACLE,MYSQL)(二)
  6. android 双 webview,Android webview加载页面
  7. 推荐 10 个实用型的热门开源项目,开发效率又能提升了!
  8. 推荐一个 Vue3 全家桶 + TS+ Vite2 + element-plus 的网站实战项目
  9. Fiddler抓包工具详解(五)(IOS、安卓抓包+fildder插件)
  10. reactjs npm start运行报错:Error: ENOSPC: System limit for number of file watchers reached