JVM--10-1---美团面试7问----解析Object 0 = new Object();

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录


美团面试7问

在这里插入图片描述

Object 0 = new Object();

JVM–10–创建对象的步骤、对象实例化内存布局、访问定位

问题1:解释一下对象的创建过程

在这里插入图片描述
在这里插入图片描述

创建过程

  1. 加载类元信息
  2. 为对象分配内存
  3. 处理并发问题
  4. 属性的默认初始化(零值初始化)
  5. 设置对象头信息
  6. 属性的显示初始化、代码块中初始化、构造器中初始化

在这里插入图片描述

一、判断对象对应的类是否加载、链接、初始化

  1. 虚拟机遇到一条 new 指令,首先去检查这个指令的参数能否在 Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化(即判断类元信息是否存在)。
  2. 如果没有,那么在双亲委派模式下,使用当前类加载器以 ClassLoader + 包名 + 类名为 Key 进行查找对应的 .class 文件,如果没有找到文件,则抛出 ClassNotFoundException 异常,
  3. 如果找到,则进行类加载,并生成对应的 Class 对象

二、为对象分配内存

首先计算对象占用空间的大小,接着在堆中划分一块内存给新对象。如果实例成员变量是引用变量,仅分配引用变量空间即可,即4个字节大小( long 和 double 是8个字节)

1. 如果内存规整:使用指针碰撞

如果内存是规整的,那么虚拟机将采用的是指针碰撞法(Bump The Point)来为对象分配内存。
  • 意思是所有用过的内存在一边,空闲的内存放另外一边,中间放着一个指针作为分界点的指示器,分配内存就仅仅是把指针指向空闲那边挪动一段与对象大小相等的距离罢了。如果垃圾收集器选择的是
    Serial ,ParNew 这种基于压缩算法的,虚拟机采用这种分配方式。一般使用带 Compact(整理)过程的收集器时,使用指针碰撞。

2. 如果内存不规整:空闲列表分配

如果内存不是规整的,已使用的内存和未使用的内存相互交错,那么虚拟机将采用的是空闲列表来为对象分配内存。
  • 意思是虚拟机维护了一个列表,记录上那些内存块是可用的,再分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的内容。这种分配方式成为了 “空闲列表(Free List)”

说明:选择哪种分配方式由 Java 堆是否规整所决定,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

三、处理并发问题

在分配内存空间时,另外一个问题是及时保证 new 对象时候的线程安全性:创建对象是非常频繁的操作,虚拟机需要解决并发问题。虚拟机采用了两种方式解决并发问题:

  • CAS(Compare And Swap)失败重试、区域加锁:保证指针更新操作的原子性
  • TLAB 把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在 Java 堆中预先分配一个小块内存,称为本地线程分配缓冲区,(TLAB,Thread Local Allocation Buffer)虚拟机是否使用 TLAB,可以通过 -XX:+/-UseTLAB 参数来设定

四、属性的默认初始化

属性的默认初始化(零值初始化)

内存分配结束,虚拟机将分配到的内存空间都初始化为零值(不包括对象头),这一步保证了对象的实例字段在 Java 代码中可以不用赋初始值就可以直接使用,程序能访问到这些字段的数据类型所对应的零值

  • 属性的默认初始化
  • 显示初始化
  • 代码块中的初始化
  • 构造器初始化
  • 所有属性设置默认值,保证对象实例字段在不赋值可以直接使用

五、设置对象的对象头

  • 将对象的所属类(即类的元数据信息)、对象的 HashCode 和对象的 GC 信息、锁信息等数据存储在对象的对象头中。这个过程的具体设置方式取决于 JVM 实现。
    -
    在这里插入图片描述

六、属性的显示初始化、代码块中初始化、构造器中初始化

  • 在 Java 程序的视角看来,初始化才正式开始。初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给引用变量
  • 因此一般来说(由字节码中跟随 invokespecial 指令所决定),new 指令之后会接着就是执行方法,把对象按照程序员的意愿进行初始化,这样一个真正可用的对象才算完成创建出来。

执行 init 方法进行初始化
在这里插入图片描述

问题2:DCL单例到底需不需要volatile?(指令重排)

DCL (double check lock)—双重校验

单例—懒汉式 (双重校验)
/**
 * lazy loading
 * 也称懒汉式
 * 虽然达到了按需初始化的目的,但却带来线程不安全的问题
 * 可以通过synchronized解决,但也带来效率下降
 */
public class Mgr06 {
    private static volatile Mgr06 INSTANCE; //JIT

    private Mgr06() {
    }

    public static Mgr06 getInstance() {
        if (INSTANCE == null) {
            //双重检查
            synchronized (Mgr06.class) {
                if(INSTANCE == null) {
                    INSTANCE = new Mgr06();
                }
            }
        }
        return INSTANCE;
    }

    public void m() {
        System.out.println("m");
    }

    public static void main(String[] args) {
        for(int i=0; i<100; i++) {
            new Thread(()->{
                System.out.println(Mgr06.getInstance().hashCode());
            }).start();
        }
    }
}


这里还用到了volatile关键字来修饰singleton,其最关键的作用是防止指令重排。

volatile两大作用:

  1. 保持线程可见性
  2. 禁止指令的重排序
提升–04—并发编程之—有序性—volatile两大作用

问题3:对象在内存中的存储布局(对象与数组的存储不同)

对象内存布局

  • 对象头(Header)
  • 运行时元数据
  • 对齐填充(Padding)
    在这里插入图片描述
    在这里插入图片描述

1. 对象头(Header)

  • 运行时元数据(Mark Word)
  • 类型指针

在这里插入图片描述

运行时元数据(Mark Word)

  • 哈希值(HashCode)
  • GC 分代年龄
  • 锁状态标志
  • 线程持有的锁
  • 偏向线程 ID
  • 偏向时间戳
hashCode就是对象的散列码,是根据对象的某些信息推导出的一个整数值,默认情况下表示是对象的存储地址。通过散列码,可以提高检索的效率,主要用于在散列存储结构中快速确定对象的存储地址,如Hashtable、hashMap中。

类型指针

  • 指向类元数据 InstanceKlass ,确定该对象所属的类型。指向的其实是方法区中存放的类元信息

2. 实例数据(Instance Data)

它是对象真正存储的有效信息,包括程序代码中定义的各种类型的字段(包括从父类继承下来的和本身拥有的字段)

规划

  • 相同宽度的字段总是被分配在一起
  • 父类中定义的变量会出现在子类之前
  • 如果 CompactFields 参数为 true(默认为 true),子类的窄变量可能插入到父类变量的空隙

3. 对齐填充(Padding)

不是必须的,也没有特别含义,仅仅起到占位符的作用

问题4:对象头具体包括什么?

  • 运行时元数据(Mark Word)
  • 类型指针

在这里插入图片描述
在这里插入图片描述

java1.8 默认开启了 指针压缩

在这里插入图片描述

1. 默认开启----压缩类型指针
2. 默认开启—实例数据中的,普通类型指针在这里插入图片描述

问题5:对象怎么定位(直接、间接)

  • 句柄访问
  • 直接指针(HotSpot采用)
JVM–10–创建对象的步骤、对象实例化内存布局、访问定位

1. 句柄访问

在这里插入图片描述

句柄访问就是说栈的局部变量表中,记录的对象的引用,然后在堆空间中开辟了一块空间,也就是句柄池

优点

  • reference 中存储稳定句柄地址,对象被移动(垃圾收集时移动对象很普遍)时只会改变句柄中实例数据指针即可,reference 本身不需要被修改

2. 直接指针(HotSpot采用

在这里插入图片描述

  • 直接指针是局部变量表中的引用,直接指向堆中的实例,在对象实例中有类型指针,指向的是方法区中的对象类型数据

问题6:对象怎么分配;

JVM–07–堆1—简介、GC

为新对象分配内存是一件非常严谨和复杂的任务,JVM 的设计者们不仅需要考虑

  • 内存如何分配
  • 哪里分配等问题
  • 并且由于内存分配算法内存回收算法密切相关
  • 所以还需要考虑 GC 执行完内存回收后是否会在内存空间中产生内存碎片
    在这里插入图片描述

在这里插入图片描述

图解过程

我们创建的对象,一般都是存放在 Eden 区的,当我们 Eden 区满了后,就会触发 GC 操作,一般被称为 YGC / Minor GC 操作

在这里插入图片描述
当我们进行一次垃圾收集后,红色的将会被回收,而绿色的还会被占用着,存放在S0(Survivor From) 区。同时我们给每个对象设置了一个年龄计数器,一次回收后就是1。

同时 Eden 区继续存放对象,当 Eden 区再次存满的时候,又会触发一个 MinorGC 操作,此时 GC 将会把 Eden 和 Survivor From 中的对象进行一次收集,把存活的对象放到 Survivor To区,同时让年龄 + 1

在这里插入图片描述
我们继续不断的进行对象生成和垃圾回收,当 Survivor 中的对象的年龄达到15的时候,将会触发一次 Promotion 晋升的操作,也就是将年轻代中的对象晋升到老年代中

在这里插入图片描述

思考:幸存区区满了后?

特别注意,在 Eden 区满了的时候,才会触发 Minor GC,而 Survivor 区满了后,不会触发 Minor GC 操作

如果 Survivor 区满了后,将会触发一些特殊的规则,也就是可能直接晋升老年代

举例:以当兵为例,正常人的晋升可能是 : 新兵 -> 班长 -> 排长 -> 连长
但是也有可能有些人因为做了非常大的贡献,直接从 新兵 -> 排长

对象分配的特殊情况

在这里插入图片描述

栈上分配

JVM–08–堆2—TLAB、逃逸分析

目前Hotspot----JDK1.8,

  1. 暂时只开启了,标量替换和同步省略
  2. 并没有施行栈上分配,所以对象实例都是分配在堆上

线程本地TLAB

JVM 确实是将 TLAB 作为内存分配的首选
  • 对象首先是通过 TLAB 开辟空间,如果不能放入,那么需要通过 Eden 来进行分配

在这里插入图片描述

问题7:object o = new object() 在内存中占用多少字节

java1.8 默认开启了 指针压缩

在这里插入图片描述

1. 默认开启----压缩类型指针
2. 默认开启—实例数据中的,普通类型指针在这里插入图片描述

案例1 : new object() 16个字节

  • 对象头 12个字节
  • 对齐填充 4个字节

在这里插入图片描述

案例2 : new T() 占用多少空间

在这里插入图片描述
总共24个字节

  • 对象头 12个字节
  • int 属性 4个字节
  • string 属性(默认压缩) 4个字节
  • 对齐填充 到8的整数倍 (64位虚拟机), 4个字节

案例3 : object o = new object() 在内存中占用多少字节

java1.8 默认开启指针压缩的时候,且是64位虚拟机的时候 .总共占用20个字节
  • new object() 占用16个字节
    markword有8字节,类型指针4字节,不能被8整除,最后丢失补全4字节,为8的倍数。
  • 引用类型变量 o 占用4 个字节

如果在32G以上,压缩指针算法失效,o的字节压不压缩都是8字节,于是在内存中占用24字节

问题8: Class实例的位置

Object o =new Object()

在这里插入图片描述

在这里插入图片描述

类加载做了哪些事情?

  1. 通过一个类的全限定名获取定义此类的二进制字节流
  2. 将这个字节流所代表的静态存储结构转化为方法区运行时数据结构
  3. 在内存中生成一个代表这个类的 java.lang.Class 对象,放入堆中,单例模式独一份 作为方法区这个类的各种数据的访问入口(类的反射.class对象)

在这里插入图片描述

方法区-数据结构

加载的类信息,存放于一块称为方法区的内存空间
  1. 加载的类信息存放于一块称为方法区的内存空间。
  2. 方法区中还会存放运行时常量池信息
  3. 可能还包括字符串字面量数字常量(这部分常量信息是 class 文件中常量池部分的内存映射)
    在这里插入图片描述

在这里插入图片描述

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值