JVM<一>内存自动管理[二]对象问题

内存自动管理思维导图

本文的相关代码和笔记都放在了我的github,希望大家多多挑错欢迎各位大佬批评

本文的代码在github中,。里面也包含了JVM实验的大部分代码,希望大家多多支持。

在这里插入图片描述

1.对象问题

1.1 对象的判亡

  1. 使用标记(可达性分析)算法,从GC root中找到有引用的对象和没有被引用的对象。
  2. 对被引用的对象进行一次标记,之后判断是否有必要执行finalize()方法
  3. 如果finalize()被调用过,或者没有被重写,就没有必要执行。
  4. 所以说一个对象到达死亡最多会进行两次标记,一般来说只会进行一次标记。
package gcAndDistribution;

public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;

    public void isAlive() {
        System.out.println("yes i am still alive");
    }

          /*调用这个方法之后把引用重新给挂到自己身上,
        那么这个对象就又有了强引用,
        就可达,
        就不会被垃圾回收器回收。
        */
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("finalize method executed!");
        FinalizeEscapeGC.SAVE_HOOK = this;
  
    }

    public static void main(String[] args) throws Throwable{
        SAVE_HOOK = new FinalizeEscapeGC();

        SAVE_HOOK = null;
        System.gc();

        Thread.sleep(500);

        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("no, i am dead");
        }

        // 只能调用一个finalize方法,所以就算再次把这个对象指向也没有什么用了
        SAVE_HOOK = null;

        System.gc();
        Thread.sleep(500);

        if (SAVE_HOOK != null) {
            System.out.println("I am still alive");
        } else {
            System.out.println("no i am dead");
        }

    }
}

  • 可以作为GC root的
    • 静态变量
    • 常量池中内容
    • 跨区域的引用。比如进行Eden收集的时候,把老年代。

在这里插入图片描述

1.2 对象的内存布局

  • 对象头

    • markword
  • 对象头->类型指针

    • 指针压缩4B
    • 不压缩8B
  • 其他变量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eJTvXuGB-1665490678633)(D:\blgs\source\imgs\image-20220419175024129.png)]

在这里插入图片描述

1.3 对象引用

看上面对象内存布局。

在这里插入图片描述

  • 强引用

    就是普通的应用

  • 软引用:可以实现缓存机制和引用队列一起使用

            ReferenceQueue referenceQueue = new ReferenceQueue();
            String s = new String("软引用,你好华为\n");
            SoftReference<String> sr = new SoftReference<String>(s, referenceQueue);
            System.out.printf(sr.get());
            s = null;
            System.gc();
            System.out.printf(sr.get());
            /*
            *  可以看出,虽然引用强引用已经指向了null, 但是内存仍旧没有释放,这是由于有
            * 弱引用的存在。弱引用只有在内存不够的时候在进行回收。
            * 为了防止内存泄漏,可以配合引用队列进行
            * */
            try{
    //              referenceQueue.remove();
    
                if (referenceQueue.poll() != null) {
                    System.out.printf("1:被回收\n");
                } else System.out.printf("0:没被回收\n");
            }catch ( Exception e) {
                System.out.println("你好,出错了");
                e.printStackTrace();
            }
            System.out.printf(sr.get());
    
  • 弱应用,

            ReferenceQueue reference2 = new ReferenceQueue();
            WeakReference weakReference = new WeakReference(new String("weakReference hello 科兴"), reference2);
            System.out.println(weakReference.get());
            System.gc();
            System.out.println(weakReference.get());
            try {
                if (reference2.poll() != null) { //使用Poll 如果使用remove会进入阻塞态
                    System.out.printf("弱引用失去引用\n");
                } else System.out.printf("弱引用仍旧在被引用\n");
            }catch ( Exception e) {
                e.printStackTrace();
            }
    
  • 虚引用: 用于管理堆外内存,netty中的zerocopy

        private static final String TAG = "PhantomReferenceDemo";
    
        // 1、定义为成员变量 防止PhantomReference被回收
        private ReferenceQueue<String> mQueue = new ReferenceQueue<>();
        private PhantomReference<String> mReference;
    
        // 2、定义为成员变量 方便手动控制回收
        private String mTest;
    
        @Test
        public void test() {
            // 4、开启线程 监控回收情况
            new Thread(() -> {
                while (true) {
                    System.out.println("线程开启");
                    Reference<? extends String> poll = mQueue.poll();
                    if (poll != null) {
                        System.out.println("引用被回收");
                    }
                    try {
                        Thread.sleep(1000);
    
                    } catch (Exception e) {
                        System.out.println(e.getMessage());
                    }
                }
            }).start();
    
    
            // 3、
            // 直接用双引号定义的 存储在方法区
            // new出来的 存储在JVM堆
            // 使用System.gc进行强制垃圾回收,之后内存被回收,就可以看到
            mTest = new String("test");
            mReference = new PhantomReference<>(mTest, mQueue);
    
            try {
                Thread.sleep(1000);
                mTest = null;
                System.gc();
                System.out.println("手动制空完成");
                Thread.sleep(2000);
            } catch ( Exception e) {
                System.out.println(e.getMessage());
            }
        }
    

在这里插入图片描述

1.4 对象创建

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uB03G3L7-1665490678641)(D:\blgs\source\imgs\image-20220419194615074.png)]

  1. 首先去看能否在常量池种定位到一个类符号引用,并判断是否经过了加载、解析和初始化的过程。(首先创建类)
  2. 分配内存,内存的大小在类加载完成之后就可以确定了。
    • 指针碰撞法
    • 空闲链表发
    • 是否有内存压缩(compact) 决定的(parNew和serial是使用指针碰撞)。(CMS理论上是空闲链表,但是可以在拿到的大区域中进行指针碰撞)。
  3. 设置对象头,(类型指针,hash, 对象的分带年龄)
  4. 执行方法

考虑并发

  • CAS失败重试
  • 线程本地分配缓冲TLAB(Thread Local Allocation Buffer) -XX +/-UseTLAB

考虑是否执行了方法。

  • new和 invokespecial是两条字节码指令。
  • 如果字节码中在之后生成了invokespecial的命令,就会,就是说invokespecial调用的是方法。
  • Java编译器编译的字节码文件一般都会。

注意

  • 首先类加载:父类的静态和字面量加载进去
  • 其次类分配内存
  • 然后父类的域和初始化块进行初始化,调用父类的构造函数
  • 然后子类的域和初始化块进行初始化,调用子类的构造函数。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jro1trLq-1665490678642)(D:\blgs\source\imgs\image-20220426180727654.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oZbIMbzN-1665490678643)(D:\blgs\source\imgs\image-20220426180631010.png)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值