• JVM—垃圾收集算法、内存分配策略



    ————————————————————————————————

    如何判断对象的生死?

    引用计数法(脑门刻字法)

    • 在每一个对象中计数器刻上它到底有多少个对象引用,每加一个引用,计数器就加一,当计数器为0的时候,就代表没有对象引用它了,这个对象就可以回收了。
    • 缺点:循环引用问题—》不好处理(循环引用问题就是两个对象互相引用)

    可达性分析

    • 选择一些对象作为根,所有和根能关联上的就不回收,反之回收。
    • 这个算法的核心思路就是通过一些列的“GC Roots"对象作为起始点,从这些对象开始往下搜索,搜索所经过的路径称之为“引用链"。当一个对象到GC Roots没有任何引用链相连的时候,证明此对象是可以被回收的。
    • 哪些对象可以作为根(GC Roots的对象)?
      • 1、在虚拟机栈(栈帧中的本地变量表)中引用的对象,譬如各个线程被调用的方法堆栈中使用到的参数、局部变量、临时变量等。
      • 2、在方法区中类静态属性引用的对象,譬如Java类的引用类型静态变量。
      • 3、在方法区中常量引用的对象,譬如字符串常量池(String Table)里的引用。
      • 4、在本地方法栈中JNI(即通常所说的Native方法)引用的对象。
      • 5、Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象(比如NullPointExcepiton、OutOfMemoryError)等,还有系统类加载器。
      • 6、所有被同步锁(synchronized关键字)持有的对象。
      • 7、反映Java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

    再谈引用

    • 强引用: 是最传统的“引用”的定义,是指在程序代码之中普遍存在的引用赋值,即类似“Objectobj=new Object()”这种引用关系。无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回收掉被引用的对象。
    • 软引用: 是用来描述一些还有用,但非必须的对象。比如说缓存就可以用软引用来构建,内存要发生内存溢出了,才把软引用进行回收,如果这次回收还没有足够的内存,才会抛出内存溢出异常。
    • 弱引用: 只要垃圾收集,就会被回收。(无论当前内存是否足够,都会回收掉只被弱引用关联的对象。)
    • 虚引用: 是最弱的一种引用关系,一个对象是否有虚引用的
      存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知。

    分代收集理论

    当前的垃圾收集器,大多数都遵循了“分代收集”的理论进行设计分代收集理论,实质是一套符合大多数程序运行实际情况的经验法则,建立在两个分代假说之上:

    • 1)弱分代假说: 绝大多数对象都是朝生夕灭的。(不用分代,全部死亡)
    • 2)强分代假说: 熬过越多次垃圾收集过程的对象就越难以消亡。(活时间越长的越难死)

    上面两个假说奠定了一个原则:收集器应该将Java堆划分出不同的区域,然后将回收对象依据其年龄(年龄即对象熬过垃圾收集过程的次数)分配到不同的区域之中存储。
    一般把Java堆划分为新生代和老年代。
    在新生代中,每次垃圾收集时都发现有大批对象死去,而每次回收后存活的少量对象,将会逐步晋升到老年代中存放。
    一个问题: 想将新生代的对象回收,但是这个对象可能是老年代的对象指向(引用)的它,为了找出该区域中的存活对象,不得不在固定的GC Roots之外,再额外遍历整个老年代中所有对象来确保可达性分析结果的正确性,反之也要把新生代全部遍历一遍。
    —>会产生很大的性能负担,因此产生了 跨代引用假说
    3)跨代引用假说: 跨代引用相对于同代引用来说仅占极少数。

    • 依据这条假说,我们就不应再为了少量的跨代引用去扫描整个老年代,也不必浪费空间专门记录
      每一个对象是否存在及存在哪些跨代引用;
    • 只需在新生代上建立一个全局的数据结构(该结构被称
      为“记忆集”),这个结构把老年代划分成若干小块,标识出老年代的哪一块内存会
      存在跨代引用。
    • 此后当发生Minor GC时,只有包含了跨代引用的小块内存里的对象才会被加入到GC
      Roots进行扫描。
      统一定义:
    • 部分收集:指目标不是完整收集整个Java堆的垃圾收集,其中又分为:
      • 新生代收集:指目标只是新生代的垃圾收集。
      • 老年代收集:指目标只是老年代的垃圾收集。目前只有CMS收集器会有单独收集老年代的行为。
      • 混合收集:指目标是收集整个新生代以及部分老年代的垃圾收集。目前只有G1收集器会有这种行为。
    • 整堆收集:收集整个Java堆和方法区的垃圾收集。

    标记-清除算法

    标记出哪些需要回收或哪些不需要回收,按照标记进行回收。(这是比较基础的算法,其他的垃圾回收算法是以它为基础改进的)
    标记-清除算法缺点:
    1,是执行效率不稳定
    2,是内存空间的碎片化问题

    标记-复制算法

    为了解决标记-清除算法面对大量可回收对象时执行效率低的问题,提出了一种称为“半区复制”的垃圾收集算法—将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。两边循环往复。
    标记-复制算法优点:

    • 实现简单,运行高效;
      标记-复制算法缺点:
    • 可用内存缩小为了原来的一半;
    • 存活的对象比较多的时候,来回复制对象导致效率很低
      标记-复制算法的优化:
      Appel式回收—》
      Appel式回收的具体做法是把新生代分为一块较大的Eden空间和两块较小的
      Survivor空间,每次分配内存,先占用Eden空间,垃圾收集的时候,Eden存活的对象放入到Survivor1当中,清理掉Eden中的对象,Survivor1当中存活的放入到Survivor2当中,清理掉Survivor1当中的对象,Survivor2满了之后,将存活的放入到Eden中,清除Survivor2中所有的对象,永远Eden区域优先存储对象。以此类推,两块满了之后,回收的进入另外一块。
      进一步优化——》
      如果Eden区域存活下来的对象比较多,那么会出现两个Survived区域装不下的情况,所以需要依赖其他内存区域(实
      际上大多就是老年代)进行分配担保。这些对象将通过分配担保机制直
      接进入老年代
      标记-复制算法缺点: 在对象存活率较高时就要进行较多的复制操作,效率将会降低;如果
      不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存
      活的极端情况。—>老年代里面不能用这样的算法
      【空间分配担保:新生代与老年代的占比一般是1:2;在新生代中,假如Eden区域30%的数据存活了下来,这样两个Survivor区域放不下这30%的对象,就将这三成数据直接放入老年代,叫做空间分配担保】

    标记-整理算法

    标记过程一样,后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。
    标记整理算法是需要移动对象的,移动对象是相对来说比较消耗时间的。

  • 相关阅读:
    大数据运维实战第二课 大话 Hadoop 发行版选型和伪分布式平台的构建
    webrtc Native C++视频旋转/裁减/绿屏问题
    php+JavaScript实现callback跨域请求jsonp数据
    Thales hsm是什么意思,有什么作用?
    Three.js一学就会系列:02 画线
    在 CentOS 8 中删除旧的 Linux 系统内核
    1990-2021年全国31个省人口相关统计面板数据
    【C++】C++11右值引用
    系统架构设计师 - 数据库系统(2)
    工业机器人常用的六种坐标系
  • 原文地址:https://blog.csdn.net/weixin_53233197/article/details/128179545