Jvm内存模型和GC垃圾回收机制

作者: 奔跑吧李博 | 来源:发表于2020-08-30 23:21 被阅读0次

    在我们程序运行中会不断创建新的对象,这些对象会存储在内存中,如果没有一套机制来回收这些内存,那么被占用的内存会越来越多,可用内存会越来越少,直至内存被消耗完。于是就有了一套垃圾回收机制来做这件维持系统平衡的任务。

    Jvm内存模型

    GC垃圾回收回收的是什么?当然是内存,那么我们先来复习一下内存的机制。


    程序计数器(Program Counter Register):

    程序计数器是一个比较小的内存区域,用于指示当前线程所执行的字节码执行到了第几行,可以理解为是当前线程的行号指示器。字节码解释器在工作时,会通过改变这个计数器的值来取下一条语句指令。

    每个程序计数器只用来记录一个线程的行号,所以它是线程私有(一个线程就有一个程序计数器)的。

    虚拟机栈(JVM Stack):

    一个线程的每个方法在执行的同时,都会创建一个栈帧(Statck Frame),栈帧中存储的有局部变量表、操作站、动态链接、方法出口等,当方法被调用时,栈帧在JVM栈中入栈,当方法执行完成时,栈帧出栈。

    本地方法栈:

    与虚拟机栈意义相似,区别在于虚拟机栈用于使Java方法,而本地方法栈则是针对于Native方法服务。

    堆区(Heap):

    堆区是理解Java GC机制最重要的区域。在JVM所管理的内存中,堆区是占用最大的一块,堆区也是Java GC机制所管理的主要内存区域,堆区由所有线程共享,在虚拟机启动时创建,堆区的存在是为了存储对象实例。

    方法区(Method Area)

    方法区是各个线程共享的区域,用于存储已经被虚拟机加载的类信息(即加载类时需要加载的信息,包括版本、field、方法、接口等信息)、final常量、静态变量、编译器即时编译的代码等。

    创建对象使用内存的过程

    Object obj = new Object()为例,Object obj表示一个本地引用,存储在JVM栈的本地变量表中。new Object()作为实例对象数据存储在堆中,堆中还记录了Object类的类型信息(接口、方法、属性、对象类型等)的地址,这些地址所执行的数据存储在方法区中。

    GC垃圾回收机制

    需要GC的原因:

    1.分配内存
    2.确保被引用对象的内存不被错误的回收
    3.回收不再被引用的对象的内存空间

    可回收对象的判定:

    1.引用计数法
    给对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时, 计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的。

    优点:引用计数收集器可以很快地执行,交织在程序的运行之中。
    缺点:很难处理循环引用,比如图中相互引用的两个对象则无法释放。

    2.可达性分析算法(根搜索算法)
    从GC Roots作为起点,向下搜索它们引用的对象,可以生成一棵引用树,树的节点视为可达对象,反之视为不可达。

    可回收对象的场景:

    1.显示地赋予某个对象为null或者将对象的引用指向另一个对象

    Object object = new Object();
    object = null;
    
    Object one = new Object();
    Object two = new Object();
    one = two;
    

    2.局部对象的使用

    void fun(){
            Object object = new Object();
            ...
    }
    

    当方法执行完,object对象会被判定为可回收对象

    3.只有弱引用与之关联
    WeakRefrence<Object> obj = new WeakRefrence<Object>(new Object);

    垃圾收集算法

    1.Mark-Sweep(标记-清除)算法
    标记清除:标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间。

    • 优点是简单,容易实现。
    • 缺点是容易产生内存碎片。

    2.Copying(复制)算法
    它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用的内存空间一次清理掉,这样一来就不容易出现内存碎片的问题。

    缺点:可使用内存缩减为一半大小。

    3.Mark-Compact (标记—压缩)算法
    在新生代中可以使用复制算法,但是在老年代就不能选择复制算法了,因为老年代的对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记—清除算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存碎片。因此就出现了一种标记—压缩算法,与标记—清除算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使它们紧凑地排列在一起,然后对边界以外的内存进行回收,回收后,已用和未用的内存都各自一边。

    4.Generational Collection(分代收集)算法

    分代收集算法会结合不同的收集算法来处理不同的空间,因此在学习分代收集算法之前我们首先要了解Java堆区的空间划分。Java堆区的空间划分在Java虚拟机中,各种对象的生命周期会有着较大的差别。因此,应该对不同生命周期的对象采取不同的收集策略,根据生命周期长短将它们分别放到不同的区域,并在不同的区域采用不同的收集算法,这就是分代的概念。
    现在主流的Java虚拟机的垃圾收集器都采用分代收集算法。Java 堆区基于分代的概念,分为新生代(Young Generation)和老年代(Tenured Generation),其中新生代再细分为Eden空间、From Survivor空间和To Survivor空间。

    当执行一次Minor Collection时,Eden空间的存活对象会被复制到To Survivor空间,并且之前经过一次Minor Collection并在From Survivor空间存活的仍年轻的对象也会复制到To Survivor空间。

    在老年代则会采用标记—压缩算法或者标记—清除算法。

    相关文章

      网友评论

        本文标题:Jvm内存模型和GC垃圾回收机制

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