垃圾回收机制(GC)
1.GC是什么(GC概述)
垃圾收集通常呗称为GC,由虚拟机自动化完成垃圾回收工作。虽然他会自动回收,但是当我们需要排查
各种内存溢出,内存泄漏问题时,当垃圾成为系统达到更高并发量的瓶颈时,我们就需要对GC的自动回收
实施必要的监控和调节。
2.要回收哪些区域
在JVM内存模型中,有三个是不需要进行垃圾回收的:程序计数器、JVM栈、本地方法栈。因为他们的生命周期
和线程同步的,随着线程的销毁,他们占用的内存会自动释放,所以只有方法区和堆需要进行GC
3.如何判断对象是否存活
判断对象常规有两种方法:引用计数算法和可达性分析算法
1.引用技术算法:给对象添加一个引用计数器,每当有一个地方引用他计数器加1,引用释放时计数器减1,
当计数器为0时可以回收。 引用计数算法实现简单,判断高效,在微软COM和Python语言等被广泛使用,
但在主流的Java虚拟机中没有使用该方法,主要是因为无法解决对象相互循环引用的问题。
2.可达性分析算法:基本思想是通过一系列称为GC Root 的对象(如系统类加载器、栈中的对象、处于激活
状态的线程等)作为起点,基于对象引用关系,开始向下搜索,所走过的路径称为引用链,当一个对象
到GC root没有任何引用链相连接,证明对象是不可用的。
说说Java GC都用来哪些算法?分别应用在什么地方?
1.标记清除算法
标记清楚算法,包含"标记"和"清除两个阶段":首先标记出所有需要回收的对象,在标记完成后统一回收
掉所有被标记的对象。
标记清除算法是最基础的收集算法,后续的收集算法都是基于该思路并对其缺点进行更改而得到的。
主要缺点:一个是效率问题,标记和清除过程的效率都不高;另外是空间问题,标记清除只有会产生
大量不连续的内存碎片,,空间碎片太多可能会导致,当程序在以后的运行过程中需要分配较大对象时
无法找到足够的连续内存而不得不提前触发另一次垃圾收集动作。
2.复制算法
将内存分成两块容量大小相等的区域,每次只使用其中一块,当这一块内存用完了,就将所有存活对象
复制到另一块内存空间,然后清除前一块内存空间。这样一来就不容易出现内存碎片的问题。
那么复制算法出现的问题在于,复制的代价较高所以适合新生代,因为新生代的对象存活率较低,
需要复制的对象较少, 需要双倍的内存空间,而且总是有一块内存空闲,浪费空间
3.标记——整理算法
在-完成标记之后,他不是直接清理可回收对象,而是将存活对象都向一端移动,然后清理掉端边界以外的内存。
不会产生内存碎片,但是依旧移动对象的成本。
4.分代收集算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法,他的核心思想是根据对象存活的生命周期将内存
划分为若干个不同的区域。一般情况下将堆区划分为老年代和新生代,在堆区之外还有一个代就是永久代
老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的
对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。
内存被分为三个区域: 新生代、老年代、永久代:方法区
5.新生代的回收算法
包含有Enden、form suricor space、 to survivor space三个区,绝大多数最新被创建的对象会被分配到这里
大部分对象在创建后边的很快不可达。
1.所有新生成的对象首先都是放在年轻代的。年轻代的目标就是尽可能快速的收集掉那些生命周期短的对象。
2. 新生代内存按照8:1:1的比例分为一个eden区和两个survivor(survivor0,survivor1)区。
一个Eden区,两个 Survivor区(一般而言)。大部分对象在Eden区中生成。回收时先将eden区存活对象
复制到一个survivor0区,然后清空eden区,当这个survivor0区也存放满了时,则将eden区
和survivor0区存活对象复制到另一个survivor1区,然后清空eden和这个survivor0区,
此时survivor0区是空的,然后将survivor0区和survivor1区交换,即保持survivor1区为空,如此往复。
3.当survivor1区不足以存放 eden和survivor0的存活对象时,就将存活对象直接存放到老年代。
若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。
4. 新生代发生的GC也叫做Minor GC,Minor GC发生频率比较高(不一定等Eden区满了才触发)。
6.老年代的回收算法
1.在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中,因此,可以认为年老代
中存放的都是一些生命周期较长的对象。
2.内存比新生代也大了很多(大概是1:2)当老年代内存满时触发Major Gc 即 Full GC,Minor
Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。
7.永久代的回收算法
用于存放静态文件,如java类,方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成
或调用一些.class。例如这个Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些
运行过程中新增的类。持久代也称方法区
常见的垃圾收集器
1.Serial收集器(复制算法)
新生代单线程收集器,标记和清理都是单线程,优点是简单高效。是cliient级别默认的GC
方式,可以通过-XX:+UseSerialGC来强制指定。
2.Serial Old收集器(标记-整理算法)
老年代单线程收集器,Serial收集器的老年代版本。
3.ParNew收集器(停止——复制算法)
新生代收集器,可以认为是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的
表现。
4.Parallel Scavenge收集器(停止-复制算法)
并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般为99%, 吞吐量= 用户线程时间/(用户
线程时间+GC线程时间)。适合后台应用等对交互相应要求不高的场景。是server级别默认采用的
GC方式,可用-XX:+UseParallelGC来强制指定,用-XX:ParallelGCThreads=4来指定线程数。
5.Parallel Old收集器(停止-复制算法)
Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量优先。
6.CMS(Concurrent Mark Sweep)收集器(标记-清理算法)
高并发、低停顿,追求最短GC回收停顿时间,cpu占用比较高,响应时间快,停顿时间短,多核cpu
追求高响应时间的选择。
7.GC是什么时候触发的(面试最常见的问题之一)
由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Minor GC和Full GC。
Minor GC:一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Minor GC,对Eden区域
C,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。
这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,
同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率
高的算法,使Eden去能尽快空闲出来。
Full GC :对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,
所以比Scavenge GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分
工作就是对于Full GC的调节。导致Full GC的原因分别是:年老代被写满,持久代被写满,System.gc()
被显示调用,上一次GC之后Heap的各域分配策略动态变化。
8.jvm查看gc命令
jstat -gc 12538 5000
即会每5秒一次显示进程号为12538的java进成的GC情况,
9.如果频繁老年代回收怎么分析解决(蚂蚁金服面试题)
老年代是存放那些在程序中经历了好几次回收仍然还活着或者特别大的对象(这个大就要看你是否设置了-XX:
PretenureSizeThreshold 参数了)。检查程序中是否有比较大的对象,或者这个参数设置是否合理。