• Java虚拟机【2】


    垃圾收集器与内存分配策略

    程序计数器、虚拟机栈、本地方法栈三个区域随线程生灭,栈中的栈帧的随着方法进入/退出,且分配的内存大小在类结构确定后就已知,因此这些区域的内存分配回收确定。需要考虑CG的是Java堆和方法区,一个接口中的多个实现类需要的内存不同,方法的多个分支需要的内存也不同。因此这部分是垃圾收集器关注的。

    1.确认对象“死活”

    垃圾收集器回收“死去”的对象,因此需要判断对象的“死活”,方法:1.引用计数法 2.可达性分析算法

    1.1.引用计数法

    给对象添加引用计数器,有个一地方引用它,计数+1,引用失效计数-1,计数为0的对象不可能再被使用。

    缺点:难以解决对象循环引用的问题(如下例子)

    public class ReferenceCountingGc{
        public Object instance = null;
        private static final int _iMB = 1024*1024; 
        private byte[] bigSize = new byte[2*_1MB];// 占内存用,以便在GC日志中看是否被回收
        
        public static void testGC(){
            ReferenceCountingGC objA = new ReferenceCountingGC();
            ReferenceCountingGC objB = new ReferenceCountingGC();
            objA.instance = objB;
            objB.instance = objA;
            System.gc(); // 由于两个对象互相引用,因此引用计数不为0,无法回收   
        }
    }

    1.2.可达性分析算法

    基本思路就是通过一系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。

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

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

    若不可达对象覆盖了finalize方法或其已被虚拟机调用过,则对象将逃脱一次“死亡”,只需将其与引用链上任一对象建立关联即可在第二次GC标记时免于“死亡”。

    1.3.引用

    原先的引用是对象只有引用与被引用两种状态,对于一些不太“重要”的对象来说就过于僵硬了,因此通过添加强引用、软引用、弱引用、虚引用方法使得内存空间若足够,就保留在内存中,若GC后内存不足就抛弃。

    强引用:如Object obj =- new Object()

    软引用:在发生内存溢出之前,将其回收

    弱引用:弱引用的对象只能活到下一次GC过程之前,不论内存足够与否,都回收掉

    虚引用:虚引用的存在不影响其生存时间。仅用来当对象被收集器回收后收到系统通知。

    1.4.方法区回收

    判断常量是否“废弃常量”的方法:

    1. 类的所有实例都被回收,Java堆中不存在该类的任何实例
    2. 加载的ClassLoader被回收
    3. 该类的java.lang.Class对象未在任何地方被引用(包括反射)

    例:String对象“abc”进入常量池,但没有任何String对象引用这个常量,则发生内存回收时会回收这个对象

    2.垃圾收集算法

     2.1.标记—清楚算法

    算法分为“标记”和“清除”两个阶段。标记阶段将需要回收的对象标记,在清除阶段进行清除

    缺点:1.标记与清除的效率都不高。2.清除后会产生大量不连续的碎片,当需要分配较大的对象时,会提前触发另一次垃圾收集

    2.2.复制算法(基于2.1)(常用

    将容量分为等量的两块,每次用其中的一块,当内存用完了,把还存在的对象复制到另一块上,再把已经使用过的内存空间一次清理掉,因此每次对整个半区进行内存回收

    优点:效率高

    缺点:内存搜小为了原来的一半,复制操作效率低

    现代的商用虚拟机采用此种方法。由于98%的对象“朝生夕死”,因此内存分为较大的Eden空间和较小的Survivor空间。回收时,将Eden和Survivor中还活着的对象复制到另一块Survivor中,然后清除之前用的空间。若Survivor空间不够用,用老年代进行担保,对象直接通过分配担保机制进入老年代。

    2.3.标记-整理算法(基于2.1)

    相比标记—清除,在清除过程中不对可回收对象进行清理,而是让存活的对象向一端进行移动,而后清理掉边界外的内存。

    2.4.分代收集算法

    根据对象存活周期不同,将内存分为几块(新生代/老生代),根据每个年代的特点采用适合的算法。如死得多的用复制算法,活的多的用2.1或2.2

    3.内存分配回收策略

    总的说,在堆上分配,主要分配在新生代的Eden上,若Eden区没有足够空间,发生MinorGC(新生代的GC)发生,若还不够,将原有的对象担保到老年区去。

    大对象进入老年代

    长期存活的对象进入老年代

    PS:老年代GC相较Minor GC慢10倍以上(Full GC/Major GC)

  • 相关阅读:
    Java JVM启动参数
    使用Navicat连接MySQL8.0版本报1251错误
    安装MySQL和出现的问题解决
    跨域问题:解决跨域的三种方案
    Java8 新特性lambda表达式(一)初始
    搭建docker私有仓库
    crontab定时任务
    CentOS610 php环境安装
    Docker常用命令
    PHP调用python脚本执行时报错
  • 原文地址:https://www.cnblogs.com/tillnight1996/p/12000082.html
Copyright © 2020-2023  润新知