美文网首页
白话JVM GC-GC算法简介

白话JVM GC-GC算法简介

作者: zh_harry | 来源:发表于2020-06-06 18:03 被阅读0次

    什么是JVM

    JVM是个程序,象是句屁话,对就是句屁话!但这句屁话揭示了真理!
    “算法+数据结构=程序”
    注:凭借一句话获得图灵奖的Pascal之父——Niklaus Wirth
    让他获得图灵奖的这句话就是他提出的著名公式。

    • 它有什么不一样?
      摘自维基百科
      编译语言(英语:Compiled language)是一种编程语言类型,通过编译器来实现。它不像解释型语言一样,由解释器将代码一句一句运行,而是以编译器,先将代码编译为机器代码,再加以运行。理论上,任何编程语言都可以是编译式,或直译式的。它们之间的区别,仅与程序的应用有关。

    许多人认为Java是一门解释执行的语言,由虚拟机解释执行class文件字节码。事实是Java是一门解释执行和编译执行并存的语言。JVM解释器让Java程序快速启动,编译器让Java程序高效运行,这是Java长久生存的一大重要原因。

    在一个Java程序执行时,首先通过javac把java文件编译为虚拟机可以识别的class文件。然后由JVM解释器解释class文件中的字节码,通过JVM把解释结果转变为机器码执行。这是我们通常所说的解释执行

    C++这类语言都是直接把代码编译为机器码执行交给计算机执行,这是我们通常所说的编译执行

    两者的最大区别就是是否存在一个中间处理器去参与由代码到机器码的流程。

    解释执行有个缺点就是每次都要解释字节码然后才生成机器码让计算机执行,解释的过程会占用很多的时间。于是JVM中诞生了编译器,编译器可以通过热点代码探测技术找到运行次数最多的代码,把这些代码及时编译为机器码,在下次调用这些代码时跳过解释的步骤,直接执行编译好的机器码,以达到加速运行时间的目的

    Java语言解释器与编译器并存的理念让Java程序得到了更加智能处理结果,是Java语言的特色,也是Java语言的优势。

    解释执行有个缺点就是每次都要解释字节码然后才生成机器码让计算机执行,解释的过程会占用很多的时间。于是JVM中诞生了编译器,编译器可以通过热点代码探测技术,找到运行次数最多的代码,把这些代码及时编译为机器码,在下次调用这些代码时跳过解释的步骤,直接执行编译好的机器码,以达到加速运行时间的目的。

    Java语言解释器与编译器并存的理念让Java程序得到了更加智能处理结果,是Java语言的特色,也是Java语言的优势。

    JVM crash 原因参考
    参数配置

    -XX:CompileCommand=exclude,com/mysql/jdbc/ConnectionImpl::execSQL 
    -XX:ErrorFile=./hs_err_pid<pid>.log
    

    core.43721 (gun debug) gbd
    https://www.jianshu.com/p/7652f931cafd

    https://www.cnblogs.com/jingmoxukong/p/5509196.html

    • 它没有什么不同

    即然是程序,就和其他的程序没有什么不同,依然可以通过数据结构和算法的方法论(空间和时间的平衡)进行研究分析!

    为什么需要GC

    GC即垃圾回收,讲个小故事!

    image.png

    在很久很久以前,我们的祖先不想在树上玩了,想下来做程序员。
    于是开始写代码,最初用C和C++写,因为也是第一次写,写了好多 bug,各种找不到对象


    image.png

    然后还被老板鄙视?写得什么玩意,一天天净装B

    image.png

    这哥们很郁闷,然后找原因,发现好多地方忘了free 内存了。而且这种事情好多地方都要手动调一次。就想如果这个事情有人帮我做了该多好啊,象我这种小白也能天天装B,还不被鄙视!

    许多年以后,上帝一般男人们出现了。talk is cheap, show me your code

    image.png 高斯林

    至此GC出现(具体GC不一定是这哥们发明的),找不到对象的问题得以解决!内存回收问题由GC帮忙解决,应用程序猿不需要手动free了!

    如何实现呢?

    应用程序员只关心具体业务,不必再关心内存的对象释放问题。对象释放由GC程序来实现。GC程序需要知道哪些对象还活着,哪些对象已经没有用了!那么问题来了,它如何知道对象是否还活着呢?


    image.png

    同样的一块内存,不同的程序(人)去读,结果是不一样的!!!!
    数据写到内存之后,便不知道其具体内容是什么了,如何解释呢?如何找到对象的引用呢?
    数据结构
    JVM,确切地说是HotSpot把这样描述对象关系的元数据结构的叫做OopMap,这家伙记录了所有对象的引用关系,GC程序就通过这哥们判断要把哪些僵死对象干掉!

    在HotSpot中,对象的类型信息里有记录自己的OopMap(oop即ordinary object pointer普通对象指针),记录了在该类型的对象内什么偏移量上是什么类型的数据.

    G1在YGC时,老年代中的对象是不回收的,也就意味着GC ROOTS里面应包含了老年代中的对象。但扫描整个老年代会很耗费时间,势必影响整个GC的性能!。所以在CMS中使用了Card Table的结构,里面记录了老年代对象到新生代引用。Card Table的结构是一个连续的byte[]数组,扫描Card Table的时间比扫描整个老年代的代价要小很多!G1也参照了这个思路,不过采用了一种新的数据结构 Remembered Set 简称Rset。

    如何判断对象是否存活

    • 引用计数法
      引用计数法就是如果一个对象没有被任何引用指向,则可视之为垃圾。这种方法的缺点就是不能检测到环的存在。
      首先需要声明,至少主流的Java虚拟机里面都没有选用引用计数算法来管理内存。
      什么是引用计数算法:
      给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值加1;
      当引用失效时,计数器值减1.任何时刻计数器值为0的对象就是不可能再被使用的。
      那为什么主流的Java虚拟机里面都没有选用这种算法呢?
      其中最主要的原因是它很难解决对象之间相互循环引用的问题。
    • 根搜索算法
      概念
      这个算法的基本思想是通过一系列称为“GC Roots”的对象作为起始点,从这些节点向下搜索,搜索所走过的路径称为引用链,当一个对象到GC Roots没有任何引用链(即GC Roots到对象不可达)时,则证明此对象是不可用的。
      那么问题又来了,如何选取GCRoots对象呢?

    在Java语言中,可以作为GCRoots的对象包括下面几种:

    (1). 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
    (2). 方法区中的类静态属性引用的对象。
    (3). 方法区中常量引用的对象。
    (4). 本地方法栈中JNI(Native方法)引用的对象。

    GC对象引用关系图

    为什么要分代?

    时间过得太快,还来不及重来,而我渐渐明白,爱需要关怀,曾经的少年现在已经长大了,也找到了对象,生了一群孩子!他想在北京三环买个开间,开一间自己的小公司!因为刚起步,公司业务也很简单,所以只招了个扫地阿姨,自己坐在办公室里。每天前来拜访的人很多,扫地阿姨很忙,少年经常骂阿姨,给你发工资,这么点事都做不好,地面那么脏,客人都进不来,阿姨也很郁闷,咱们就一个开间,很多人都是到门口看一眼就走了,很少有人走进来签约,但我也去扫一遍(阿姨确实挺笨的!)少年反思了一下,原来原因不在于阿姨,我把开间分开就行了嘛,然后再雇一个阿姨,一个只管前台接待,一个管理VIP!GC分代就这么产生了

    GC算法-标记清除

    image.png
    • 这是最简单原始的办法,好比上边的扫地阿姨!!!缺点很明显
    1. 效率问题,标记和清除两个过程的效率都不高(通过分代可以缓解)
    2. 空间问题,标记清除和产生大量不连续的内存碎片,内存碎片过多容易导致以后在程序运行的过程中需要分配大对象时,无法找到足够的连续内存而不得不提前触发一次垃圾收集动作。
    • 适用场景
      适用于老年代。因为老年代回收的几率小且不频繁能减少内存碎片

    GC算法-复制

    这哥们通过多雇一个阿姨和分区管理提高了阿姨的扫地效率,生意也越来越好,但创业没有那么容易。正如你所想,问题越来越多。最初小哥解决问题的办法很粗暴,一个开间不够,直接花钱租更大的办公室!(加内存,直接到现在我们也一样在用这种办法),早期还好,后来,公司越来越大,开支越来越大,成本还是要控制的,而各种各样的客户也越来越多,比如有的胖客户,一个人占多个人的位置(大对象),有的还是组团来的,而标记-清理算法,明显有很大的碎片,这样来个大胖子,没有他的位置只能等!客户体验很差!!!!


    image.png

    研究发现:新生代中的对象98%都是朝生夕死的。所以老板又脑洞大开,将办公室前台(Eden)和VIP(Old)之间加了两个非常小的休息室(Survivor)每当前台和客户满了的时侯,把客户邀至休息室。然后清扫客户留下来的垃圾。
    每次使用Eden和其中一个Survivor,另一个Survior 空闲。一般比例是Eden : Survivor = 8:1,如果将年轻代分10分,Eden区占8分,Sruvior 占1分*2

    • 缺点
      空间利用率只有50%,但是经过适当的调整Eden和Survivor的比例能达到70%
      复制收集算法在对象存活率较高时就要进行较多的复制操作,效率将会变低
    • 适用场景
      使用了对象存活率较低的内存空间,如Eden区。

    GC算法 之-整理

    一般老板都是抠门的,公司越来越大,小哥也越来越精打细算,突然有一天,这哥们发现休息区搞成一个不行吗?腾出一间房我租出去,北京房价那么贵!!
    于是脑洞又大开,跟扫地阿姨说,你在扫地的时侯,从门口先扫,一行一行地扫,然后请客户移至你扫完的位置,我这个房间不就可以腾出来了嘛,办法总比困难多,没有想不到,只有做不到。就这样,标记,整理横空出世!

    复制收集算法在对象存活率较高时就要执行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选用这种算法

    根据老年代的特点,有人提出了另外一种"标记-压缩"(Mark-Compact)算法,标记过程仍然与"标记-清除"算法一样,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存


    image.png

    参考资料

    https://crowhawk.github.io/2017/08/15/jvm_3/

    https://stackoverflow.com/questions/39569649/why-is-cms-full-gc-single-threaded?from=timeline&isappinstalled=0

    https://blogs.oracle.com/jonthecollector/our-collectors

    https://plumbr.io/blog/garbage-collection/minor-gc-vs-major-gc-vs-full-gc?from=timeline&isappinstalled=0

    https://juejin.im/post/5ed32ec96fb9a0480659e547

    相关文章

      网友评论

          本文标题:白话JVM GC-GC算法简介

          本文链接:https://www.haomeiwen.com/subject/wbpttktx.html