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天前
|
安全 Java 应用服务中间件
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
什么是类加载器,类加载器有哪些;什么是双亲委派模型,JVM为什么采用双亲委派机制,打破双亲委派机制;类装载的执行过程
JVM常见面试题(三):类加载器,双亲委派模型,类装载的执行过程
|
6天前
|
存储 缓存 监控
【Java面试题汇总】JVM篇(2023版)
JVM内存模型、双亲委派模型、类加载机制、内存溢出、垃圾回收机制、内存泄漏、垃圾回收流程、垃圾回收器、G1、CMS、JVM调优
【Java面试题汇总】JVM篇(2023版)
|
1月前
|
存储 安全 Java
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别;什么是程序计数器,堆,虚拟机栈,栈内存溢出,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
|
2月前
|
Java 编译器 程序员
JVM常见面试题(一):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别
JVM常见面试题(一):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别
JVM常见面试题(一):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别
|
1月前
|
缓存 算法 Java
这些年背过的面试题——JVM篇
本文是技术人面试系列JVM篇,面试中关于JVM都需要了解哪些基础?一文带你详细了解,欢迎收藏!
|
2月前
|
存储 Java 程序员
Java面试题:方法区在JVM中存储什么内容?它与堆内存有何不同?
Java面试题:方法区在JVM中存储什么内容?它与堆内存有何不同?
54 10
|
2月前
|
存储 运维 Java
Java面试题:JVM的内存结构有哪些主要部分?请简述每个部分的作用
Java面试题:JVM的内存结构有哪些主要部分?请简述每个部分的作用
43 9
|
2月前
|
存储 安全 Java
Java面试题:在JVM中,堆和栈有什么区别?请详细解释说明,要深入到底层知识
Java面试题:在JVM中,堆和栈有什么区别?请详细解释说明,要深入到底层知识
53 3
|
2月前
|
存储 Java 编译器
Java面试题:描述方法区(Method Area)的作用以及它在JVM中的演变(从永久代到元空间)
Java面试题:描述方法区(Method Area)的作用以及它在JVM中的演变(从永久代到元空间)
37 3
|
2月前
|
算法 Java
Java面试题:列举并解释JVM中常见的垃圾收集器,并比较它们的优缺点
Java面试题:列举并解释JVM中常见的垃圾收集器,并比较它们的优缺点
54 3