Java GC学习总结

一、概要

1、GC(Garbage Collection):垃圾回收。在GC中,垃圾所指的是程序在运行过程中,会产生出一些无用的对象,或者说是已经被弃用的对象,而这些对象会占用着一部分的内存空间,如果长时间不去回收这些内存空间,那么最终会导致OOM(内存泄漏)。GC就负责处理这些已用对象(new)。

2、JVM堆: 1)新域(coping算法):用来存放新产生的对象。分为三部分——eden、from survivor和to survivor(比例8:1:1)。

2)旧域(tracing算法):用来存放经过几次回收还没有回收掉的对象。(新旧比例1:2)

3)永久域:用于存放静态文件,如Java类、方法等。对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。独立。不包括在JVM堆内。默认为4M。

3、注意:

1)JVM堆的大小决定了GC的运行时间。如果JVM堆的大小超过一定的限度,那么GC的运行时间会很长。

2)对象生存的时间越长,GC需要的回收时间也越长,影响了回收速度。

3)大多数对象都是短命的,所以,如果能让这些对象的生存期在GC的一次运行周期内就很好了。

4)应用程序中,建立与释放对象的速度决定了垃圾收集的频率。

5)如果GC一次运行周期超过3-5秒,这会很影响应用程序的运行,如果可以,应该减少JVM堆的大小了。

6)通常情况下,JVM堆的大小应为物理内存的80%。

二、收集器介绍

新生代收集器使用的收集 器:Serial、PraNew、Parallel Scavenge

老年代收集器使用的收集器:Serial Old、Parallel Old、CMS

每个收集器介绍:

Serial收集器(复制算法):新生代单线程收集器,标记和清理都是单线程,优点是简单高效。

Serial Old收集器(标记-整理算法):老年代单线程收集器,Serial收集器的老年代版本。

ParNew收集器(停止-复制算法):新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现。

Parallel Scavenge收集器(停止-复制算法):并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间/(用户线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景。

Parallel Old收集器(停止-复制算法):Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先。

CMS(Concurrent Mark Sweep)收集器(标记-清理算法):高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu 追求高响应时间的选择。

三、GC工作流程

新生成的对象都放在eden中。eden满了,GC开始工作。停止应用程序的运行,开始收集垃圾,把所有可找到的对象都copy到from survivor空间中,这个空间满了就copy到to survivor空间中(会覆盖原有存储对象),来回copy。工作流程大致可以分为:

1)当eden满了,触发Scavenge GC

2)young GC做两件事: (1)去掉一部分没用的object

(2)把老的还被引用的object发到survivor里面,几次GC以后再放到old里面

3)当old满了,触发Full GC。Full GC耗内存,把young old大部分垃圾都收掉,这个时候用户线程都会被block。

注意:Full GC 对整个堆进行整理,包括Young、Tenured和Perm,所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:

1.年老代(Tenured)被写满

2.持久代(Perm)被写满

3.System.gc()被显示调用

4.上一次GC之后Heap的各域分配策略动态变化

四、GC如何判断一个对象是垃圾

1、引用计数法(Reference Counting Collector)

1)算法分析:引用计数是垃圾收集器中的早期策略。在这种方法中,堆中每个对象实例都有一个引用计数。当一个对象被创建时,且将该对象实例分配给一个变量,该变量计数设置为1。当任何其它变量被赋值为这个对象的引用时,计数加1(a = b,则b引用的对象实例的计数器+1),但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1。任何引用计数器为0的对象实例可以被当作垃圾收集。当一个对象实例被垃圾收集时,它引用的任何对象实例的引用计数器减1。

2)优点:引用计数收集器可以很快的执行,交织在程序运行中。对程序需要不被长时间打断的实时环境比较有利。

3)缺点:无法检测出循环引用。如父对象有一个对子对象的引用,子对象反过来引用父对象。这样,他们的引用计数永远不可能为0。例如:

public class Main {
    public static void main(String[] args) {
        MyObject object1 = new MyObject();
        MyObject object2 = new MyObject();
          
        object1.object = object2;
        object2.object = object1;
          
        object1 = null;
        object2 = null;
    }
}

        最后面两句将object1和object2赋值为null,也就是说object1和object2指向的对象已经不可能再被访问,但是由于它们互相引用对方,导致它们的引用计数器都不为0,那么垃圾收集器就永远不会回收它们。

    2、可达性分析算法(GC Roots Analysis)——主流

        1)基本思路:通过一系列名为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,下图对象object5, object6, object7虽然有互相判断,但它们到GC Roots是不可达的,所以它们将会判定为是可回收对象。


2)在Java语言里,可作为GC Roots对象的包括如下几种:
    (1)虚拟机栈(栈桢中的本地变量表)中的引用的对象
    (2)方法区中的类静态属性引用的对象
    (3)方法区中的常量引用的对象

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

3、finalize()方法最终判定对象是否存活
    即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程。(标记的前提是对象在进行可达性分析后发现没有与GC Roots相连接的引用链。)
  1)第一次标记并进行一次筛选。
    筛选的条件:此对象是否有必要执行finalize()方法。
    当对象没有覆盖finalize方法,或者finzlize方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”,对象被回收。
2)第二次标记
    如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为:F-Queue的队列之中,并在稍后由一条虚拟机自动建立的、低优先级的Finalizer线程去执行。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。这样做的原因是,如果一个对象finalize()方法中执行缓慢,或者发生死循环(更极端的情况),将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。

    Finalize()方法是对象脱逃死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象要在finalize()中成功拯救自己----只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了。(任何一个对象的finalize方法都只会被系统自动调用一次。)

流程图如下:


五、内存泄漏问题

1、在谈内存泄漏之前,我们先看一下java和c++在内存分配和管理上有什么区别?

Java与C++之间有一堵由动态内存分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人想出来。

对于从事C和C++程序开发的开发人员来说,在内存管理领域,他们既是拥有最高权利的皇帝,也是从事最基础工作的劳动人民-----既拥有每一个对象的所有权,又担负着每一个对象从生命开始到终结的维护责任。
对于Java程序员来说,虚拟机的自动内存分配机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,而且不容易出现内存泄露和内存溢出问题,看起来由虚拟机管理内存一切都很美好。不过,也正是因为Java程序员把内存控制的权利交给Java虚拟机,一旦出现内存泄露和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那排查错误将会是一项异常艰难的工作。

并且好的Java程序在编写的时候肯定要考虑GC的问题,怎样定义static对象,怎样new对象效率更高等等问题,简称面向GC的编程。也可以说Java的内存分配管理是一种托管的方式,托管于JVM。

C++经过编译时直接编译成机器码,而Java是编译成字节码,由JVM解释执行。C++是编译型语言,而Java兼具编译型和解释型语言的特点。

2、java有了GC同样会出现内存泄漏问题

1)静态集合类像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,所有的对象Object也不能被释放,因为他们也将一直被Vector等应用着。

static Vector v=new Vector();
     for(int i=1;i<100;i++) 
     {
	Object o=new Object();
	v.add(o);
	o=null;
     }

在这个例子中,代码栈中存在Vector 对象的引用 v 和 Object 对象的引用 o 。在 For 循环中,我们不断的生成新的对象,然后将其添加到 Vector 对象中,之后将 o 引用置空。问题是当 o 引用被置空后,如果发生 GC,我们创建的 Object 对象是否能够被 GC 回收呢?答案是否定的。因为, GC 在跟踪代码栈中的引用时,会发现 v 引用,而继续往下跟踪,就会发现 v 引用指向的内存空间中又存在指向 Object 对象的引用。也就是说尽管o 引用已经被置空,但是 Object 对象仍然存在其他的引用,是可以被访问到的,所以 GC 无法将其释放掉。如果在此循环之后, Object 对象对程序已经没有任何作用,那么我们就认为此 Java 程序发生了内存泄漏。

2)各种连接,数据库连接,网络连接,IO连接等没有显示调用close关闭,不被GC回收导致内存泄露。

3)监听器的使用,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露。

六、面试题

GC在什么时候,对什么东西,做了什么事情?
在什么时候:eden满了minor gc,升到老年代的对象大于老年代剩余空间full gc,或者小于时被HandlePromotionFailure参数强制full gc;gc与非gc时间耗时超过了GCTimeRatio的限制引发OOM,调优诸如通过NewRatio控制新生代老年代比例,通过MaxTenuringThreshold控制进入老年前生存次数等答到就行;

对什么东西:从root搜索不到,而且经过第一次标记、清理后,仍然没有复活的对象;
做了什么事情:说出诸如新生代做的是复制清理、from survivor、to survivor是干啥用的、老年代做的是标记清理、标记清理后碎片要不要整理、复制清理和标记清理有有什么优劣势等。 讲清楚串行、并行(整理/不整理碎片)、CMS等搜集器可作用的年代、特点、优劣势,并且能说明控制/调整收集器选择的方式。

七、eclipse设置GC日志输出

右击项目——properties——run/debug setting——edit


1、在eclipse根目录下的eclipse.ini配置文件中添加以下参数:

-verbose:gc (开启打印垃圾回收日志)

-Xloggc:eclipse_gc.log (设置垃圾回收日志打印的文件,文件名称可以自定义)

-XX:+PrintGCTimeStamps (打印垃圾回收时间信息时的时间格式)

-XX:+PrintGCDetails (打印垃圾回收详情)

添加完以上参数后当启动Eclipse后就能在Eclipse根目录看到一个eclipse_gc.log的gc日志文件

2、设置eclipse初始堆、非堆内存大小以及年轻代

-Xms50m –Xmx200m -XX:PermSize=30m -XX:MaxPermSize=60m

3、添加JVM监控参数

-Djava.rmi.server.hostname=127.0.0.1

-Dcom.sun.management.jmxremote.port=6688

-Dcom.sun.management.jmxremote.ssl=false

-Dcom.sun.management.jmxremote.authenticate=false

4.gc日志:

Java HotSpot(TM) Client VM (25.25-b02) for windows-x86 JRE (1.8.0_25-b18), built on Oct  7 2014 14:31:05 by "java_re" with MS VC++ 10.0 (VS2010)
Memory: 4k page, physical 8340720k(3545144k free), swap 8787248k(2501780k free)
CommandLine flags: -XX:InitialHeapSize=268435456 -XX:MaxHeapSize=943718400 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:-UseLargePagesIndividualAllocation
4.458: [GC (Allocation Failure) 4.458: [DefNew: 69952K->8704K(78656K), 0.0681410 secs] 69952K->22643K(253440K), 0.0690407 secs] [Times: user=0.06 sys=0.02, real=0.08 secs]
5.741: [Full GC (Metadata GC Threshold) 5.741: [Tenured: 13939K->33208K(174784K), 0.1036054 secs] 48618K->33208K(253440K), [Metaspace: 11442K->11442K(12672K)], 0.1041130 secs] [Times: user=0.11 sys=0.00, real=0.11 secs]
12.427: [GC (Allocation Failure) 12.427: [DefNew: 70016K->8704K(78720K), 0.0821340 secs] 103224K->55838K(253504K), 0.0822684 secs] [Times: user=0.08 sys=0.00, real=0.09 secs]
12.787: [Full GC (Metadata GC Threshold) 12.787: [Tenured: 47134K->56307K(174784K), 0.1662610 secs] 59785K->56307K(253504K), [Metaspace: 19215K->19215K(20864K)], 0.1663899 secs] [Times: user=0.16 sys=0.00, real=0.16 secs]
17.018: [GC (Allocation Failure) 17.018: [DefNew: 70016K->8704K(78720K), 0.0475891 secs] 126323K->68567K(253504K), 0.0477196 secs] [Times: user=0.03 sys=0.02, real=0.06 secs]
21.047: [GC (Allocation Failure) 21.047: [DefNew: 78720K->8704K(78720K), 0.0752255 secs] 138583K->83739K(253504K), 0.0753766 secs] [Times: user=0.08 sys=0.00, real=0.06 secs]
21.320: [Full GC (Metadata GC Threshold) 21.320: [Tenured: 75035K->61015K(174784K), 0.2800589 secs] 87423K->61015K(253504K), [Metaspace: 31969K->31969K(34176K)], 0.2802057 secs] [Times: user=0.28 sys=0.00, real=0.29 secs]

5、GC日志说明:

GC打印时间: [垃圾回收类型回收时间: [收集器名称: 年轻代回收前占用大小->年轻代回收后占用大小(年轻代当前容量),年轻代局部GC时JVM暂停处理的时间] 堆空间GC前占用的空间->堆空间GC后占用的空间(堆空间当前容量),GC过程中JVM暂停处理的时间]。
垃圾回收类型:分为GC和Full GC。GC一般为堆空间某个区发生了垃圾回收,Full GC基本都是整个堆空间及持久代发生了垃圾回收,通常优化的目标之一是尽量减少GC和Full GC的频率。
收集器名称:一般都为收集器的简称或别名,通过收集器名称基本都能判断出那个区发生了GC。
DefNew:年轻代(新生代)发生了GC (若为DefNew可知当前JVM年轻代使用的串行收集器)
ParNew:年轻代(新生代)发生了GC (若为ParNew可知当前JVM年轻代使用了并行收集器)
Tenured:老年代发生了GC
Perm:持久代发生了GC

参考:https://blog.csdn.net/jiafu1115/article/details/7024323

          http://www.importnew.com/16173.html

          http://blog.csdn.net/u011669081/article/details/51866838

          https://blog.csdn.net/ochangwen/article/details/51406779

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值