JVM面试高频问题(上)

简介: JVM面试高频问题(上)

一、进程与线程

在谈JVM的这些问题前,我们先来复习一下有关线程和进程的关系

进程可以看作是程序的执行过程。一个程序的运行需要CPU时间、内存空间、文件以及I/O等资源。操作系统就是以进程为单位来分配这些资源的,所以说进程是分配资源的基本单位。

线程从属于进程,只能在进程的内部活动,多个线程共享进程所拥有的的资源。如果把进程看作是完成许多功能的任务的集合,那么线程就是集合中的一个任务元素,负责具体的功能。虽然CPU、内存、I/O等资源分配给了进程,但实际上真正利用这些资源并在CPU上执行的却是线程,即真正完成程序功能的是线程。


因为进程作为这些资源的拥有者,它的负载很重,在进程的创建、切换、删除过程中的时间和空间开销都很大。所以目前主流的操作系统都只将进程作为资源的拥有者,而把CPU调度和运行的属性赋予了线程。


比如打开浏览器程序,会产生相应的进程,浏览器进程中包含有许多线程,如HTTP请求线程,I/O线程,渲染线程,事件响应线程等。浏览器进程拥有着内存和I/O资源等,但当我们在浏览器中输入文字时,真正使用I/O资源接收我们输入的文字,并在CPU处理文字的却是浏览器进程中的I/O线程。即真正完成浏览器文字输入功能的是线程。


80d1b17abbef4feaa0fc96d7d2d7b96a.png

 现代很多操作系统支持让一个进程包含多个线程,从而提高程序的并行程度和资源的利用率。


二、JVM进程

我们知道Java语言是需要运行在JVM上的。实际上,JVM也是一个软件程序,这就意味着它执行起来也会在操作系统中创建进程,即JVM进程,通常又叫JVM实例。而我们所写的main方法,实际上就是JVM进程中主线程的所在。


从操作系统的角度来看,我们常说的Java程序,应该包括JVM和我们编写的Java代码。


当我们写完Java代码,并编译成class文件后,使用Java命令执行main方法;或者直接在IDE启动main方法时,JVM程序就会执行,操作系统会将其从磁盘中装入内存,并创建一个JVM进程,随后启动主线程,主线程会去调用某个类的 main 方法,因此这个主线程就是我们写的main方法所在。


实际上,JVM本身就是一个多线程应用,即使我们在代码中并没有手动的创建线程,JVM进程也并不是只有一个主线程,而是也会有其他线程。这些线程完成着JVM的功能,如GC线程负责回收JVM使用过程中的垃圾对象。JVM进程启动完成后,必然会有的线程如下:

image.png

至此,我们知道了,启动一个Java程序,本质上就是启动JVM程序,并在操作系统中创建一个JVM进程。这个JVM进程会由操作系统分配许多资源,如内存、I/O等。JVM进程中包含有许多线程,这些线程共享JVM进程分配到的资源,同时这些线程也是CPU核心上执行的实体,它们完成着JVM所具有的功能。


同时我们通过实验也能发现,启动多少个java程序,就会创建多少个JVM进程(JVM实例)。每个实例都是独立的,互不影响,即一个程序(一个JVM软件程序)可以被多个进程共用(创建多个JVM进程或者说JVM实例)


三、JVM

JVM内存区域划分

总的来说,JVM大致可以分为四部分:

  • 方法区
  • 程序计数器

大家对java中的变量应该都不陌生吧!变量分为全局变量、局部变量、静态变量

tip: 大家要主要我们这里说的是JVM中的栈和堆,和操作系统的栈和堆、数据结构中的栈和堆是不同的。大家以后再被问到栈和堆的时候,一定要搞清楚是哪里面的栈和堆


举个例子

public class test {
    public int a; // 全局变量(也可叫做test这个类的成员变量)
    public static int b; // 静态变量
    public void method() {
        int c = 0; // 局部变量
        System.out.println("这是test这个类的一个普通成员方法");
    }
    public static void run() {
        System.out.println("这是一个静态方法!"); // 静态方法不需要借助实例化对象
    }
    public static void main(String[] args) {
        test test1 = new test(); // test1 是test类的一个实例化对象
        test test2 = new test(); // test2 是另一个实例化对象
    }
}
  1. 1.我们的局部变量就是存储在栈上的,全局变量属于我们new出来的实例化对象的一部分,储存在堆上,静态变量就比较特殊了,他存储在方法区中。
  2. 2.那么我们栈上还有什么东西呢?还有我们类中各个方法间的调用关系。


在回到堆中,我们到我们new出来的实例化对象是存储在堆上的,我们的实例化出来的每个对象都有他对应的变量和方法(即我们的成员方法和成员变量),他们自然和对象一起都储存在堆上。


那静态变量和静态方法呢?

他们不属于我们实例化出来的对象,他们是在我们我们这个类创建出来的时候就存在了,不依托实例化对象。他们属于类对象,我们方法区里放到就是类对象。


那么类对象里有啥呢?

包括类是啥名字

继承自谁,实现了那些接口

有啥属性,属性名是啥,类型是啥,访问权限是啥

有什么方法,方法名是?参数是?返回值?访问权限?方法内部的指令是?方法里面干了什么?


总之类对象对我们这个类做了一个整体的描述,一个类可以有多个实例化对象,但只有一个类对象(即每个静态变量和静态方法在不同的实例化对象中也只有一个)。


我们总结一下

  • 栈(方法之间的调用关系、局部变量)
  • 堆(实例化出来的对象:全局变量(也叫成员变量)、成员方法)
  • 方法区(类对象:静态变量、静态方法....)


哦,对了我们还有程序计数器没说


760776feccd04c228761c1671624a6c4.png

就像我们上面说的,当我们启动一个java,启动main方法的时候,我们JVM程序就会执行,就会创建出来一个JVM进程,同时还会有多个线程来负责完成JVM的工作。那么我想问,是每个线程都有上面这四个区域吗?


不是的,我们的方法区 和 堆是整个JAVA进程中只有一份的(该进程内的多个线程共享这一份资源),但程序计数器和栈,则是每个线程都有一份。


为啥呢:

操作系统cpu等相关资源分配给进程,该进程内的所有线程共享该进程的所有资源,线程是执行的基本单位。


每个线程在执行各自执行各自的代码,各自是一个执行流,所有说每个线程都需要知道接下来要执行的指令是什么(程序计数器的功能)?每个线程也需要记录下当前的调用栈(存在方法区)

9c9c4856a3104424ade7aeb076ddb072.png

JVM类加载

Java程序启动的时候,就需要让JVM把class文件给读进内存并进行一系列后续的工作。


类加载流程

1、加载  找到class文件,打开文件,读文件,创建空的类对象

2、链接

验证 —— 检查.class文件格式是否符合规范要求(JVM规范中明确描述了)

准备 —— 给静态变量分配内存空间,空间里填充0值。

解析 —— 把字符串常量进行初始化,把“符合引用替换成直接引用”

dd8916f117aa482f97ab0cba03f71611.png

3、初始化

针对类的静态成员进行初始化,执行静态代码块,如果这个类的父类还没加载,也要去加载父类


而面试中,面试官最爱考的就是——“双亲委派模型”(描述了是类加载中的加载阶段,去那些目录里找.class文件

为了理解所谓的双亲委派模型,我们需要知道:类加载器——JVM中特殊的模块,功能就是负责把类给加载起来,完成类加载的工作。



460474df462543f2a893a7db786fb69e.png


0552a016e6744267a003e05071aaa6df.png

JVM垃圾回收(GC)

大家还记得吗?在C语言中,我们通过malloc动态申请内存(申请的内存是在堆中),每次申请完后都要我们手动释放内存(free)。如果不释放就回造成内存泄漏等严重问题。

但是如果光指望我们程序员手动释放内存,那显然是不靠谱的。

为在Java中就由机器负责回收不再使用的内存空间——这种机制就被称为内存回收机制(garbage collection简称GC)


1、垃圾回收中,回收的是什么?


4da0b1f26da54b9bab6e20653ae3d3f4.png

2、如何确定该对象是需要回收的?

那么我们知道了回收的单位是对象,那么我们如何具体确定某个对象就是垃圾(不再使用了呢)呢?

确定是不是垃圾,有很多种办法。其中Java里主要使用的是“可达行分析”这种办法,在别的编程语言中(比如Python)中使用的是“引用计数这种方法”。


在《深入理解Java虚拟机》这本书中,这两种办法都有提到。

  • 那么如果我们在面试中被问到:在垃圾回收机制中,如何判断对象是不是垃圾?你可以两个都说。
  • 如果问的是:在JVM中,如何判断对象是不是垃圾,你只需回答“可达性分析”就行。


引用计数法

使用额外的计数器,来记录对于某个对象来说,有多少引用指向他。

要想使用对象,就需要有引用指向他,如果没用引用了——引用计数为0了,说明该对象无法被使用了,也就是需要回收的垃圾了。


42d4253bbc5e4eb2b99f1a8d03d0ec38.png


可达性分析(Java真正采取的方法)

可达性,什么意思呢?

就是以代码中的一些特殊变量为起点,然后以起点触发,看看那些对象都能被访问到。只要对象能访问到,就标记为“可达”,当完成一圈标记后,剩下的就是“不可达“的了,也就是要回收的垃圾了!!!


相关文章
|
1月前
|
SQL 缓存 监控
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
本文详细解析了数据库、缓存、异步处理和Web性能优化四大策略,系统性能优化必知必备,大厂面试高频。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:4 大性能优化策略(数据库、SQL、JVM等)
|
23天前
|
存储 算法 安全
JVM常见面试题(四):垃圾回收
堆区域划分,对象什么时候可以被垃圾器回收,如何定位垃圾——引用计数法、可达性分析算法,JVM垃圾回收算法——标记清除算法、标记整理算法、复制算法、分代回收算法;JVM垃圾回收器——串行、并行、CMS垃圾回收器、G1垃圾回收器;强引用、软引用、弱引用、虚引用
|
1月前
|
Arthas 监控 Java
JVM进阶调优系列(9)大厂面试官:内存溢出几种?能否现场演示一下?| 面试就那点事
本文介绍了JVM内存溢出(OOM)的四种类型:堆内存、栈内存、元数据区和直接内存溢出。每种类型通过示例代码演示了如何触发OOM,并分析了其原因。文章还提供了如何使用JVM命令工具(如jmap、jhat、GCeasy、Arthas等)分析和定位内存溢出问题的方法。最后,强调了合理设置JVM参数和及时回收内存的重要性。
|
2月前
|
存储 监控 算法
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程 ?
尼恩提示: G1垃圾回收 原理非常重要, 是面试的重点, 大家一定要好好掌握
美团面试:说说 G1垃圾回收 底层原理?说说你 JVM 调优的过程  ?
|
3月前
|
安全 Java 应用服务中间件
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
什么是类加载器,类加载器有哪些;什么是双亲委派模型,JVM为什么采用双亲委派机制,打破双亲委派机制;类装载的执行过程
102 35
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
|
2月前
|
Java 应用服务中间件 程序员
JVM知识体系学习八:OOM的案例(承接上篇博文,可以作为面试中的案例)
这篇文章通过多个案例深入探讨了Java虚拟机(JVM)中的内存溢出问题,涵盖了堆内存、方法区、直接内存和栈内存溢出的原因、诊断方法和解决方案,并讨论了不同JDK版本垃圾回收器的变化。
34 4
|
2月前
|
Java API 对象存储
JVM进阶调优系列(2)字节面试:JVM内存区域怎么划分,分别有什么用?
本文详细解析了JVM类加载过程的关键步骤,包括加载验证、准备、解析和初始化等阶段,并介绍了元数据区、程序计数器、虚拟机栈、堆内存及本地方法栈的作用。通过本文,读者可以深入了解JVM的工作原理,理解类加载器的类型及其机制,并掌握类加载过程中各阶段的具体操作。
|
2月前
|
存储 缓存 JavaScript
JVM面试真题总结(一)
JVM面试真题总结(一)
|
3月前
|
存储 缓存 监控
【Java面试题汇总】JVM篇(2023版)
JVM内存模型、双亲委派模型、类加载机制、内存溢出、垃圾回收机制、内存泄漏、垃圾回收流程、垃圾回收器、G1、CMS、JVM调优
【Java面试题汇总】JVM篇(2023版)
|
2月前
|
存储 Kubernetes 架构师
阿里面试:JVM 锁内存 是怎么变化的? JVM 锁的膨胀过程 ?
尼恩,一位经验丰富的40岁老架构师,通过其读者交流群分享了一系列关于JVM锁的深度解析,包括偏向锁、轻量级锁、自旋锁和重量级锁的概念、内存结构变化及锁膨胀流程。这些内容不仅帮助群内的小伙伴们顺利通过了多家一线互联网企业的面试,还整理成了《尼恩Java面试宝典》等技术资料,助力更多开发者提升技术水平,实现职业逆袭。尼恩强调,掌握这些核心知识点不仅能提高面试成功率,还能在实际工作中更好地应对高并发场景下的性能优化问题。