前言:垃圾回收机制,大家都知道JAVA的垃圾回收都是JVM自动回收的,不需要程序员去管理。但是我们还是得知道原理才能在适当时机进行JVM调优
原理:当我们new 一个对象时JVM堆区就会分配一块内存(地址,大小)给这个对象,当这个对象“不可达”的时候(即程序无法访问的时候),GC就需要回收这块空间。
Java语言规范没有明确地说明JVM使用哪种垃圾回收算法,但是任何一种垃圾回收算法一般要做2件基本的事情:(1)发现无用信息对象;(2)回收被无用对象占用的内存空间,使该空间可被程序再次 使用,
算法包括:
1. 引用计数(Reference Counting):给每个对象添加一引用计数器,每当有一个地方引用它的时候,计数器+1;引用失效的时候,计数器-1
2.可达性分析算法(Rearchability Analysis):通过一系列的“GC roots”的对象作为起点,从这些节点向下搜索,搜索所超过的路径称为引用链,当一个对象到GC roots没有任何引用链相连的时候则此对 象是不可用的,不可用的对象则是JVM判定为可回收的对象。目前Java中可以作为GC ROOT的对象有:
1、虚拟机栈中引用的对象(本地变量表)
2、方法区中静态属性引用的对象
3、方法区中常亮引用的对象
4、本地方法栈中引用的对象(Native对象)
3. 标记-清除(Mark-Sweep):从引用的根节点开始标记所有引用的对象,遍历整个堆内存,把未标记的对象清楚(老年代)
4. 复制(Copying):把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中(年轻代)
5. 标记-整理(Mark-Compact) :第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象 “压缩”到堆的其中一块,按顺序排放(老年代)
6. 分代(Generational Collecting)
首先我们得先清除JVM堆内存的结构(如下图),可以看到一共有3个区域:年轻代(Young Generation)、老年代(Old Generation)、永久代(Permanent Generation,也就是方法区)
年轻代(Young Generation)
包括一个Eden区和2个Survivor 区(From 和 to ),绝大多数的对象刚创建时都被分配在Eden区(Eden区是连续的内存空间,因此在其上分配内存极快),当第一次Eden区空间满了的时候进行第一 次Minor GC:回收掉“消亡”的对象,存活的对象复制到From内存 中(To内存此时是空的),Eden区域为空;下次Eden满的时候,又会触发Minor GC,回收掉消亡对象,存活的对象还是复制到From内存 中,当From内存满了的时候,GC会把From中还存活的对象复制到To内存中吗,清空From,这就确保了From和To永远至少有一个区是空的;重复以上步骤依然存活的对象会被默认放到老年代中。在年轻 代发生的GC只有Minor GC,发生的频率比较高(不一定是Eden满的时候)
老年代(Old Generation)
存放经历N次Minor GC依然存活的对象,当老年代的内存空间满的时候会触发Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高
永久代(Permanent Generation也就是方法区)
用于存放静态文件,如Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运 行过程中新增的类
总结的一些常见问题:
Q:JVM GC回收哪个区域内的垃圾?
JVM GC只回收堆区和方法区内的对象。而栈区的数据,在超出作用域后会被JVM自动释放掉,所以其不在JVM GC的管理范围内
Q:JVM GC怎么判断对象可以被回收了?
对象没有引用
作用域发生未捕获异常
程序在作用域正常执行完毕
程序执行了System.exit()
程序发生意外终止(被杀线程等)
PS:上面那个是网上的答案,我个人觉得应该回答从GC root搜索不到,而且经过第一次标记、清理后,仍然没有复活的对象被回收更加合理
Q:GC策略都有哪些分类?
引用计数:难解决对象之间相互引用的问题
可达性分析算法:基本所有GC算法都引用根搜索算法这种概念
标记-清除 :会有内存碎片
复制:是对象的存活率非常低
标记-整理(Mark-Compact)缺点:成本大
JVM为了优化内存的回收,使用了分代回收的方式,对于新生代内存的回收(Minor GC)主要采用复制算法。而对于老年代的回收(Major GC),大多采用标记-整理算法
Q:什么时候一个对象会被GC ?
把minor GC和Full GC介绍下即可
Q:如果老年代的对象需要引用新生代的对象,会发生什么呢?
为了解决这个问题,老年代中存在一个 card table ,它是一个512byte大小的块。所有老年代的对象指向新生代对象的引用都会被记录在这个表中。当针对新生代执行GC的时候,只需要查询 card table 来决 定是否可以被回收,而不用查询整个老年代。这个 card table 由一个write barrier 来管理。write barrier给GC带来了很大的性能提升,虽然由此可能带来一些开销,但完全是值得的。
总结:其实GC只要了解了三个问题就行 “什么时候”,“对什么东西”,‘“做了什么”