目录
- JVM底层原理
-
- 面试题
-
- Java 对象的创建过程?
- 什么是指针碰撞?什么是空闲列表?/ 内存分配的两种方式?
- JVM 里 new 对象时,堆会发生抢占吗?JVM 是怎么设计来保证线程安全的?/ 内存分配并发问题?
- 对象的内存布局?
- 对象怎么访问定位?
- 内存溢出和内存泄漏?
- 能手写内存溢出的例子吗?
- 内存泄漏可能由哪些原因导致呢?
- 如何判断对象仍然存活?/ 如何判断对象是否死亡?
- Java 中可作为 GC Roots 的对象有哪几种?
- 说一下对象有哪几种引用?/ 强引用、软引用、弱引用、虚引用?
- 什么是 Stop The World?
- 什么是 OopMap?
- 对象一定分配在堆中吗?有没有了解逃逸分析技术?
- 线上服务 CPU 占用过高怎么排查?
- 内存飙高问题怎么排查?
- 举例栈溢出的情况?
- 调整栈大小,就能保证不出现溢出吗?
- 分配的栈内存越大越好吗?
- 垃圾回收是否会涉及到虚拟机栈
- 方法中定义的局部变量是否线程安全?
- 静态变量和局部变量?
- 如何判断一个常量是废弃常量?
JVM底层原理
面试题
Java 对象的创建过程?
- 类加载检查:
- 当虚拟机遇到 new 指令时,首先检查这个指令的参数,也就是要创建的对象的类是否已经加载过
- 如果没有加载过,虚拟机会执行类加载过程
- 分配内存:
- 类加载检查通过后,虚拟机会为新对象分配内存空间
- 对象内存初始化:
- 分配内存完成后,虚拟机会将分配到的内存空间初始化为零值
- 设置对象头:
- 虚拟机会在对象的内存空间中设置对象头,用于存储关于对象的元数据信息
- 执行初始化方法(构造函数):
- 经过上述步骤,一个新的对象已经产生,但是从 Java 程序的角度来看,对象的创建还没有完成
- 然后,会根据程序员定义的构造函数进行初始化
什么是指针碰撞?什么是空闲列表?/ 内存分配的两种方式?
指针碰撞:
- 指针碰撞假定 Java 堆中的内存是绝对规整的
- 内存的分界点由一个指针作为指示器来标示,指向已分配内存的末尾
- 在分配对象内存时,只需要将指针向空闲空间方向移动对象内存大小的位置
- 适用于基于压缩策略的收集器,例如 Serial 和 ParNew 收集器
空闲列表:
- 空闲列表假设 Java 堆内存并不规整,已分配内存和空闲内存交错分布
- 虚拟机维护一个空闲列表,记录哪些内存块是可用的,即未被分配的
- 在分配对象内存时,虚拟机会在空闲列表中找到一块足够大的空间来分配给对象。分配后,虚拟机需要更新空闲列表上的记录,标记分配的区域为已用
- 适用于基于清除算法的收集器,例如 CMS 收集器
JVM 里 new 对象时,堆会发生抢占吗?JVM 是怎么设计来保证线程安全的?/ 内存分配并发问题?
- CAS:使用 CAS 操作来保证更新操作的原子性
- 本地线程分配缓冲 (TLAB):
每个线程在 Java 堆中预先分配一小块内存,称为本地线程分配缓冲
要分配内存的线程,先在本地缓冲区中分配,只有本地缓冲区用完,分配新的缓冲区才需要同步锁定
对象的内存布局?
对象在堆内存中的布局可以划分为三个部分:对象头、实例数据和对齐填充
- 对象头:包括两部分信息:存储对象自身的运行时数据;类型指针
- 实例数据:用来存储对象真正的有效信息
- 对齐填充:起占位符的作用
对象怎么访问定位?
使用句柄:
- Java 堆中划分内存来作为句柄池
- 句柄中包含对象实例数据的指针和对象类型数据的指针
直接指针:
- 对象的实例数据直接存放在堆内存中
对比:
- 使用句柄:在对象被移动的时候,不需要更新引用地址
- 直接指针:效率高,节省了一次指针定位的时间开销(HotSpot 使用)
内存溢出和内存泄漏?
- 内存溢出:申请的内存超过可用内存,内存不足
- 内存泄漏:申请的内存空间没有被正确释放,导致内存空间被浪费
能手写内存溢出的例子吗?
- Java 堆溢出:Java 堆用于存储对象实例,只要不断创