JVM 面试题集

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?

Java 虚拟机是 Java 程序运行环境的一部分,负责执行 Java 字节码,它是 Java 实现平台无关性的基石。

Java 程序首先被编译成一种中间形式,即字节码(.class 文件),这些字节码不针对任何特定的硬件或操作系统。当运行 Java 程序时,JVM 在实际的硬件平台上解释执行这些字节码,实现了“一次编写,到处运行”(Write Once, Run Anywhere, WORA)的理念。因此,Java 程序无需为不同平台重新编译,只要对应平台上有相应的 JVM 实现,Java 程序就可以在上面运行。

这种设计有如下几个好处:

  1. 跨平台性:只需编写一次代码,就可以在多个平台上运行,无需针对每个操作系统写特定的代码。

  2. 安全性:JVM 提供了一个隔离的运行环境,可以在执行 Java 程序时对其进行检查和限制,减少安全风险。

  3. 移植性:由于 JVM 屏蔽了底层硬件和操作系统的差异,Java 程序更容易从一个平台迁移到另一个平台。

    内存管理

说一下 JVM 的主要组成部分及其作用?

JVM 的主要组成部分如下:

  1. 类加载器(Class Loaders)
    • 负责加载 Java 类到 JVM 中。它在运行时将 Java 的 .class 文件加载到内存中,并为它们创建对应的 Class 对象。
    • 类加载器按照父级委派模型工作,首先会请求父类加载器加载类,只有在父类加载器加载失败时,才会尝试自己加载。
  2. 运行时数据区(Runtime Data Areas)
    • 方法区(Method Area):存储每个类的结构,如运行时常量池、字段和方法数据,方法和构造函数的代码,以及类型、方法和构造函数的特殊方法。
    • 堆(Heap):JVM 中内存最大的一块,用于存储所有类实例和数组。垃圾收集器主要的工作区域也在这里。
    • 栈(Stacks):存储局部变量和部分结果,并参与方法调用和返回。每个线程拥有自己的调用栈。
    • 程序计数器(PC Register):当前线程所执行的字节码的行号指示器。
    • 本地方法栈(Native Method Stacks):为 JVM 使用到的 Native 方法服务。
  3. 执行引擎(Execution Engine)
    • 负责执行类文件中的指令。它包括一个虚拟处理器、解释器、及即时编译器(JIT 编译器)。
    • 解释器:快速解释字节码,但执行同样代码的效率较低。
    • 即时编译器(JIT):编译热点代码(频繁执行的代码)为本地机器码,提高执行效率。
  4. 垃圾回收器(Garbage Collector)
    • 负责回收堆内存中不再被使用的对象。垃圾回收器的存在使得 Java 程序员不需要手动管理内存,减少了内存泄漏和指针失误的问题。
  5. 本地接口(Native Interface)
    • 提供了一个接口,用于交互 Java 代码和本地库,允许 Java 程序调用或被其他语言如 C/C++ 写的程序调用。
  6. 本地库(Native Libraries)
    • 由 JVM 使用的标准库,这些库通常用 C/C++ 编写,被本地接口调用。

32 位和 64 位的 JVM,int 类型变量的长度是多数?

在 Java 中,数据类型的大小不受 JVM 是 32 位还是 64 位的影响。这是 Java 语言的一部分规范,确保了 Java 程序的可移植性。因此,无论在 32 位还是 64 位的 JVM 上,int 类型的变量长度都是 32 位(4 字节)。这同样适用于其他基本数据类型,如 byteshortlongfloatdoublechar 和 boolean,它们的大小在不同的 JVM 架构中都是一致的。

请详细介绍下 JVM 运行时数据区

VM 运行时数据区域是 Java 虚拟机定义的内存区域,用于在 Java 程序运行期间存储数据。这些区域包括堆(Heap)、方法区(Method Area)、虚拟机栈(VM Stacks)、程序计数器(Program Counter Register)和本地方法栈(Native Method Stacks)。

  • 堆(Heap):JVM中最大的一块内存区域,它被所有Java线程共享。在堆中,主要存放了Java应用创建的对象实例和数组。因为这些对象的生命周期不总是确定的,所以堆也是垃圾收集器的主要工作区域,以确保释放那些不再被任何引用的对象所占用的内存。
  • 方法区(Method Area):同样是线程共享的内存区域,它用来存储每个类的结构信息,比如类的名称、直接父类的名称、方法和变量的信息以及编译后的代码等。在一些JVM实现中,方法区可能被称为"永久代"(PermGen),但从Java 8开始,已经被元空间(Metaspace)所取代。
  • **Java栈(Java Stack)**则是线程私有的,它的生命周期与线程相同。每个栈由多个栈帧组成,每个栈帧对应着一次Java方法调用。栈帧中包含了局部变量表、操作数栈、动态链接信息以及方法返回时的操作。
  • **程序计数器(Program Counter Register)**是每个线程私有的内存区域,它包含了当前线程所执行的字节码的行号指示器。如果执行的是Java方法,计数器记录的是字节码指令的地址;如果执行的是Native方法,计数器的值则是undefined。
  • **本地方法栈(Native Method Stack)**与Java栈相似,但它被用来支持Java中的Native方法(即用其他语言如C或C++编写的方法)。当线程调用一个Native方法时,它的调用状态会被压入本地方法栈。

说一下堆栈的区别?

堆(Heap)

  • 堆是由 JVM 运行时管理的内存区域,用于存放 Java 应用程序创建的对象和数组。
  • 堆内存是在所有线程之间共享的,对象无固定的存活周期,存活时间可能从应用程序开始到结束。
  • 内存的分配和回收是动态的,垃圾收集器负责回收那些不再被任何引用的对象,防止内存泄漏。
  • 堆的大小和生命周期是可以调节的,通常有一个固定的开始大小,但它会随着对象的创建和回收动态扩展或收缩,直到达到 JVM 配置的限制。

栈(Stack)

  • 栈是线程私有的内存区域,每个线程都有自己的栈,用于存储局部变量和部分结果,以及用于方法调用和返回。
  • 栈内存中的数据有明确的生命周期,遵循“先进后出”(LIFO)的原则。每个方法调用时会创建一个栈帧,方法结束时,对应的栈帧会被销毁。
  • 对于栈内存的分配和回收,操作更快速而且效率较高,因为它不需要复杂的垃圾收集算法,每个方法结束后,局部变量就会自动释放。
  • 栈的大小通常比堆小,且不可动态扩展,每个线程的栈空间在线程创建时被分配。

总的来说,堆是用于存储对象和数组的内存区域,是线程共享的,其对象的生命周期不固定,由垃圾收集器管理。而栈是线程私有的内存区域,用于基本类型的局部变量和对象引用,其数据随着方法调用而创建,方法结束而销毁。

对象创建的过程了解吗?

详细过程如下:

  1. 类加载检查
    • 当代码试图创建对象时,JVM 首先检查这个类的 Class 对象是否已经被加载、链接和初始化。如果没有,那么JVM会执行类加载过程。
  2. 内存分配
    • 一旦确定类已经被加载,JVM 接下来将在堆上为新对象分配内存。
    • 对象所需的内存大小在类加载完成后即已知,包括对象的所有实例变量和其他开销(如元数据信息、对齐填充等)。
    • 内存分配可以通过「指针碰撞」(如果内存是绝对连续的)或者「空闲列表」(如果内存是非连续的)方式来实现。
    • JVM 实现可能会采用垃圾回收算法中的分代收集算法,在这种情况下,新创建的对象通常会被放在堆的年轻代(Young Generation)。
  3. 初始化零值
    • 内存分配后,JVM 将分配的内存空间初始化为零值。这确保了对象的实例变量不会有未知的值。
  4. 设置对象头
    • JVM 会在对象的内存中设置对象头(包括类的元数据信息、哈希码、垃圾回收信息等)。
    • 对象头标识了对象的运行时类型和锁状态等信息。
  5. 执行 <init> 方法
    • 对象头设置完毕后,JVM 会调用构造函数。构造函数可能会调用父类的构造函数,并执行所有初始化块。
    • 构造函数中的代码将负责设置实例变量的初始值,这些值可能与默认零值不同。
  6. 引用分配
    • 完成对象的初始化后,JVM 将分配的内存地址赋值给引用变量,此时对象才算是完全创建好了。

整个对象创建过程是由 JVM 的类加载器、内存分配子系统、执行引擎等协作完成的。这个过程中,可能会触发垃圾收集活动,特别是在堆内存不足时。同时,虚拟机也可能对对象的内存分配和初始化过程进行优化,如通过逃逸分析判断对象是否可以在栈上分配,或者通过即时编译器优化对象的创建代码。

怎么获取 Java 程序使用的内存?堆使用的百分比?

  1. 使用命令行工具
    • jstat 命令可以用来查看堆内存以及其他各种运行时数据区的使用情况。
    • 使用 jstat -gc <pid> 可以看到年轻代、老年代以及永久代/元空间的当前使用情况。
  2. 使用 JConsole 或 VisualVM
    • 这些是 Java 提供的可视化工具,可以用来监控 Java 应用程序的内存使用、线程使用等运行时数据。
    • 它们可以显示堆内存的使用情况,包括初始大小、已用大小、提交大小和最大大小。
  3. 使用 Runtime 类
    • Runtime 类提供了与 Java 应用程序的运行时环境相关的方法。
    • Runtime.getRuntime().totalMemory() :获取 JVM 的总内存量。
    • Runtime.getRuntime().freeMemory() :获取 JVM 的空闲内存量。
    • Runtime.getRuntime().maxMemory() :获取 JVM 尝试使用的最大内存量。
    • 堆使
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值