Java虚拟机(二)

2 对象创建

    a 虚拟机遇到new指令,首先根据该指令参数在常量池中定位一个类的符号引用,并检查这个符号引用的类是否被加载、解析、初始化,如果没有,必须先执行相应的类加载过程

    b 虚拟机为新生对象分配内存

        分配内存的两种方法:指针碰撞、空闲列表,这两种方法的区别就在于堆中内存是否规整,如果规整就用指针碰撞

        并发问题:由于创建对象在虚拟机中非常频繁,即使仅仅修改一个指针所指向的位置也会产生并发问题

        解决并发问题的方法:

            1 同步锁--CAS+失败重试来保证更新操作的原子性

            2 本地线程分配缓冲(TLAB)--即每个线程都在自己的独立空间进行操作

    c 将分配内存空间都初始化为零值

    d 为对象进行必要的设置,如如何判断类的元数据信息、对象的哈希码、对象的GC分代年龄等

    e 执行<init>方法

3 对象的内存分布

    a 对象头

    b 实例数据

    c 对象填充

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

    1 判断对象是否存货的两种方法

        A 引用计数算法:

            给对象添加一个计数器,每当一个地方引用它时,计数器值就加1,当引用失效时,计数器值减1,当计数器为0的对象则为不可能触及的对象

            优点:实现简单

            缺点:难以解决对象之间相互循环引用问题

        B 可达性分析算法

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

            可做GC Roots的对象:

            a 虚拟机栈(栈帧中的本地变量表)中引用的对象

            b 方法区中类静态属性引用的对象

            c 方法区中常量引用的对象

            d 本地方法栈中JNI(即一般说的Native方法)引用的对象

    2 引用的四种方式

        A 强引用:

            是指程序代码中普遍存在的new,只要强引用还存在,垃圾收集器永远也不会回收掉被引用的对象

        B 软引用:   

            是用来描述一些还有用但并非需要的对象,在系统要发生内存溢出之前对软引用的对象进行回收,如果回收这些软引用对象后还没有足够的内存,才会抛出内存溢出异常,可以使用SoftReference类来实现软引用

        C 弱引用

            是用来描述非必需的对象,当垃圾收集器工作时直接回收弱引用对象,可以使用WeakReference类来实现弱引用

        D 虚引用

            这是最弱的一种引用关系,唯一目的就是能在这个对象被收集器回收时收到一个系统通知,可以用PhantomReference类来实现

    3 方法区回收

        方法区主要收集两部分内容:废弃常量和无用的类

        a 废弃常量:指这些常量不被任何地方引用

        b 满足下面三个条件则为无用的类

            1、该类所有实例都已经被回收

            2、加载该类的ClassLoader已经被回收

            3、该类对应的java.lang.Class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法

        在大量使用反射、动态代理、CGLib等ByteCode框架,动态生成JSP以及OSGi这类频繁自定义ClassLoadere的场景,都需要虚拟机具备类卸载的功能

    4 垃圾收集算法

        A 标记--清楚算法(下面算法的基础)

            首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象

            缺点:效率问题,标记和清楚两个过程的效率都不高

        B 复制算法

            它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

            优点:这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。

            缺点:只是这种算法的代价是将内存缩小为了原来的一半,未免太高了一点。  

            进而衍生出了一种新的内存分配方式:Eden:Survivor:Survivor=8:1:1。这样将新生成的对象存放在Eden和一个Survivor中,在Minor GC后将存货的对象复制到另一个Survivor区域中,并将Eden和一个Survivor中的对象清楚,这样就只会浪费10%的内存。

            这里还会出现一个内存担保问题,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖其他内存(这里指老年代)进行分配担保。

            这种算法在老年代中不太适用,因为复制的对象的存活率较高,操作效率会降低

        C 标记--整理算法

            这个算法时根据老年代的特点提出的一种算法

            先标记存活的对象,然后将所有存货的对象移动到一端,再直接清理掉端边界以为的内存

        D 分代收集算法

            这个算法就是将Java堆分为新生代和老年代,根据各个年代特点选择不用的算法



---------------------------------未完待续-------------------------------------------------

猜你喜欢

转载自blog.csdn.net/weixin_41574643/article/details/80198267