JVM学习(六)--垃圾回收
目录
一、垃圾回收
1、概述
1.1、什么是垃圾(Garbage))?
1.2、为什么需要GC?
1.3、Java中垃圾回收的重点区域是?
1.4、早期的GC
2、垃圾回收算法
2.1、垃圾判别阶段算法
1、引用计数算法
2、可达性分析算法(或根搜索算法、追踪性垃圾收集)
2.2、垃圾清除阶段算法
1、标记-清除算法
2、复制算法
3、标记-压缩算法
4、分代收集算法
5、增量收集算法
6、分区算法
3、相关概念
4、垃圾回收器
5、分析GC日志
一、垃圾回收
面试题:
1、讲讲JVM的GC?
2、垃圾回收机制
3、垃圾回收的优点和原理
1、概述
垃圾收集,不是Java语言的伴生产物。早在1968年,第一门开始使用内存动态分配和垃圾收集技术的Lisp语言诞生。
关于垃圾收集有三个经典问题:
1、哪些内存需要回收?
2、什么时候回收?
3、如何回收?
垃圾收集机制是Java的招牌能力,极大地提高了开发效率。如今,垃圾收集几乎成为现代语言的标配,即使经过如此长时间的发展,Java的垃圾收集机制仍然在不断的演进中,不同大小的设备、不同特征的应用场景,对垃圾收集提出了新的挑战,这当然也是面试的热点。
1.1、什么是垃圾(Garbage))?
1、垃圾是指在运行程序中没有任何指针指向的对象,这个对象就是需要被回收的垃圾。
2、外文:An object is considered garbage when it can no longer be reached from any pointer in the running program.
如果不及时对内存中的垃圾进行清理,那么,这些垃圾对象所占的内存空间会一直保留到应用程序结束,被保留的空间无法被其他对象使用。甚至可能导致内存溢出。
1.2、为什么需要GC?
对于高级语言来说,一个基本认知是如果不进行垃圾回收,内存迟早都会被消耗完,因为不断地分配内存空间而不进行回收,就好像不停地生产生活垃圾而从来不打扫一样。
除了释放没用的对象,垃圾回收也可以清除内存里的记录碎片。碎片整理将所占用的堆内存移到堆的一端,以便 JVM 将整理出的内存分配给新的对象。
随着应用程序所应付的业务越来越庞大、复杂,用户越来越多,没有GC就不能保证应用程序的正常进行。而经常造成STW的GC又跟不上实际的需求,所以才会不断地尝试对GC进行优化。
对于Java开发人员而言,自动内存管理就像是一个黑匣子,如果过度依赖于“自动”,那么这将会是一场灾难,最严重的就会弱化Java开发人员在程序出现内存溢出时定位问题和解决问题的能力。
此时,了解]VM的自动内存分配和内存回收原理就显得非常重要,只有在真正了解JVM是如何管理内存后,我们才能够在遇见OutOfMemoryError时,快速地根据错误异常日志定位问题和解决问题。
当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到更高并发量的瓶颈时,我们就必须对这些“自动化”的技术实施必要的监控和调节。
1.3、Java中垃圾回收的重点区域是?
垃圾回收器可以对年轻代回收,也可以对老年代回收,甚至是全堆和方法区的回收。
其中,Java堆是垃圾收集器的工作重点。
从次数上讲:1、频繁收集Young区 2、较少收集Old区 3、基本不动Perm区(或元空间)
1.4、早期的GC
在早期的C/C++时代,垃圾回收基本上是手工进行的。开发人员可以使用new关键字进行内存申请,并使用delete关键字进行内存释放。
这种方式可以灵活控制内存释放的时间,但是会给开发人员带来频繁申请和释放内存的管理负担。倘若有一处内存区间由于程序员编码的问题忘记被回收,那么就会产生内存泄漏,垃圾对象永远无法被清除,随着系统运行时间的不断增长,垃圾对象所耗内存可能持续上升,直到出现内存溢出并造成应用程序崩溃。
现在,除了Java以外,C#、Python、Ruby等语言都使用了自动垃圾回收的思想,也是未来发展趋势。可以说,这种自动化的内存分配和垃圾回收的方式已经成为现代开发语言必备的标准。
2、垃圾回收算法
面试题:
1、GC的算法,复制算法和标记清除的优缺点?
2、GC 的三种收集方法:标记清除、标记整理、复制算法的原理与特点,分别用在什么地方,如果让你优化收集方法,有什么思路?
3、JVM GC算法有哪些,目前的DK版本采用什么回收算法
4、如何判断一个对象是否存活?
5、你是用什么方法判断对象是否死亡?
2.1、垃圾判别阶段算法
在堆里存放着几乎所有的java对象实例,在GC执行垃圾回收之前,首先需要区分出内存中哪些是存活对象,哪些是已经死亡的对象。只有被标记为已经死亡的对象,GC才会在执行垃圾回收时,释放掉其所占用的内存空间, 因此这个过程我们可以称为垃圾标记阶段。
那么在JVM中究竟是如何标记一个死亡对象呢?
简单来说,当一个对象已经不再被任何的存活对象继续引用时,就可以宣判为已经死亡。
1、引用计数算法
引用计数算法(Reference Counting)比较简单,对每个对象保存一个整型的引用计数器属性。用于记录对象被引用的情况。
原理:对于一个对象A,只要有任何一个对象引用了A,则A 的引用计数器就加1,当引用失效时,引用计数器就减1。只要对象A的引用计数器的值为8即表示对象A不可能再被使用,可进行回收。
优点:实现简单,垃圾对象便于辨识;判定效率高,回收没有延迟性。
缺点1:它需要单独的字段存储计数器,这样的做法增加了存储空间的开销。
缺点2:每次赋值都需要更新计数器,伴随着加法和减法操作,这增加了时间开销。
缺点3:引用计数器有一个严重的问题,即无法处理循环引用的情况。这是一条致命缺陷,导致在Java 的垃圾回收器中没有使用这类算法。
/*** -XX:+PrintGCDetails -Xms20m -Xmx20m* 证明:java使用的不是引用计数算法*/
public class RefCountGc {// 这个成员属性唯一的作用就是占用一点内存private byte[] bigSize = new byte[5 * 1024 * 1024];//5MBObject reference = null;public static void main(String[] args) {RefCountGc obj1 = new RefCountGc();RefCountGc obj2 = new RefCountGc();obj1.reference = obj2;obj2.reference = obj1;obj1 = null;obj2 = null;// 显式的执行垃圾回收行为// 这里发生Gc,obj1和obj2能否被回收?System.gc();try {Thread.sleep(1000000);} catch (InterruptedException e) {throw new RuntimeException(e);}}
}
如果就想用此算法,怎么解决循环引用
引用计数算法,是很多语言的资源回收选择,例如因人工智能而更加火热的Python,它更是同时支持引用计数和垃圾收集机制。
Java并没有选择引用计数,是因为其存在一个基本的难题,也就是很难处理循环引用关系。
2、可达性分析算法(或根搜索算法、追踪性垃圾收集)
相对于引用计数算法而言,可达性分析算法不仅同样具备实现简单和执行高效等特点,更重要的是该算法可以有效地解决在引用计数算法中循环引用的问题,防止内存泄漏的发生。
相较于引用计数算法,这里的可达性分析就是Java、C#选择的。这种类型的垃圾收集通常也叫作追踪性垃圾收集(Tracing Garbage Collection)。
原理:其原理简单来说,就是将对象及其引用关系看作一个图,选定活动的对象作为GCRoots,然后跟踪引用链条,如果一个对象和GC Roots之间不可达,也就是不存在引用链条,那么即可认为是可回收对象。
基本思路:
1、可达性分析算法是以根对象集合(GcRoots)为起始点,按照从上至下的方式搜索被根对象集合所连接的目标对象是否可达。
2、使用可达性分析算法后,内存中的存活对象都会被根对象集合直接或间接连接着,搜索所走过的路径称为引用链(Reference chain)。
3、如果目标对象没有任何引用链相连!则是不可达的,就意味着该对象已经死亡,可以标记为垃圾
对象。
4、在可达性分析算法中,只有能够被根对象集合直接或者间接连接的对象才是存活对象。
这个算法目前较为常用。
优点:实现简单,执行高效,有效的解决循环引用的问题,防止内存泄漏。
GC Roots
在Java 语言中, GC Roots 包括以下几类元素:
1、虚拟机栈中引用的对象。比如:各个线程被调用的方法中使用到的参数、局部变量等。
2、本地方法栈内JNI(通常说的本地方法)引用的对象。
3、类静态属性引用的对象。比如:Java类的引用类型静态变量
4、方法区中常量引用的对象。比如:字符串常量池(string Table)里的引用
5、所有被同步锁synchronized持有的对象
6、Java虚拟机内部的引用。
基本数据类型对应的class对象,一些常驻的异常对象(如:NullPointerException、OutofMemoryError),系统类加载器。
7、反映java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。
8、除了这些固定的GC Roots集合以外,根据用户所选用的垃圾收集器以及当前回收的内存区域不同,还可以有其他对象“临时性”地加入,共同构成完整GCRoots集合。比如:分代收集和局部回收(Partial Gc)。
如果只针对Java堆中的某一块区域进行垃圾回收(比如:典型的只针对新生代),必须考虑到内存区域是虚拟机自己的实现细节,更不是孤立封闭的,这个区域的对象完全有可能被其他区域的对象所引用,这时候就需要一并将关联的区域对象也加入GCRoots集合中去考虑,才能保证可达性分析的准确性。
小技巧:由于Root 采用栈方式存放变量和指针,所以如果一个指针,它保存了堆内存里面的对象,但是自己又不存放在堆内存里面,那它就是一个Root。
面试题:
1、Java Gc机制?GC Roots有哪些?
2、哪些部分可以作为GC Root?
3、JVM怎样判断一个对象是否可回收,怎样的对象才能作为Gc root?
注意点:如果要使用可达性分析算法来判断内存是否可回收,那么分析工作必须在一个能保障一致性的快照中进行。这点不满足的话分析结果的准确性就无法保证。
这点也是导致GC进行时必须“Stop The World”的一个重要原因。
即使是号称(几乎)不会发生停顿的CMS收集器中,枚举根节点时也是必须要停顿的。
2.2、垃圾清除阶段算法
当成功区分出内存中存活对象和死亡对象后,GC接下来的任务就是执行垃圾回收,释放掉无用对象所占用的内存空间,以便有足够的可用内存空间为新对象分配内存。目前在JVM中比较常见的三种垃圾收集算法是标记-清除算法(Mark-Sweep)、复制算法(Copying )、标记-压缩算法( Mark-Compact)。
1、标记-清除算法
2、复制算法
3、标记-压缩算法
4、分代收集算法
5、增量收集算法
6、分区算法
3、相关概念
4、垃圾回收器
5、分析GC日志
JVM学习(一)
JVM学习(五)--执行引擎
再小的努力,乘以365都很明显!
每天⽤⼼记录⼀点点。内容也许不重要,但习惯很重要!
一个程序员最重要的能力是:写出高质量的代码!!
有道无术,术尚可求也,有术无道,止于术。
无论你是年轻还是年长,所有程序员都需要记住:时刻努力学习新技术,否则就会被时代抛弃!