JVM之GC的简单理解

JVM之GC

引用的分类
  1. 强引用

    被强引用关联的对象不会被强制回收

    比如 new 一个对象

    Object ob = new Object()
    
  2. 软引用

    被软引用关联的对象只有在内存不够的时候才会去执行一次GC,进行回收,若是回收之后,内存还不够,就会爆出OutOfMermoy的错误

  3. 弱引用

    只要进行垃圾回收,就会被回收

  4. 虚引用

    无法从虚引用得到对象,设置的唯一目的就是在对象回收的时候得到一个系统的通知

如何判断对象可以被GC
引用计数法

给每一个对象添加一个引用计数器,这个对象每被调用一次,引用计数+1,调用完毕,引用计数-1,当引用计数为0的时候我们就可以执行将这个对象垃圾回收

但是可能出现的问题就是两个对象永远互相引用,引用计数永远为1,不能回收对象,

public class A {
    public B b;
}

public class B {
    public A a;
}

public calss Main {
    public static void main(string[] args) {
        A a1 = new A();
        B b1 = new B();
        A = B.a;
        B = A.b;
    }
}
可达性分析法

每次回收垃圾的时候,我们以GCRoots为起点,可达的对象都是存活的,不可达对象被回收。

下图来看,我们要回收的就是O4

在这里插入图片描述

GC Roots中对象又一般包含四种对象

  1. 虚拟机栈中局部变量表中引用的对象

  2. 本地方法栈引用的对象

  3. 方法区中类静态属性引用的对象

  4. 方法区中常量引用的对象

我们可以用链表来想象一下可达性分析,将头节点的next置为null,头节点可达,头节点后面的结点不可达,那木GC就会回收到头节点后面的结点。如果将头节点直接置为null,那木若没有对象再次引用这个头节点,在下次GC的时候一般都会被回收。

finalize()

当一个对象调用这个finalize()方法的时候,那木就有可能在该方法中让对象重新被引用,而实现自救。自救只能进行一次,如果被回收的对象之前调用过finalize()方法,那木就不会执行这个方法。

简单来讲,就是这个对象快被死了,但是他不想死,于是用这个方法,但是这个方法有可能把它救了,又有可能把它救不了。并且如果被救了之后,再次快死的时候,只能去死了,而不能去执行这个方法了。

垃圾回收的几种方式
1. 标记复制(新生代)

简单来说就是将一块内存区域一分为二,然后一份用来存储被执行的对象,进行垃圾回收的时候,我们直接遍历,标记存活的对象,将其复制一份到另一块内存区域,然后直接清除掉这块区域的全部内容。

这样的的方法垃圾回收的速度是比较快的,但是它对空间的利用率只用50%。

2. 标记清除(老年代)

遍历我们的内存区域,检查我们的对象是否为可达对象,将不可达对象则添加标记,在回收的过程中,并且回收掉垃圾对象。并且在回收的过程中还会判断回收后的分块是否和前后的空闲区域是否连续,若是连续,会合并这两个分块。

这种方法速度较慢,并且会存在内存碎片,导致大对象无法分配空间。

3. 标记整理(老年代)

肯定是先标记存活的对象,然后将存活的对象向一端移动,直接清除到端边界以外的内容。和标记清除的方法

不会产生内存碎片,移动大量对象,速度更慢。

虚拟机hotspot垃圾收集算法之分代收集算法
老年代和新生代以及永久代

在这里插入图片描述

  1. Young Generation,新生代,垃圾回收算法被称为Minor GC。里面又分为伊甸区(eden)和From Survivor和To Survivor区域。内存空间的分配区间比位 e : s : s = 8 : 1 : 1。采用的是复制标记算法来清理不可达对象。

    因为java对象具有朝生夕灭的特点,所以新生代中的MinorGC非常频繁。在某个方法中,创建一个对象引用一个对象,用完之后就无用了,朝生夕灭。

  2. Old Generation,老年代,垃圾回收算法被称为Major GC。这种算法的速度一般比MinorGC慢十倍

  3. Permanent 是对方法区的GC一般被称为永久代。而前两者都在堆上。而在元空间上进行的GC的对象一般是废弃的常量和无用的类。性价比较低。

新生代对象如何进入老年代
  1. 大对象直接进入老年代,就是这个对象所需的空间太大,新生代无法提供足够的空间,直接被分配到老年代了。(比如很长的字符串或者数组)

  2. 长期存活的对象,进入老年代,新生代中的对象如果经过15次以上的minorgc就会进入到老年代中,每经过一次MinorGC,年龄+1。

  3. 在Survivor空间中相同年龄的对象总和大于Survivor的一半,那木年龄大于或等于该对象的就可以直接进入老年代。无需等到15岁。

  4. 空间分配担保

    发生Minor GC时,是使用复制算法将Eden区和Survivor区存活对象复制到另一个Survivor区:

    Survivor区只占新生代10%空间,我们没有办法保证每次回收都只有不多于10%的对象存 活,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保(Handle Promotion)。

    内存的分配担保就好比我们去银行借款,如果我们信誉很好,在大多数的情况下都能按时偿还,于是银行可能会默认我们下一次也能按时按量地偿还贷款,只需要有一个担保人能保证。 如果我不能还款时,可以从他的账户扣钱,那银行就认为没有风险了。

    内存的分配担保也一样,如果另外一块Survivor空间没有足够空间存放上一次新生代收集下 来的存活对象时,这些对象将直接通过分配担保机制进入老年代

垃圾回收产生的影响
  1. STW, 垃圾回收工作是在垃圾回收线程中执行的,而在执行垃圾回收工作的某一部分工作的时候,我们需要暂停用户线程而去进行垃圾回收工作,也就是(STOP THE WORLD)STW。停止了世界,停止了全部的用户线程,停止了程序。
  2. 而停止程序并不是在哪里都可以停止的,必须在安全点才可以停止线程。在类加载的时候,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用。这些特定的位置就是“安全点”
  3. 抢断式中断 和 主动式中断
    • 抢断式中断 : GC的时候,直接停止全部线程,若是发现线程中断的位置未在安全点上,则恢复线程,让线程执行到安全点上。
    • 主动式中断: 需要GC的时候,设置一个标志,各线程执行时循环轮转查看这个标志,发现这个中断标志为真的时候,
用户体验(STW)和吞吐量
  1. 用户体验即使 需要短暂的STW,就是每一次暂停用户线程的时间要短。

  2. 吞吐量CPU用于运行用户代码的时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间 /(运行用户代码时间+垃圾收集时间),虚拟机总共运行了100分钟,其中垃圾收集花掉1分钟,那吞吐量就是99%.

    高的吞吐量必然会导致用户体验降低,而用户体验提高就会导致吞吐量的降低。

垃圾收集器
1. Serial收集器 (新生代收集器)

单线程,单核 标记复制算法 暂停用户线程 (STW)

2.ParNew收集器 (新生代收集器)

多线程 标记复制算法 暂停用户线程 (STW)

3.Paraller Scavenge收集器 (新生代收集器)

多线程 标记复制算法 可控制的吞吐量

这个可以用户线程和GC线程同时工作

4.Serial Old 收集器 (老年代收集器)

单线程 单核 标记整理算法 暂停用户线程(STW)

5.Paraller Old收集器 (老年代收集器)

多线程 标记整理算法 可控制的吞吐量

用户线程和GC线程可以同时工作

6.CMS 收集器 ( 老年代收集器)

用户体验优先 追求最短停止时间的

标记-清除算法

CMS收集器执行垃圾收集的四个步骤

  1. 初始标记

    (STW) 仅仅标记GCROOTS直接关联到的对象,速度快,标记的对象是可达对象,也就是存活对象。

  2. 并发标记

    (用户线程和GC线程同时执行)标记每一条链上面的可达的对象,也就是遍历老年代的所有区域,标记可达对象。但是又不可能标记出老年代所有的可达对象,因为是并发运行的,在运行期间会发生新生代的对象晋升到老年代、或者是直接在老年代分配大对象、或者更新老年代对象的引用关系等,对于这些对象,都是需要进行重新标记的,否则有些对象就会被遗漏,发生漏标的情况。

  3. 重新标记

    (STW)是为了修正并发标记期间因为用户线程继续运行而导致标记产生变动的那一部分对象的标记。比并发标记的时间短,比初始标记时间长。

  4. 并发清除

    (用户线程和GC线程同时执行)清除掉不可达对象

CMS收集器的缺点

  1. 第四步并发清除的时候同样会产生第二步并发标记产生的问题,也就是堆内的有些可达对象可能会变成不可达对象,而未被清除。就产生了“浮动垃圾问题”。这些浮动垃圾就可能只能放到下一次的CMS去集中处理他们。因为并发清除阶段会产生浮动垃圾,所以我们不能在老年代的空间快满的时候去处理垃圾,我们必须存放一部分余留空间去存储浮动垃圾,一旦没有足够的空间去处理浮动垃圾,我们就必须使用Serial Old收集器然后STW去收集垃圾。
  2. 用户体验优先就可能导致了低吞吐量的问题,CMS会抢占CPU资源。并发阶段虽然不会导致用户线程暂停,但却需要CPU分出精力去执行多条垃圾收集线程,从而使得用户线程的执行速度下降。
  3. CMS收集器收集垃圾会导致大量的内存碎片,一旦若是又大对象进入老年代,而没有足够的连续空间,就会进行依次Full GC。
7. G1收集器(新生代 + 老年代收集器)

整体 : 标记整理算法 局部标记复制算法

用户体验优先 几乎不会发生STW
在这里插入图片描述

  • 把整个堆空间分成了region块,然后进行垃圾回收
  • 在进行垃圾回收的过程中基本不会STW
  • 每一个region块可能是Eden space,也可能是Survivor space,还可能使Old Generation

垃圾回收的过程

  • 新生代

    复制算法 把Eden和Survivor复制到另一个Survivor区

  • 老年代

    1. 初始标记

      STW 遍历GCROOTS直接关联的对象 但是是和MinorGC一起完成,并不需要去单独完成,在G1进行MinorGC的时候,就已经进行了老年代的初始标记

    2. 并发标记

      和CMS的第二步没有区别, 但是多可一步:若是发现那个O块中的存活对象概率很小或者基本没有存活对象,就直接回收了这一块内存区域。

    3. 最终标记

      和CMS中做的一样,只不过算法不一样。

    4. 筛选回收

      在这个阶段中,G1会挑选出那些对象存活率低的region进行回收, 将其中存活对象进行复制到另一块region区域,然后直接进行垃圾回收,相当于标记复制。

简单的查看JVM的监控工具
-查看虚拟机内所有的HotSpot虚拟机进程
jps

- 生成内存存储快照 生成headdump文件
jmap -heap 660
jmap -dump:format=b,file=t.txt 660
- 一般和jmap生成的文件一起用
jhat

- 查看虚拟机的线程快照 定位线程的问题 如死锁之类的
jstack -f pid

- 显示虚拟机啊的配置信息
jinfo
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值