• JVM学习——垃圾回收GC(学习过程)


    JVM学习-垃圾回收(GC)

    2020年02月19日06:03:56,开始学习垃圾回收,学习资料来源(张龙老师的JVM课程)

    JVM内存数据区域知识复习

    学习垃圾回收之前,要对JVM内部的内存区域有详细的了解。先对相关内存区域的内容知识进行复习和总结,为垃圾回收的学习奠定基础。

    image-20200219060429726

    灰色区域是线程共享

    白色区域为线程隔离

    image-20200219064036596

    image-20200219064120088

    image-20200219064229291

    引用占用4个字节,空对象占用8个字节。

    方法执行结束后,对应的栈Stack中的变量马上回收,但是Heap中的对象要等到GC来回收。

    JVM垃圾回收(Garbage Collection)

    理论知识

    JVM垃圾回收(GC)模型

    1. 垃圾判断算法
    2. GC算法
    3. 垃圾回收器的实现和选择

    垃圾判断算法

    1. 引用计数算法(Reference Counting)
    2. 根搜索算法 (GC Roots Tracing)

    引用计数算法

    image-20200219064625169

    对象循环引用的问题结束: A引用B,B又引用A。就算没有外部引用。但是又状态一直为1,这样的就回收不了了。

    image-20200219064807017

    当然,还是可以通过其他手段来规避他的这个缺点的。

    根搜索算法

    ![image-20200219070328494](/Users/shangyifeng/Library/Application Support/typora-user-images/image-20200219070328494.png)

    image-20200219070437201

    方法区

    image-20200219070745942

    垃圾回收(GC)算法

    1. 标记-清除算法(Mark-Sweep)
    2. 标记-整理算法(Mark-compact)
    3. 复制算法(Copying)
    4. 分代算法(Generational):几乎所有虚拟机都会采用这个算法

    为什么复制算法在新生代用的多呢?因为java大多是的java对象都是招生熄灭的。可被回收的并不多。老年代的生命周期较长,所以不方便使用复制算法。

    所以老年代采用标记清楚和标记整理算法

    标记-清楚算法(Mark-Sweep)

    image-20200219071343749

    标记-清楚算法:的执行原理图如下所示

    如有以下这些对象

    image-20200219072023671

    Runtime Stack 虚拟机栈是根搜索算法的入口

    image-20200219072122102

    先标记

    image-20200219072157821

    F - J 之间有循环引用。 但是 D-G -F-J-M 都是需要被回收掉的。绿色的都是还要 保留的。

    流程执行完之后就是如下所示

    再清除

    image-20200219072245947

    image-20200219072301392

    复制(Copying)搜集算法

    image-20200219072438309

    实现简单,运行高效:但是:需要将内存缩小为原来的一般,代价高昂。

    image-20200219072606909

    现在主要用来回收 新生代

    image-20200219073336674

    在老年代中,一般不能直接选用这种算法。(因为老年代的存活率高 )

    复制搜集算法的示意图

    1. 把A- C=复制

    image-20200219073521882

    把相互引用的都给复制

    image-20200219073711606

    余下的没有被复制,会被连锅端掉

    image-20200219073746128

    复制搜集算法优点

    image-20200219073803923

    标记-整理算法

    image-20200219073918760

    如下为执行示意图

    标记移动。腾出来连续的空间。没有内存碎片。就是有点慢。

    image-20200219073959046

    image-20200219074030501

    分代收集算法(Generational Collecting)

    image-20200219074425744

    新生代 和 老年代 的定义

    新生代:部分刚新生的对象,没有用的

    老年代:经历多次垃圾回收,还会保留

    image-20200219074816247

    JVM6时候的参考(现在已经没有永久代了)

    image-20200219074836178

    image-20200219075017841

    为什么说只会浪费百分之十呢,这里二娃给我讲述的

    这次 设置了2个Survivor是非常巧妙的。第一次的时候,8+1 被使用。复制完之后。放入另外一个Survivor。然后下一次的时候。8+另外一个1,把对象复制到1上。所以就完成了,轮流替换。每次被闲置的就会只有一个占用百分之10的Survivor来准备接受要复制的对象。over。

    image-20200219075635735

    JVM启动的时候可以根据程序的需要,选择一定的需求所对应的垃圾收集器和对应的垃圾收集算法。

    image-20200219075742489

    内存相关概念

    内存结构

    image-20200219075826778

    内存分配

    image-20200219080003520

    内存回收

    image-20200219080025676

    强引用,软引用,弱引用,虚引用

    image-20200219080154476

    软引用:内存不够的时候,一会被会GC,长期不用也会被收集

    垃圾收集算法

    image-20200219080244760

    黄色区域应用在年轻代

    灰色区域应用在老年代

    GC触发的时机

    image-20200219080326960

    尽量避免对Full GC的使用,因为全局GC调用的时候,会停滞所有的义务代码执行。

    MinorGC是一定会有的。

    垃圾回收器

    垃圾回收器理论分析

    没有万能的垃圾回收器,交互性和吞吐率是不兼容的,没有银弹

    image-20200219080629770

    垃圾收集器的“并行”和“并发”

    image-20200219081106164

    让用户线程处于等待状态是一个很恐怖的事情。

    收集器

    Serial收集器

    STW(Stop The World) 暂停所有的工作线程

    虚拟机运行Client模式下的默认收集器

    image-20200219081155185

    image-20200219081227092

    单线程的GC。

    ParNew收集器

    Serial的多线程版本。其他都一样

    image-20200219081416624

    image-20200219081532679

    Parallel Scavenge收集器

    image-20200219081637673

    允许较长较长时间的STW。

    Serial Old收集器

    image-20200219081722260

    Parallel Old收集器

    image-20200219081731864

    image-20200219081742646

    CMS收集器:实现非常复杂的收集器

    image-20200219094602469

    image-20200219094637936

    image-20200219094655006

    image-20200219094720256

    image-20200219094735395

    综上,PPT 都需要自己详细读读。

    再次回顾一下JVM的内存数据区域的分配情况

    image-20200219092857424

    1. Oracle的HotSpot虚拟机,将 java虚拟机方法栈和本地方法栈放在同一个地方
    2. 程序计数器是唯一一个没有要求说必须要使用GC垃圾回收的地方。
    3. 白色的地方是线程独有的

    总结

    • 垃圾判断算法

      • 引用计数算法
      • 根搜索算法
    • 垃圾回收GC算法(常见的)

      • 标记-清除算法
      • 标记-整理算法
      • 复制算法(8-1-1)
      • 分代算法(综合前面几种,针对不同的类型,采用不同的算法)
    • 垃圾收集器

      • Serial
      • ParNew
      • Parallel Scavenger (PS (jdk1.8默认的新生代垃圾收集器))
      • Parallel Old(ParOld jdk1.8默认的老年代垃圾收集器)
      • Serial Old
      • CMS

    Java内存泄露的经典原因

    1. 对象定义在错误的范围(Wrong Scope)
    2. 异常(Exception)处理不当
    3. 集合数据管理不当

    对象定义在错误的范围

    image-20200219095147567

    从成员变量 移动到 局部变量。 (布局就不一样了。变量会被及时回收)

    异常处理不当

    image-20200219095332632

    异常处理的不到位,关闭流的操作,应该放置在finally里面。

    image-20200219095408581

    集合数据管理不当

    image-20200219095427055

    垃圾回收日志(GC Log)

    上案例:

    image-20200219100059547

    这里需要注意点是:数组是原生数据类型。 数组里面的默认值都是0。如果是引用类型的数组,里面的值都为null。

    程序初始:

    平淡无奇

    image-20200219100226243

    输出结果:平淡无奇:

    image-20200219100816679

    接下来添加一定的JVM参数,进行启动,就变得大不一样

    JVM参数加 -verbise:gc : 添加垃圾回收GC的详细信息

    JVM参数加 Xms20M Xmx20m:分别表示堆容量的初始值和最大值,一般都设置为一样。固定位20M

    JVM参数加-XMn10M:表示新生代的容量大小为10M

    JVM参数加-XX:+PrintGCDetails:打印GC的详细信息

    JVM参数加-XX:SurvivorRatio=8:表示Even空间和Survivor的比例是8:1:1

    image-20200219100657109

    运行结果:只有GC详情,但是未执行GC

    这东西都是:-XX:+PrintGCDetails 打印的,还没有展现GC、

    在程序中,再加入一行 代码,再加一个数组(大小就到达GC临界点)

    image-20200219101210887

    运行结果:GC就出来了

    image-20200219101121573

    image-20200219101313534

    随着程序的变化,内存空间的修改。会导致不同的打印结果出现。

    GC:表示 minor GC ,后面数据的含义和格式

    ​ Allocation Failure:分配失败

    ​ PSYoungGen:年轻代 PS(Parallel Scavenger 收集器)。

    ​ 执行前-执行后(总容量):(7694k->624k(9216k))

    ​ 执行GC之前堆的大小-执行后的存活对象的大小(堆的大小):(5656k->4728k(19456k))

    image-20200219102735659

    4104k对应的,正好是老年代新增的 大小。(从新生代释放出来,进入老年代)

    Full GC 会导致程序暂停 (2,2,2,2 会出现Full GC, 2,2,3,3 反而不会进入Full GC)

    没进入Full GC的原因:当新生代已经无法容纳要生成的对象的时候,直接放入老年代

    验证的概念如下:GC的触发时机

    image-20200219101421689

    对于网上的文章或者概念,还有书本等等。里面的内容都是不确定是对的,唯一能验证概念的,就是通过代码,通过数据去看结论。

    JVM参数

    image-20200219094720256

    image-20200219094735395

    java -XX:+PrintCommandLineFlags -version :输出启动参数和java版本

    image-20200219110007233

    -XX:+UseParallelGC : 默认使用的GC是 Parallel

    -XX:PretenureSizeThreshold=4194304:大小阈值设置,设置超过多大值时直接在老年代进行分配。

    超过这个值之后,就不会在新生代去创建,直接进行入老年代

    设置好上述参数,然后执行下面程序:

    image-20200219112038982

    但是:并不是上面的参数结论的作用。所以,一般结论正确与否,还是需要通过代码来确定正确性。

    是因为:上述概念是需要使用 Serial 垃圾回收器。

    所以再加一个参数:-XX:+UseSerialGC设置项目启动之后使用的GC是Serial垃圾回收器

    image-20200219113314931

    老年代占用5M:直接就是程序定义的对象的大小:说明直接进入老年代。

    使用可视化工具查看垃圾回收

    image-20200219114409701

    这里可视化工具提供的手动按钮是 Full GC (System.gc())

    image-20200219114515452

    image-20200219114528973

    关于System.gc()的补充,我们手动调用gc或者自己执行gc方法的时候,我们只是告诉了JVM需要执行垃圾回收,但是至于什么时候回收,是由JVM决定的。

    如果我们不告诉JVM需要GC的话,只有在创建新的对象的时候才有可能去校验是否需要垃圾回收。因为只有在创建对象的时候会导致堆内存的增加。

    这就是System.get()方法存在的意义和价值:在没有对象创建的时候触发垃圾回收。

    jmc查看的可视化

    image-20200219114902217

    image-20200219115251845

    FromSurvivor和ToSurvivor的角色的切换

    如果频繁切换,那么会通过一定的算法转移到老年代。

    -XX:MaxTenuringThreshold=5:在可以自动调节对象晋升(Promote)到老年代阈值的GC中设置该阈值的最大值。

    设置可以晋升到老年代的最大存活年龄。该参数的默认值为15,CMS中默认值为6,G1中默认为15(在JVM汇总,该数值是由4个bit来表示的,所以最大值为1111,即15)

    -XX:+PrintTenuringDistribution:打印出年龄为n的字节对象

    经历了多次GC后,存货的对象会在FromSurvivor和ToSurvivor的角色之间来回切换。而这里面的一个前提则是这两个空间有足够的大小来存放这些数据,在这些算法中。会计算每个对象年龄的大小,如果达到某个年龄后发现总大小已经大于了Survivor空间的50%,那么这时候就需要调整阈值,不能再继续等到默认的15此GC后才完成晋升,因为这样会导致Survivor空间不足,所以需要调整阈值,让这些存货对象尽快完成晋升。

    Survivor空间不足是很严重的。

    image-20200219121330920

    image-20200219121345626

    运行结果:

    image-20200219121422183

    threshold的动态调整

    image-20200219130642857

    image-20200219130751220

    这个时候运行,没有问题。

    接下来添加JVM参数

    XX:TargetSurviorRatio=60 :占据百分之60,会重新计算阈值

    XX:+PrintGCDateStamps:打印JVM运行的时间戳

    XX:+UseConcMarkSweepGC: 老年代使用CMS

    XX:+UserParNewGC:新生代使用ParNew

    XX:MaxTenuringThreshold=3:最大年龄为3,就得去老年代了

    image-20200219131136864

    运行结果

    image-20200219131207830

    image-20200219132004058

    image-20200219132049706

    这里,下面的图,只有age=1的,说明,age=1,=2,=3的已经被全部放入老年代当中。

    image-20200219131906826

    枚举根节点

    image-20200219154558522

    OopMap数据结构

    安全点: Safepoint

    image-20200219154756567

    安全点:程序执行并非在所有地方都能停顿下来开始GC,只有在达到安全点时才能暂停。

    image-20200219154809669

    image-20200219155750717

    抢断式中断:JVM中断

    主动式中断:轮询标志的地方和安全点是重合的。现在自己中断自己。

    image-20200219160034945

    安全区域

    image-20200219160331233

    image-20200219161026460

    Safe Region

    CMS垃圾回收器详解

    将CMS垃圾回收器学完之后,需要大力度的学习一下G1垃圾回收器。

    典故:CMS研究的人离职了,新来的人怎么也看不懂C++的源码。

    CMS:Concurrent Mark Sweep 并发标记清除

    image-20200219162027113

    并发的含义:垃圾收集的线程可以和用户的业务线程同时执行。

    标记和清除:说明CMS是采用标记和清除来完成的。

    标记:标记的是哪些对象依然是属于存活状态。哪些是需要清除的对象。

    清除:清除掉垃圾的对象。

    image-20200219162510850

    初始标记:

    并发标记:

    重新标记:为了修正并发期间因用户线程继续运作而导致的变动的部分。

    并发清除:

    CMS收集器运作步骤:

    image-20200219162739106

    初始标记:STW,没有用户线程

    并发标记:可以和用户线程同时执行

    重新标记:STW,没有用户标记

    并发清理:可以和用户线程同时执行

    重置线程:恢复到初始状态

    CMS收集器的优点和缺点

    优点:

    image-20200219163126923

    停顿:指的是用户线程停顿

    缺点:

    image-20200219163151757

    空间分配担保:新生代容纳不了,直接扔给老年代,这就叫老年代担保。

    image-20200219163505962

    CMS收集器收集步骤:

    image-20200219163917024

    不带Concurrent,会导致用户线程暂停

    image-20200219164301754

    image-20200219164418340

    image-20200219165516791

    image-20200219165709071

    image-20200219165754883

    image-20200219165840708

    到此:标记完成。接下来调用清理算法。

    image-20200219165907894

    image-20200219165926941

    image-20200219165958257

    image-20200219170028823

    用程序去认识CMS垃圾收集器

    程序代码

    image-20200219174208582

    虚拟机参数

    image-20200219174226957

    运行结果:

    image-20200219174525991

  • 相关阅读:
    python中xrange和range的异同
    Python:使用threading模块实现多线程编程
    python Queue模块
    Python中pass语句的作用
    Python的作用域
    eclipse颜色配置
    protobuf
    python调试总结
    chardet安装
    Windows下搭建PHP开发环境
  • 原文地址:https://www.cnblogs.com/bigbaby/p/12348968.html
Copyright © 2020-2023  润新知