jvm学习笔记3

玩转JVM中的对象及引用-0721

回顾

1.jvm包括方法区、堆、栈

2.处理的流程

3.堆的分代划分,

4.JHSDB可以查看内存映射,对象放在哪里,

5.查看了对应的栈,以及栈帧之间共享数据,

6.深入辨析了堆和栈

7.oom中的不同类型

8.常量池划分了三种,运行时常量池在堆里面

9.上节课作业

  • String str1 = “abc”;

    String str2 = new String(“abc”);

    String str3 = str2.intern();

  • 此处比较的引用对象的地址

  • false、false、true

1.虚拟机对象的创建过程

  • 虚拟机发现new这个关键字

1.1对象的创建过程

  • 1.检查加载

    • 检查类是不是存在,检查符号引用(全地址),同时检查类是不是被加载过
  • 2.分配内存

    • 从堆空间划一部分内存

      • 指针碰撞
        • 内存中分配区域和未分配区域比较规整,连续
        • 此时只需要从前一个对象使用完开始的连续空闲区域开始存储即可
      • 空闲列表(cms不带整理空间区域)
        • 内存中的空闲区域不连续,很分散
    • 解决并发安全

      • CAS加失败重试

        • compare and swap,比较old值和实时值是否相等,相等则交换,不相等则再来一次;

          是一种乐观锁的方式;

        • 有一定的开销

    • TLAB(分配缓冲)

      • 新生代中eden区,线程1来了,事先在eden区划分一块区域,线程2来了,又给线程2划分一块区域
      • 受制于大小,一般只会为eden区的百分之一,如果分配区域比这个大,就会采用cas的策略
      • 相比于cas效率高
      • -XX:+UseTLAB,默认情况下是开启的
  • 3.内存空间的初始化

    • 碰到new字段,会进行内存空间的初始化,
    • 零值,int默认是0,boolean默认是false
  • 4.设置环节(设置对象头)

    • 对象头Header
      • 哈希码
      • GC分代年龄
      • 锁状态标识
      • 线程持有的锁
      • 偏向线程id
      • 偏向时间戳
    • 类型指针
    • 若为对象数组,还需要有数组长度
  • 5.对象初始化

    • 进过前面的过程后,还需要调用java层面的构造方法

2.对象的内存布局及访问

2.1内存布局

  • 对象头
    • 运行时数据-mark word
      • 哈希码
    • 类型指针
    • 对象数组,应该有记录数组长度的数据
  • 实例数据
  • 对齐填充(非必须)
    • 对象头+实例数据必须是8字节的整数,方便进行内存分配和垃圾回收

2.2对象的访问定位

java栈中的本地变量表是指向句柄,还是直接指针

  • 使用句柄
    • java堆中存在句柄池
      • 句柄是到对象实例数据的指针(对象实例数据存在于堆中的实例池),到对象类型数据的指针(对象类型数据存在于方法区中)
  • 直接指针(hotspot中使用直接指针)
    • 到对象类型数据的指针
    • 对象实例数据
    • 解决了句柄带来的开销,提高了性能

3.对象的存活及各种引用

3.1判断对象的存活

  • 什么是垃圾

    • 垃圾回收的重点是堆

    • c语言是手动操作内存,malloc free

    • c++, new delete

    • 手动是否内存,容易出现忘记回收和多次回收的问题

      • 忘记回收会造成内存泄漏
      • malloc 13号 free free
        • 可能导致某些对象被误删,会出现npe
    • 没有任何引用指向的一个对象或者多个对象(循环引用)

  • 1.引用计数法

    • 对象被引用,计数就加1,引用被撤掉,计数就减一
    • 无法解决对象间相互引用,循环引用的问题,这种对象实际没有被线程使用,但是计数也不为0
    • python使用的就是引用计数法,需要额外手段解决这种循环引用问题,gc效率比jvm低
  • 2.可达性分析(根分析)

    • 1.什么叫根?
      • gc roots(前四种重点)
        • 虚拟机栈引用的对象(局部变量表)
        • 方法区中的静态属性引用的对象
        • 方法区中常量引用的对象
        • 本地方法栈中的JNI(native)引用的对象
        • jvm内部引用的对象(class对象、异常对象npe、oomError、系统类加载器)
        • 所有被同步锁(sysnchronized)锁住的对象
        • jvm内部的jmxBean、JVMTI中注册的回调、本地缓存等
        • JVM实现中的临时性对象,跨代引用的对象(分代模型只回收部分代的对象)
    • 引用链与根直接相连、或者同在一条引用链上
    • 解决了循环引用的问题
  • 实例验证

    • new Object A [10 * 1024 * 1024] 两个都是10m

      new Object B

      //设置循环引用

      A = null // 切除了与局部变量表的联系 切断根可达

      B = null

      system.gc

      • gc 25723k -> 848k
      • 说明循环引用的问题可以解决
  • 3.class回收条件

    必须同时满足以下所有条件

    • 该类的所有实例都被回收,堆不存在任何实例
    • 加载该类的类加载器被回收
    • 该类对应的java.lang.class对象没有任何地方被引用,无法在任何地方通过反射访问该类的方法(就是存在这个类但是没有new的情况)
    • 参数控制:-XX:-Xnoclassgc,禁止类的垃圾回收,可配置
  • 4.finalize方法

    • Object类 有一个finalize()方法

    • 可回收的方法也不是立马回收,第一次判断是是否根可达,第二次判断finalize

      • 重写finalize方法

        super.finalize();

        FinalizeGc.instance = this;

      • finalize方法优先级很低,无法保证执行顺序

      • 执行两次finalize方法,只有第一次有效,

    • 尽量不要使用finalize,有更好的方式,try-finally

3.2 各种引用

引用(main方法) -> T对象 -> B对象

T对象和B对象之间的引用 强、软、弱、虚

  • 强引用 =

    • = ,new出来的对象,用=给某个引用
    • 垃圾收齐永远不会回收这部分对象,即使是oom也不会回收
  • 软引用(SoftReference)

    • out of memory会对软引用进行回收,jvm在即将oom,会把这部分对象回收

    • 软引用需要强引用构建

      • SoftReference userSoft = new SoftReference<~>(x);

        x = null;//干掉强引用

        system.gc();

        //发现此时不会被回收

        //新建一个list,不断往里面充填字节 new byte[1024 * 1024 * 1]

        //此时会被回收掉

      • 缓存的图片、视频资源可以用软引用,因为这部分空间很大,即将oom时就把这部分回收掉

  • 弱引用WeakReference

    • 将上一步中的SoftReference替换成WeakReference
    • 只要发生垃圾回收,这部分对象一定会被回收
    • 也是用在缓存部分,certLocker,weakHashmap
  • 虚引用PhantomReference(幽灵引用)

    • 随时可能被回收
    • 用的很少
    • 在jvm启动时,需要自我检查,检查垃圾回收线程是否正常,可以设置一些虚引用,如果能被回收,说明jvm正常,起到监控垃圾回收期的作用

4.对象的分配策略及优化技术

4.1对象的分配策略

  • 对象的分配原则

    • 对象优先在Eden分配(几乎所有对象都在堆空间分配,因为虚拟机中还会有个技术,可以在栈上分配,此时必须满足逃逸分析)

    • 空间分配担保(minor gc后会对老年年的最大可用连续空间进行判断,相当于担保,如果够就不会触发老年代的gc过程)

      (老年代原本有500m,已经使用了490m,如果minor gc进入的空间大于剩余,会触发major gc)

    • 大对象直接进入老年代(不需要在新生代和老年代之间交换)

    • 长期存活的对象进入老年代(垃圾回收)minor gc

    • 动态对对象年龄判断(垃圾回收)minor gc

  • 虚拟机优化技术

    • 逃逸分析

      • 对象不会逃逸出一个方法,这个引用也没有其他地方使用

      • 满足逃逸分析,分配在栈上

      • 默认开启逃逸分析

      • -XX:-DoEscapeAnalysis -XX:+PrintGC

        关闭逃逸分析,打印GC日志

      • static void allocate{

        ​ person a = new person(18,“man”);

        }

  • 创建对象时,虚拟机分析过程

    • new 一个对象 ->1.判断是否在栈上分配 -> 2.本地线程分配缓冲 ->3.是否是大对象

    • 1满足在栈中生成

    • 1不满足,2满足,生成在eden区

    • 1,2不满足,3满足,放在Tenured区(避免多余的垃圾回收,因为大对象迟早会回收);

      -XX:PretenuredSizeThreshold = 4m(大对象的标准),只对Serial和ParNew两种收集器可用有效;

      jvm有自己的判断逻辑去判断是否是大对象;

    • 1,2,3不满足,生成在eden区

  • 长期存活的对象

    • 如果eden区满了,复制回收算法,判断对象存活(可达、强引用),进行垃圾回收(minor gc),对象会进入from(swap1),对象头里面分代年龄(age)会加1,然后如果eden区又满了,所有的对象会被复制到to区,同时把from区的对象也复制到to区,同时分代年龄加1,当分代年龄达到15,进入老年代
    • 为什么是15,hotspot mark word,分代年龄最大是二进制 4位,就是15,cms是6
    • -XX:MaxTenuringThreshold,最大年龄
  • 动态对象年龄判断

    • 不是要求所有对象的年龄达到15才进入老年代,如果相同年龄的对象总和超过了from或者to内存的一半,这一部分对象可以直接进入老年代,到了老年代,就不区分年龄了,
  • gc

    • eden from to : minor gc
    • tenured : major gc 或 full gc
  • 空间分配担保

    • 悲观策略----minor gc -> major gc
    • 有空间分配担保,先进行minor gc,然后再判断老年代是否能放下,如果能发下,就不会major gc,如果放不下,就进行major gc
    • 悲观策略效率很低,实际上分代作用没有体现,空间分配担保就可以允许你冒险,实际几率很大,只有5%的可能性放不下,如果major gc后也放不下,那就是oom了

答疑

1.动态年龄判断,

from区中某一个分代年龄的总和已经超过了from区的一半,就直接进去老年代(一次回收的垃圾太多,因为复制回收算法,之后也是来回的复制)

2.任何对象回收都需要进行可达性分析、各种引用

3.full gc是可管理的区域都会回收

4.什么是否进入from区,什么时候进入to区,如果对象还活着,触发minor gc,就会交换进入另一个区

5.本地线程缓存是为了保证并发安全,相比于cas效率更高

6.full gc和major gc,只有cms单独针对老年代,full gc除了新生代、老年代、元空间,所以用full gc会更精准点

7.分析逃逸,这个对象是否可以在其他地方使用,

8.把对象置为null,对象变为可回收,不会立刻回收,垃圾回收器需要空间满了才会触发垃圾回收

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值