Java中的垃圾回收(GC)
垃圾回收,英文名Garbage Collection(GC).
Java进程在启动后,会创建垃圾回收线程,来对内存中无用的对象进行回收.
目录
1. 那么问题来了,什么是垃圾?
Java进程运行后,如果某个类型(方法区中的类信息,堆中的类对象),常量(常量池),对象(堆),如果不可用,称为垃圾.
2. 什么是垃圾回收?
Java进程启动后,gc垃圾回收守护线程会回收以上的垃圾对象.
3. 什么时候回收垃圾?
创建对象时,需要在对应的内存区域分配内存空间,如果该区域不足,就触发该区域的gc.(侠客莫急,对应能回收的内存区域都有哪些,稍后介绍)
System.gc(); //只是建议jvm进行垃圾回收,不一定执行.
4. 判断是否为垃圾的算法
4.1 引用计数法
给对象增加一个引用计数器,每当有一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任何时刻计数器为0的对象就是不能再被使用的,即对象已"死"。
主流的jvm中,没有使用这种算法,因为无法解决循环引用的问题,如下图所示.
4.2 可达性分析算法
此算法的核心思想为 : 通过一系列称为"GC Roots"的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称之为"引用链",当一个对象到GC Roots没有任何的引用链相连时(从GC Roots到这个对象不可达)时,证明此对象是不可用的.
其中,GC Roots指的是: 当前时间点,线程运行某个方法,在方法栈帧中持有的引用对象.
以下图为例:
对象Object5-Object7之间虽然彼此还有关联,但是它们到GC Roots是不可达的,因此他们会被判定为可回收对象。
了解四种引用类型:
- 强引用 : 普通的new对象,都是强引用,只要是可达的,任何情况下都不能回收.
- 软引用 : 即使是可达的,如果要发生该区域的内存溢出(OOM),就会回收.
- 弱引用 : 即使是可大的,如果该区域发生gc,就会回收.
- 虚引用 : 任何情况下,都无法通过虚引用获取到对象.目的只是在该对象被回收时,通知jvm.
5. 回收的区域
不用回收的区域: 程序计数器;虚拟机栈(方法执行就是入栈,方法返回就是出栈,会自动清除栈帧内存,所以没有gc);回收的区域如下:
5.1 方法区的gc
频率非常低的回收区域,由于回收的条件非常苛刻,所以效率也低,在jdk1.7叫方法区,jdk1.8叫元空间.
方法区是设计层面的叫法,在具体实现,gc垃圾回收中,称为永久代.
5.2 堆的gc
频率非常高的回收区域.方法中new对象,方法返回,栈帧就被清除了,也就意味着堆中new出来的对象没有任何引用指向它,就可以回收.
堆在gc时,进一步被划分为以下的内存:
(1) 年轻代的gc: 又称为Yong GC,Minor GC (次要的gc,对用户线程的影响来说,是次要) ;回收特点: 非常频繁,回收效率也非常高.
(2) 老年代的gc: 又称为Old GC,major GC(主要的gc,是说对用户线程的影响是主要的,较大); 回收特点: 一般回收速度比Minor GC慢10倍以上.
(3) 补充Full GC: 不同的场景下,可能指老年代gc,也可能是全堆gc,也可能是用户线程暂停的gc.
eden伊甸区,西方文化中的亚当和夏娃在伊甸园偷吃了禁果,于是创造了人类.
6. 垃圾回收算法
JVM是使用垃圾回收线程来执行垃圾回收工作.
基于垃圾回收器执行垃圾回收工作,回收不可达对象.每一种垃圾回收器使用的回收算法可能不同,还可能有多种,依据垃圾回收算法执行垃圾回收工作.
6.1 标记清除算法(老年代的回收算法)
(1) 分为两个阶段
- 标记: 标记不可达对象
- 清除: 清理垃圾
(2) 有两个缺陷
- 标记和清除两个阶段效率都不高. 类似本地删文件,遍历某个文件夹,遍历10万个文件,标记1000个垃圾文件,再遍历1000个垃圾文件并删除.
- 回收后存在内存碎片. 意味着在该区域创建大对象时,需要分配一块连续的空间,可能就不够.
6.2 复制算法(新生代的回收算法)
将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另一块上面,然后再把已经使用过的内存区域一次清理掉。
(1) 优点:
- 效率高,对象朝生夕死,存活的对象不多,复制也很快,清理也很快.
- 没有内存碎片的问题.
(2) 缺陷:
- 空间利用率不高,只有50%.
6.3 标记整理算法(老年代的回收算法)
复制收集算法在对象存活率较高时会进行比较多的复制操作,效率会变低。因此在老年代一般不能使用复制算法.
优点:
- 存活对象较多的时候,移动效率也比较高.
- 没有内存碎片问题.
6.4 分代收集算法
当前JVM垃圾收集都采用的是"分代收集(Generational Collection)"算法,这个算法并没有新思想,只是根据对象存活周期的不同将内存划分为几块。
具体就是以上划分GC堆为:
(1) 新生代
- Eden区
- From Survivor(S0)
- To Survivor(S1)
(2) 老年代
- 当次gc时,使用的空间为Eden+S0,gc完成后,S1还保留存活对象,程序继续执行,创建的对象存放在Eden区,如果创建对象时,Eden区空间不足,又会触发新生代的gc:重复以上步骤.
7. GC回收策略
(1) 对象优先分配在Eden区。
(2) 长期存活的对象进入老年代。新生代Minor GC后,存活在S区空间的对象,年龄+1,超过年龄阈值(默认15),就晋升到老年代。
(3) 大对象直接进入老年代,有设置JVM大对象的参数,超过大对象的阈值,直接进入老年代。目的:新生代空间相对老年代小很多,新生代使用复制算法,如果创建的大对象保存在新生代,复制起来效率低。
(4) 空间分配担保机制。
(5) 动态对象年龄判断,新生代gc时,存活对象中,某个年龄的对象总和大于S区空间/2,把大于等于该年龄的对象,存放在老年代。
作者:一颗苹果.
来源链接:https://blog.csdn.net/weixin_43939602/article/details/117948764