Skip to content

常见面试题:

  • 介绍下 Java 内存区域(运行时数据区)
  • Java 对象的创建过程(五步,建议能默写出来并且要知道每一步虚拟机做了什么)
  • 对象的访问定位的两种方式(句柄和直接指针两种方式)

运行时数据区域

线程私有的:随着线程的创建而创建,随着线程的结束而死亡

  • 程序计数器:记录当前线程执行的位置,方便多线程切换时,可以知道线程上次运行到哪里了
    • 唯一一个不会出现 OOM 的内存区域
  • 虚拟机栈:每次方法调用压入一个栈帧,包括局部变量等
    • 会有 OOM 和 StackOverFlowError,无垃圾回收
  • 本地方法栈:使用到 Native 方法时使用的(虚拟机栈保存 java 方法),其他特性和虚拟机栈相同

线程共享的:

  • 堆:存放绝大部分对象实例和数组,如果 JIT 逃逸分析保证对象引用只在方法中,会在栈上分配对象(局部对象)
    • 垃圾回收主要区域
  • 方法区:保存 Class 文件的相关信息
    • 具体实现:1.8 以前是永久代,后面是元空间
  • 直接内存 (非运行时数据区的一部分)

Java 虚拟机规范对于运行时数据区域的规定是相当宽松的。以堆为例:堆可以是连续空间,也可以不连续。堆的大小可以固定,也可以在运行时按需扩展 。虚拟机实现者可以使用任何垃圾回收算法管理堆,甚至完全不进行垃圾收集也是可以的。

直接内存

直接内存是一种特殊的内存缓冲区,并不在 Java 堆或方法区中分配的,而是通过 JNI 的方式在本地内存上分配的。

直接内存并不是虚拟机运行时数据区的一部分,也不是虚拟机规范中定义的内存区域,但是这部分内存也被频繁地使用。而且也可能导致 OutOfMemoryError 错误出现。

JDK1.4 中新加入的 NIO(Non-Blocking I/O,也被称为 New I/O),引入了一种基于通道(Channel)缓存区(Buffer)的 I/O 方式,它可以直接使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆中的 DirectByteBuffer 对象作为这块内存的引用进行操作。这样就能在一些场景中显著提高性能,因为避免了在 Java 堆和 Native 堆之间来回复制数据

直接内存的分配不会受到 Java 堆的限制,但是,既然是内存就会受到本机总内存大小以及处理器寻址空间的限制。

类似的概念还有 堆外内存 。在一些文章中将直接内存等价于堆外内存,个人觉得不是特别准确。

堆外内存就是把内存对象分配在堆外的内存,这些内存直接受操作系统管理(而不是虚拟机),这样做的结果就是能够在一定程度上减少垃圾回收对应用程序造成的影响。

HotSpot 虚拟机对象探秘

对象的创建

  • 类加载检查:需要在常量池中有这个类的符号引用,并且如果类没有被加载过,先执行类加载
  • 内存分配:
    • 指针碰撞:将内存分成空闲和不空闲的两块,中间用指针分隔,直接分配即可,适合内存规整
    • 空闲列表:维护一个空闲列表,适合内存不规整
    • 看 GC 算法的使用
  • 初始化零值:将分配的内存空间初始化零值
  • 设置对象头:标记字段(GC 年龄,锁状态等)
  • 执行 init 方法

正在精进