Skip to content

Java 虚拟机 已完结

JVM

说一下 JVM 的内存结构

提示

堆、虚拟机栈、本地方法栈、方法区、程序计数器

  1. 方法区:类对象、常量、常量池、静态变量
  2. 堆:存储对象实例
  3. 虚拟机栈
  4. 本地方法栈
  5. 程序计数器:存储下一条指令地址

说一下堆和栈的区别

提示

个数、内容、物理地址、生命周期

  1. 内存共享:每个线程都有各自的栈,所有线程共享一个堆。
  2. 存储内容:栈存放临时变量、操作数等,堆存放对象实例。
  3. 生命周期:栈跟随线程,堆跟随整个程序
  4. 物理地址:栈的地址是连续的,堆的地址是分散的。

内存溢出和内存泄漏的区别

  1. 内存泄漏:申请了内存没有释放
  2. 内存溢出:没有足够的内存去使用

栈溢出的原因

  1. 设置的栈内存过小
  2. 方法中存在错误,比如无限递归

方法区溢出的原因

  1. 使用动态代理生成了过多的类
  2. 设置的方法区内存过小

运行时常量池溢出的原因

  1. 常量池设置的的内存过小
  2. 大量调用了 intern 方法

什么是类加载?类加载的过程?

什么是类加载:将 class 文件读入内存,存放在 JVM 的方法区中的过程。

类加载的过程:加载、连接(验证、准备、解析)、初始化、使用、卸载。

img

什么是类加载器?类加载器有哪些?

类加载器:用于通过类的全限定名获取 Class 对象

类加载器:

  • 启动类加载器
  • 扩展类加载器
  • 应用程序加载器
  • 自定义加载器

什么是双亲委派模型?

一个类加载器收到类加载请求时,会先委托父类加载器完成加载,直至启动类加载器。只有父加载器无法完成加载时,会让子类加载器尝试加载。

为什么需要双亲委派模型?

  1. 保证 Java 的核心代码不会被恶意替换,保证代码安全。
  2. 避免一个类被重复加载

怎么判断两个类是否相等?

  1. 来源于一个 Class 文件(相同限定名)
  2. 被同一个类加载器加载

类的实例化顺序?

  1. 父类 static
  2. 当前类 static
  3. 父类普通代码
  4. 父类构造方法
  5. 当前类普通代码
  6. 当前类构造方法

Java 对象的定位/访问方式

提示

直接指针:引用指向堆中的地址

句柄:维护一个映射表,表中存放堆中的地址

Java 通过指向对象的引用来操作对象,访问方式分为句柄直接指针两种。

  1. 直接指针:引用指向堆中对象的地址。优点是访问速度快。(Hotspot 虚拟机使用这种)
  2. 句柄:引用指向堆中的一个句柄,句柄里存放真实对象的地址。优点是对象移动改变地址时,不需要修改对象引用的地址。

如何判断一个对象是否存活?

提示

引用计数法、可发性分析

  1. 引用计数法:记录这个对象被引用的次数
  2. 可达性分析:通过存在的引用,分析是否能访问到该对象

GC 是什么?为什么需要 GC?

GC 是垃圾回收机制。

为什么需要 GC:内存回收非常容易出问题,Java 提供的 GC 可以实现自动回收内存。

可作为 GC Roots 的对象有什么?

  1. 栈中的引用的对象
  2. 静态属性引用的对象
  3. synchronized 持有的对象
  4. 常量引用的对象
  5. Native 方法引用的对象
  6. JVM 内部引用的对象

什么情况下类会被卸载?

  1. 所有实例已经被回收
  2. 加载该类的类加载器已经被回收
  3. 该类的类对象没有被引用,不能通过反射访问该类

可以对满足上述 3 个条件的类进行回收,但不一定会进行回收。

强引用、软引用、弱引用、虚引用是什么?

  1. 强引用:对象不会被回收。
  2. 软引用:内存不足时回收,常用于缓存机制。
  3. 弱引用:垃圾回收器运行时回收。
  4. 虚引用:用于跟踪对象回收状态。

聊聊 Java 的 GC,分别作用在什么范围?

  1. Young GC:新生代
  2. Old GC:老年代
  3. Full GC:整个堆
  4. Mixed GC(混合收集):新生代和部分老年代(G1 GC)

JVM 的内存分配策略

  • 对象优先在 Eden(伊甸园区)分派
  • 大对象和长期存活的对象直接进入老年代
  • 动态对象年龄判定
  • 空间分配担保

Full GC 的触发条件

  1. 主动调用 System.gc()
  2. 老年代空间不足
  3. 空间分配担保失败

垃圾回收算法有那些?

  1. 标记清除:通过标记不需要的对象,再统一清除。
  2. 复制清除:将存活对象复制到新空间,旧空间的对象全部清除。
  3. 标记整理:标记存活对象,再整理存活对象以消除碎片。
  4. 分类收集:将对象分为不同代,根据对象的生命周期选择不同的回收策略。

有哪些垃圾回收器?

对象头了解吗?

对象由对象头、实例数据、对齐填充字节组成。

对象头分为 MarkWord、指向类对象(Class 对象)的指针、数组长度(只有数组有)组成。

MarkWord 包含对象哈希码、GC 分代年龄和锁标志位。

对齐填充字节:JVM 要求对象占的内存大小是 8 byte 的倍数,因此后面有几个字节用于把对象的大小补齐至 8 byte 的倍数。

main 方法的执行过程

java
public class Application {
    public static void main(String[] args){
        Person p = new Person("wmh");
        p.getName();
    }
}

class Person {
    public string name;

    public Person(String name){
        this.name = name;
    }

    public string getName(){
        return this.name;
    }
}
  1. 编译 Application.java 得到 class 文件,运行 class 文件得到一个 JVM 进程。从类路径中找到 Application.class 的二进制文件,将类信息加载到方法区中(类的加载)
  2. JVM 从 main 方法开始运行
  3. Person p = new Person("wmh");:加载 Person 类,放入方法区。在堆中申请内存,初始化对象,执行构造方法。
  4. p.getName();:根据 p 的引用找到 p 对象,根据 Person 类对象的方法表获得 getName 方法,并执行。

对象创建过程

  1. 类加载检查:查看类对象是否被加载
  2. 分配内存
  3. 初始化零值:将分配的内存置为零值
  4. 设置对象头:在对象头标记是哪个类的实例、对象哈希码、GC 分代年龄等
  5. 执行 init 方法:根据构造方法初始化对象

Released under the MIT License.