【初级】JVM 面试题,是有多简单?

本文详细介绍了JVM中类的判断、对象创建的步骤、内存分配策略以及对象访问方式,包括类加载条件、字节码执行、对象属性初始化和空对象的内存占用。同时探讨了不同内存分配方式的优缺点,如句柄式和直接指针访问,并提及了Java对象在内存中的布局与GC相关的话题。
摘要由CSDN通过智能技术生成

怎么判断2个类是同一个类

类名+包名相同加载这2个类的类加载器对象必须是同一个

类主动使用方式

类的使用可以分主动使用、被动使用,区别是被动使用类时,不会导致类的初始化,这会导致很多难以查找的 Bug 的

  • 通过new关键字、反射、clone、反序列化机制实例化对象
  • 调用类的静态方法时
  • 使用类的静态字段或对其赋值时
  • 通过反射调用类的方法时
  • 初始化该类的子类时(初始化子类前其父类必须已经被初始化)
  • JVM启动时被标记为启动类的类(简单理解为具有main方法的类)

创建对象的几种方式

  • new用new语句bai创建对象,这是最常du用的创建对象的方式。
  • Class.newInstance(),JDK9 以后过时了,只能使用public的无参构造方法
  • Construtcor.newInstance(xx) 有参无参都可以,也没有访问限制,这就是反射的好处
  • clone()
  • 反序列化手段:比如IO,ObjectInputStream 的readObject() 方法,从本地文件,或者网络上获取
  • 第三方库Objenesis 利用字节码技术,动态生成class信息

这个大家了解,知道有这么回事就行,注意反射的2种方式差别,实际场景会用上

创建对象的几种方式

public class Max {
    public void run() {
        String name = new String();
    }
}

字节码:

  public void run();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #2                  // class java/lang/String
         3: dup
         4: invokespecial #3                  // Method java/lang/String."<init>":()V
         7: astore_1
         8: return
      LineNumberTable:
        line 11: 0
        line 12: 8
  • 先走了一个new指令,会判断这个类加载没加载,加载完后去堆内存申请一块内存空间并返回该地址到操作数栈栈顶
  • dup 指令会把刚刚申请到的内存地址复制一份并保存到栈帧中操作数栈栈顶,这样之后操作数栈实际上就上下有2个相同的内存地址了
  • 执行String对象的初始化方法,这需要地址以便找到对象去执行方法,所以这里会消耗调一个内存地址的操作数
  • 然后把这个堆内存地址赋值给局部变量表里的name属性,所以才会有dup指令把内存地址复制一边

通常这个问题是和 volitale 保证有序性、单例模式的双判断写法为什么还要加上 volitale 关键字这个问题联系在一起问的

对象创建的步奏

  • 判断对象对应的类是否被加载、链接、初始化了 也就是new指令会去方法区常量池中定位到类的符号引用,检查这个符号引用是否被加载了
  • 给对象分配内存空间,计算对象需要占用多大的内存空间

堆内存规整:指针碰撞堆内存不规整:JVM要维护一个空闲列表,记录哪些内存块可用的,哪块是用过的,碎片化的问题,对应的是标记清除算法,伊甸园区回收完垃圾之后不做规整整理就这样,比如早期的CMS垃圾回收器

  • 并发处理 堆内存是共享区域,就会有并发问题
  • CAS 失败重试,区域加锁保证原子性
    堆内存给每个线程预先分配一块TLAB空间,也就是针对每个线程预先给一块专属的内存空间,以防止并发问题,问题是这块空间不大,所以还需要上面CAS的配合,可以通过-XX:UseTLAB来设置
  • 初始化分配到的内存空间(默认初始化) 也就是给属性赋一个初始的默认值,即便该属性在代码里设置值了,在这一步也会先给一个默认值,在之后的步奏里再赋指定值
  • 设置对象头 对象头保存有对象的hashcode,GC信息,锁信息,所属类(方法区元数据地址)
  • 显示初始化,执行init方法并初始化 具体可以看下一个问题的解答,init就是类的构造器

实际到代码

Dog dog = new Dog();

new 关键字可以理解为1-5步,Dog() 相当于第6步指针碰撞:用过的内存在一边,没用过的内存在另一边,中间临界点有一个指针。要是需要新分配内存空间了,只需要把指针移动执定大小即可。不过JVM是否采用这种分配方式,的看采用的垃圾回收器选择的是不是
Serial、ParNew 这样的压缩算法(标记整理)了。核心就是垃圾回收之后新生代伊甸园区的是规整过的才能这么玩在这里插入图片描述

对象属性赋值过程

  • 属性的默认初始化给对象中的属性赋一个默认值,比如 int age = 100,在这一步 age=0
  • 显示初始化/代码块初始化还是拿 age 说事,到这里 age=100 了
  • 构造器中初始化

上个问题6里面的init方法对应的就是这里的2、3部

对象访问的2种方式

有:句柄式、直接指针 2种

1.句柄式在这里插入图片描述

在JVM堆内存中,维护一个列表,存储每个对象对应的内存地址,好比我们在数据库搞一个索引表出来,大家要理解句柄的含义,就是索引,句柄是C++的内容,但是在java里好多地方还有她的身影句柄的好处在这个索引表的内存地址是不变的,不管是对象的地址怎么变,对于别人来说其实都是不变的缺点是真用内存,访问效率低一些

2.直接指针
在这里插入图片描述
这是目前 htospot 虚拟机访问对象的方式,不需要索引表,我直接记录你的内存位置,缺点是地址变了需要通知使用者这道题还是知道的好,还真就有人问。具体就是涉及到 GC 的标记清除算法上的优缺点,句柄式的可以并发的GC垃圾回收,因为句柄是不变的,我恩始终可以找到对象。直接指针GC我们就必须停止其他线程,因为引用地址会变

空对象占内存多少

在这里插入图片描述
这是一道常问的面试题,我们只考虑一般对象,对象实体是空的,Mark Word 占64位8个字节类型指针默认是8个字节的,但是在开启指针压缩时会变成4个字节,JDK8 是默认开启的
参考对齐方式,对象的大小必须能被8整除。所以一个空对象默认占用内存大小是12个字节,算上对齐方法的化是16个字节

如果你对Java技术学习还有些问题,可以点击加入交流学习

最后

最新2021整理收集的一些Java学习资料(都整理成文档),有很多干货,包含mysql,netty,spring,线程,spring cloud等详细讲解,也有详细的学习规划图,面试题整理等,点击这里备注CSDN,即可全部领取在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值