JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存

简介: JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别;什么是程序计数器,堆,虚拟机栈,栈内存溢出,堆栈的区别是什么,方法区,直接内存

目录

一、JVM基本介绍——概念、组成、重点

  • 1.1 JVM是什么
  • 1.2 JVM由哪些部分组成,运行流程是什么?
  • 1.3 JDK、JRE、JVM 关系
  • 1.4 学习什么

二、JVM组成

  • 2.1 什么是程序计数器
  • 2.2 你能详细地介绍堆吗
  • 2.3 什么是虚拟机栈
  • 2.4 垃圾回收是否涉及栈内存
  • 2.5 栈内存分配越大越好吗
  • 2.6 方法内的局部变量是否线程安全
  • 2.7 什么情况下会导致栈内存溢出
  • 2.8 堆栈的区别是什么
  • 2.9 介绍下方法区
  • 2.10 直接内存
  • 2.11 总结
javap -v  xx.class    #打印堆栈大小,局部变量的数量和方法的参数

一、JVM基本介绍——概念、组成、重点

1.1 JVM是什么

JVM(Java Virtual Machine,即java虚拟机),java程序的运行环境(java二进制字节码的运行环境)。

JVM是一种用于计算设备的规范,它是一个虚构出来的计算机,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。针对java用户,也就是拥有可运行的.class文件包(jar或者war)的用户。里面主要包含了jvm和java运行时基本类库(rt.jar)。rt.jar可以简单粗暴地理解为:它就是java源码编译成的jar包。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java能够“一次编译,到处运行”的原因

JVM是Java跨平台的关键,因为它屏蔽了不同操作系统之间的差异,可以让相同的Java程序在不同的操作系统上运行出相同的结果。

好处:

  • 一次编写,到处运行
  • 自动内存管理,垃圾回收机制

1-1.png

1-2.png

1.2 JVM由哪些部分组成,运行流程是什么?

1-3.png

  • JVM由哪些部分组成:类加载子系统,运行数据区(方法区、堆、程序计数器、虚拟机栈、本地方法栈),执行引擎(解释器、即使编辑器、垃圾回收)、本地库接口

  • JVM包含两个子系统和两个组件,两个子系统为Class loader(类装载器)、Execution engine(执行引擎);两个组件为Runtime data area(运行时数据区)、Native Interface(本地库接口)

    • Class loader(类加载器):根据给定的全限定名类名(如:java.lang.Object)来装载class文件到运行时数据区中的方法区;

    • Execution engine(执行引擎):执行引擎也叫解释器,负责解释命令,交由操作系统执行;

    • Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。

    • Runtime data area(运行时数据区域):这就是我们常说的JVM的内存,我们所有写的程序都被加载到这里,之后才开始运行。由五部分组成,Method Area/MateSpace 方法区/元空间、Heap 堆、PC Register 程序计数器、JVM Stacks 虚拟机栈、Nativa Method Stacks 本地方法栈

  • 运行流程、作用 :首先通过编译器把 Java 代码转换成字节码,类加载器(ClassLoader)再把字节码加载到内存中,将其放在运行时数据区(Runtime data area)的方法区内;而字节码文件只是 JVM 的一套指令集规范,并不能直接交给底层操作系统去执行,因此需要特定的命令解析器执行引擎(Execution Engine),将字节码翻译成底层系统指令,再交由 CPU 去执行,而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。

  • GC垃圾回收主要针对 运行数据区中的堆空间。

1.3 JDK、JRE、JVM 关系

我们在 JVM常见面试题(一):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别 中已详细介绍过JDK、JRE、JVM的联系与区别,此处简单讲解下:

JDK(Java Development Kit,Java开发工具包)、JRE(Java Runtime Environment,Java运行时环境)、JVM(Java Virtual Machine,即java虚拟机)。

JDK是 Java 语言的软件开发工具包(SDK)。在JDK的安装目录下有一个jre目录,里面有两个文件夹bin和lib,在这里可以认为bin里的就是jvm,lib中则是jvm工作所需要的类库,而jvm和 lib合起来就称为jre。

1-4.png

  • JDK包含了JRE,JRE包含了JVM。
  • 如果只想运行Java程序,只需安装JRE即可(少数情况例外);如果想要开发Java程序,则需要安装JDK。
  • JDK是用于java程序的开发,而jre则是只能运行class而没有编译的功能;Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。这就是Java的能够“一次编译,到处运行”的原因。

1-5.png

1-6.png

1-7.png

1.4 学习什么

1-8.png

1-9.png

二、JVM组成

2.1 什么是程序计数器

程序计数器:线程私有的,内部保存的字节码的行号。用于记录正在执行的字节码指令的地址。

2-1.png

javap -v xx.class    #打印堆栈大小,局部变量的数量和方法的参数

2-2.png

2.2 你能详细地介绍堆吗

线程共享的区域:主要用来保存对象实例,数组等,当堆中没有内存空间可分配给实例,也无法再扩展时,则抛出OutOfMemoryError异常。

2-3.png

Java堆主要组成部分:

  • 元空间保存的类信息、静态变量、常量、编译后的代码(Jdk1.8引入)
  • 年轻代被划分为三部分,Eden区和两个大小严格相同的Survivor区,根据JVM的策略,在经过几次垃圾收集后,任然存活于Survivor的对象将被移动到老年代区间。
  • 老年代主要保存生命周期长的对象,一般是一些老的对象

2-4.png

总结:你能详细地介绍Java堆吗

  • 线程共享的区域:主要用来保存对象实例,数组等,内存不够则抛出OutOfMemoryError异常
  • 组成:年轻代+老年代
    • 年轻代被划分为三部分,Eden区和两个大小严格相同的Survivor区
    • 老年代主要保存生命周期长的对象,一般是一些老的对象
  • Jdk1.7和1.8的区别
    • 1.7中有有一个永久代,存储的是类信息、静态变量、常量、编译后的代码
    • 1.8移除了永久代,把数据存储到了本地内存的元空间中,防止内存溢出

2.3 什么是虚拟机栈

Java Virtual machine Stacks (java 虚拟机栈),早期也叫Java栈。每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个栈帧(stack Frame) ,对应着一次次的Java方法调用。一次方法的调用,就是栈帧入栈到出栈的过程;

虚拟机栈是线程私有的,生命周期和线程一致,其作用为 主管Java程序的运行,它保存方法的局部变量(8大基本数据类型、对象的引用地址)、部分结果,并参与方法的调用和返回

每个线程都有自己的栈,栈中的数据都是以栈帧(stack Frame)的格式存在。栈帧是一个内存区块,是一个数据集,维系着方法执行过程中的各种数据信息,存储:局部变量表(Local variables)、操作数栈(operand stack) (或表达式栈)、动态链接(Dynamic Linking) (或指向运行时常量池的方法引用)、方法返回地址(Return Address) (或方法正常退出或者异常退出的定义)、一些附加信息

  • 每个线程运行时所需要的内存,称为虚拟机栈,先进后出
  • 每个栈由多个栈帧(frame)组成,对应着每次方法调用时所占用的内存。栈帧对应的方法执行完后,栈会将该方法对应的栈帧弹出栈,释放内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

2-5.png

2-6.png

多线程下

2-7.png

public class StackTest {
   
    public static void main(String[] args) {
   
        StackTest stackTest = new StackTest();
        stackTest.methodA();
    }

    public void methodA() {
   
        int i = 10;
        int j = 20;
        methodB();
    }

    private void methodB() {
   
        int k = 30;
        int m = 40;
    }
}

2-8.png

补充:本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的。其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。

2.4 垃圾回收是否涉及栈内存

垃圾回收主要指就是堆内存,不涉及栈内存,当栈帧弹栈以后,内存就会释放。

1)什么是垃圾回收?

垃圾回收(Garbage Collection,GC),顾名思义就是释放垃圾占用的空间,防止内存泄露。有效地使用可以使用的内存,对内存堆中已经死亡的或者长时间没有使用的对象进行清除和回收。

2)结合2.5,因为栈帧对应的方法执行完后,栈会将该方法对应的栈帧弹出栈,释放内存,因此垃圾回收不涉及栈内存。

2.5 栈内存分配越大越好吗

未必,默认的栈内存通常为1024k

栈帧过大会导致线程数变少,例如,机器总内存为512m,目前能活动的线程数则为512个(512m/1024k=512);如果把栈内存改为2048k,那么能活动的栈帧就会减半(512m/2048k=256)

(栈内存的大小不会影响方法执行的速度,而且由于计算机硬件的储存大小是有限的,栈空间内存设置过大,创建线程数量较多时会出现栈内存溢出OutofMemoryError,导致最大线程数减少,得不偿失。同时,栈内存也决定方法调用的深度,栈内存过小则会导致方法调用的深度较小,如递归调用的次数较少)

2.6 方法内的局部变量是否线程安全

  • 如果方法内局部变量没有逃离方法的作用范围,它是线程安全的
  • 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全

2-9.png

2.7 什么情况下会导致栈内存溢出

  • 栈帧过多导致栈内存溢出。典型问题:递归调用,没有结束语句,一直递归调用方法,导致栈帧过多、栈内存溢出
  • 栈帧过大导致栈内存溢出。单个栈帧的所需要的内存超出了栈内存大小
public static void m4(){
   
    m4();
}

java.lang.StackOverflowError

2.8 堆栈的区别是什么

  • 栈内存一般会用来存储局部变量和方法调用,但堆内存是用来存储Java对象和数组的的。堆会GC垃圾回收,而栈不会。
  • 栈内存是线程私有的,而堆内存是线程共有的。
  • 两者异常错误不同,但如果栈内存或者堆内存不足都会抛出异常。
    • 栈空间不足:java.lang.StackOverFlowError。
    • 堆空间不足:java.lang.OutOfMemoryError。

2.9 介绍下方法区

  • 方法区(Method Area)是各个线程共享的内存区域
  • 主要存储类的信息、运行时常量池
  • 虚拟机启动的时候创建,关闭虚拟机时释放
  • 如果方法区域中的内存无法满足分配请求,则会抛出OutOfMemoryError: Metaspace

2-10.png

常量池

可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息

javap -v Application.class    #查看字节码结构(类的基本信息、常量池、方法定义)

2-11.png

运行时常量池

常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址

2-12.png

2.10 直接内存

直接内存:并不属于JVM中的内存结构,不由JVM进行管理。是虚拟机的系统内存,常见于 NIO 操作时,用于数据缓冲区,它分配回收成本较高,但读写性能高。

举例:Java代码完成文件拷贝

2-13.png

常规IO的数据拷贝流程

2-14.png

NIO数据拷贝流程

2-15.png

2.11 总结

1)什么是程序计数器

线程私有的,每个线程一份,内部保存的字节码的行号。用于记录正在执行的字节码指令的地址。

2)你能详细地介绍Java堆吗

  • 线程共享的区域:主要用来保存对象实例,数组等,内存不够则抛出OutOfMemoryError异常
  • 组成:年轻代+老年代
    • 年轻代被划分为三部分,Eden区和两个大小严格相同的Survivor区
    • 老年代主要保存生命周期长的对象,一般是一些老的对象
  • Jdk1.7和1.8的区别
    • 1.7中有有一个永久代,存储的是类信息、静态变量、常量、编译后的代码
    • 1.8移除了永久代,把数据存储到了本地内存的元空间中,防止内存溢出

3)什么是虚拟机栈

每个线程在创建时都会创建一个虚拟机栈,其内部保存一个个栈帧(stack Frame) ,对应着一次次的Java方法调用。一次方法的调用,就是栈帧入栈到出栈的过程。

  • 每个线程运行时所需要的内存,称为虚拟机栈,先进后出
  • 每个栈由多个栈帧(frame)组成,对应着每次方法调用时所占用的内存。栈帧对应的方法执行完后,栈会将该方法对应的栈帧弹出栈,释放内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

4)垃圾回收是否涉及栈内存

垃圾回收主要指就是堆内存,不涉及栈内存,当栈帧弹栈以后,内存就会释放。

5)栈内存分配越大越好吗

未必,默认的栈内存通常为1024k,栈帧过大会导致线程数变少。

6)方法内的局部变量是否线程安全

  • 如果方法内局部变量没有逃离方法的作用范围,它是线程安全的
  • 如果是局部变量引用了对象,并逃离方法的作用范围,需要考虑线程安全

7)什么情况下会导致栈内存溢出

  • 栈帧过多导致栈内存溢出。典型问题:递归调用,没有结束语句,一直递归调用方法,导致栈帧过多、栈内存溢出
  • 栈帧过大导致栈内存溢出。单个栈帧的所需要的内存超出了栈内存大小

8)堆栈的区别是什么

  • 栈内存一般会用来存储局部变量和方法调用,但堆内存是用来存储Java对象和数组的的。堆会GC垃圾回收,而栈不会。
  • 栈内存是线程私有的,而堆内存是线程共有的。
  • 两者异常错误不同,但如果栈内存或者堆内存不足都会抛出异常。
    • 栈空间不足:java.lang.StackOverFlowError。
    • 堆空间不足:java.lang.OutOfMemoryError。

9)解释一下方法区

  • 方法区(Method Area)是各个线程共享的内存区域
  • 主要存储类的信息、运行时常量池
  • 虚拟机启动的时候创建,关闭虚拟机时释放
  • 如果方法区域中的内存无法满足分配请求,则会抛出OutOfMemoryError: Metaspace

10)介绍一下运行时常量池

  • 常量池:可以看作是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
  • 当类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址

11)直接内存

  • 并不属于JVM中的内存结构,不由JVM进行管理。是虚拟机的系统内存
  • 常见于 NIO 操作时,用于数据缓冲区,分配回收成本较高,但读写性能高,不受 JVM 内存回收管理

参考 黑马程序员相关视频与笔记

相关文章
|
11天前
|
监控 Oracle Java
JDK 21中的分代ZGC:一场内存管理的革命
JDK 21引入了分代ZGC,为Java应用程序的内存管理带来了革命性的进步。分代ZGC通过将堆内存划分为年轻代和老年代,采用并发处理和染色指针技术,实现了高吞吐量、低延迟和更好的可扩展性。这一特性显著提升了系统的性能和稳定性。
95 51
|
1月前
|
Java
jvm复习,深入理解java虚拟机一:运行时数据区域
这篇文章深入探讨了Java虚拟机的运行时数据区域,包括程序计数器、Java虚拟机栈、本地方法栈、Java堆、方法区、元空间和运行时常量池,并讨论了它们的作用、特点以及与垃圾回收的关系。
63 19
jvm复习,深入理解java虚拟机一:运行时数据区域
|
1月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
65 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
1月前
|
存储 安全 Java
jdk21的外部函数和内存API(MemorySegment)(官方翻译)
本文介绍了JDK 21中引入的外部函数和内存API(MemorySegment),这些API使得Java程序能够更安全、高效地与JVM外部的代码和数据进行互操作,包括调用外部函数、访问外部内存,以及使用不同的Arena竞技场来分配和管理MemorySegment。
45 1
jdk21的外部函数和内存API(MemorySegment)(官方翻译)
|
25天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
1月前
|
存储 算法 Java
深入理解Java虚拟机(JVM)及其优化策略
【10月更文挑战第10天】深入理解Java虚拟机(JVM)及其优化策略
41 1
|
25天前
|
监控 数据可视化 Java
如何使用JDK自带的监控工具JConsole来监控线程池的内存使用情况?
如何使用JDK自带的监控工具JConsole来监控线程池的内存使用情况?
|
2月前
|
安全 Java API
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
65 11
|
2月前
|
监控 Java 大数据
【Java内存管理新突破】JDK 22:细粒度内存管理API,精准控制每一块内存!
【9月更文挑战第9天】虽然目前JDK 22的确切内容尚未公布,但我们可以根据Java语言的发展趋势和社区的需求,预测细粒度内存管理API可能成为未来Java内存管理领域的新突破。这套API将为开发者提供前所未有的内存控制能力,助力Java应用在更多领域发挥更大作用。我们期待JDK 22的发布,期待Java语言在内存管理领域的持续创新和发展。
|
2月前
|
监控 数据可视化 Java
使用JDK自带的监控工具JConsole来监控线程池的内存使用情况
使用JDK自带的监控工具JConsole来监控线程池的内存使用情况