前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >关于JVM中的几大面试题

关于JVM中的几大面试题

作者头像
半月无霜
发布2023-10-18 16:34:29
1360
发布2023-10-18 16:34:29
举报
文章被收录于专栏:半月无霜

关于JVM中的几大面试题

一、介绍

本文介绍JVM中的几个面试题,十分有用

主要有几题

二、答疑

1)Java类的加载过程

image-20230830182103851
image-20230830182103851

简单来说,可以这样理解分类

  1. 类的加载
    1. 获取二进制文件,将.class文件加载至JVM
  2. 类的连接,验证,准备,解析合称连接
    1. 分配空间
    2. 静态属性赋值(赋初始值,而不是我们给予的值,如int是0,包装类为null
  3. 类的初始化,(是初始化,不是实例化)
    1. 静态属性赋值,这时候就是赋我们给予的值了

什么是符号引用,什么又是直接引用 可以这样进行理解,我们有一个A类和B类,A类中使用到了B类 在字节码中,会用一个符号代表这是B类,这就是符号引用 而在B类进行类加载后,JVM成功的加载了这个B类,使得堆内存中有对应的B.class的对象,同时方法区中有静态方法与属性。 这个时候,A类就会将之前的符号引用,改为直接引用,设置为上面堆内存的B.class对象,或者方法区中的静态方法与属性

类加载的时机

  1. 实例化类对象
  2. 调用类的静态方法
  3. 使用类的静态属性

2)双亲委派机制是什么

在了解双亲委派机制之前,我们先设想一个问题,就是如果我们用户自己写一个String这样一个的类,会出现什么样的情况?

这个问题说简单也简单,说复杂就比较复杂了,这个问题正好是由双亲委派机制来进行解决的。


在了解双亲委派机制之前,我们先得了解几个ClassLoader类加载器

类加载器

说明

加载类的范围

Bootstrap ClassLoader

启动类加载器,最顶层的类加载器,这个加载器,Java中不能获取,返回的是一个null

<JAVA HOME>/lib

Extension ClassLoader

扩展类加载器

<JAVA HOME>/lib/ext

Application ClassLoader

应用程序类加载器,也是我们最常用的类加载器

classpath/java.class.path

User ClassLoader

用户自定义的类加载器

任意来源的类

好的,当了解完上面的四种类加载器之后,我们将进行验证,看下面代码

代码语言:javascript
复制
package com.banmoon.parentsappoint;

public class ParentsAppointTest {

    public static void main(java.lang.String[] args) {
        System.out.println("java.lang.String:" + "abc".getClass().getClassLoader());
        System.out.println("com.banmoon.parentsappoint.String:" + String.class.getClassLoader());
    }

}
image-20230812165237418
image-20230812165237418

为什么,他们的类加载器是不同的呢。有人说了,是因为类加载器本身就是有不同的加载类职责范围。

那么当我们进行类加载的时候,程序怎么知道这个类要用什么类加载器。然而就是这段不同的类,确定使用不同类加载器的过程,就是我们将的双亲委派机制。

我们先看这段代码,正是双亲委派机制的代码,在ClassLoader.java中可以找到这段代码

代码语言:javascript
复制
protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        // 首先,先检查类是否已经被加载
        Class<?> c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                // parent是父亲加载器,这里仅仅是逻辑层面上的,并不是指继承方面的父类
                if (parent != null) {
                    // 如果父亲加载器不为空,则先交给父亲加载器进行类加载
                    c = parent.loadClass(name, false);
                } else {
                    // 如果父亲加载器为空,那说明接下来是Bootstrap ClassLoader了,直接交给特殊的方法进行加载
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }

            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                // 如果上面的父亲类加载器没有加载成功,那就自己查找
                c = findClass(name);

                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}

这就是双亲委派机制,下面可以画一个图

image-20230812182014362
image-20230812182014362

可以看到,类加载的时候,永远都是Bootstrap ClassLoader(启动类加载器)尝试去加载

当然,如果类不合适,将会向下进行委派加载

上面的这种行为可以这样概括,向上检查,向下委托加载

3)JVM内存模型

下面这个就是JVM的内存模型,有些细节没有完全画出来,后续会补上

image-20230813094052500
image-20230813094052500

需要讲一下,其中的这些是什么意思

  • 堆内存:这个先简单概括一下,基本对象的创建都会存储在该内存区域
  • 方法区:方法区是一个概念,然而不同的虚拟机会有不同的实现。在这里,我们只需要关注方法区究竟是干什么的即可。在方法区中,主要存储以下三个内容,
    • 类签名:记录权限名,访问权限,版本号
    • 属性:记录类属性,访问权限
    • 方法:方法字节码记录
  • 常量池:在上面讲到过符号引用改为直接引用,那些这一块常量池就是存储这一块东西的。可以理解为,他是由字节码中一个指针指向另一个字节码。比如说定义了一个String的属性,那么在类加载的连接阶段,常量池中会存储这么一个指针常量。
  • 运行时常量池:这是再上面模型图中没有体现的,需要单独讲解。它里面主要存储两个内容。可以看到的是,运行时常量池是包括了常量池的。所以这一块知识点,不需要额外去记忆。
    • 运行时产生的:如字符串,如上面的符号引用改为直接引用
    • 编译期间产生的:主要是字节码中定义的静态信息,各个类的Class对象。还有就是开发者编写的静态变量。
  • :对象主要存储在堆内存中,这里也是垃圾回收GC的主战场。下面篇幅会提到
  • 程序计数器:用来存储字节码的指令地址,提供给执行引擎去读取执行。简单的来说就是执行到哪一步了
  • 虚拟机栈:换个名字叫Java方法栈,这样好理解一下。Java在调用方法时,会将字节码方法入栈,这个东西叫做栈帧。栈这种数据结构,就是先入后出。类似的,一个A方法压入栈,这个方法调用一个B方法,就会将B方法压入栈。结构展示A在最底下,B在上。在结束的时候,是B方法栈帧先结束,然后才是A方法的栈帧。符合先入后出原则。在栈帧结构内部,我们可以如下进行划分,分别是
    • 局部变量表
      1. 主要存储方法的参数、定义在方法内的局部变量,包括八大基本数据类型,对象的引用地址,返回值地址。
      2. 局部变量表中存储的基本单元为变量槽(Sot),32位(4字节)以内的数据类型占一个slot,64位(long,double)的占两个slot。
      3. 局部变量表是一个数字数组,byte、short、char都会被转化为int,boolean类型也会被转化为int,0代表alse、非0代表true。
      4. 局部变量表的大小是在编译期间决定下来的,所以在运行时它的大小是不会变的。
      5. 局部变量表中含有直接或者间接指向的引用类型变量时,不会被垃圾回收处理。
    • 操作数栈:除了上面的局部变量表,还有一个操作数栈。这个操作数栈是在方法执行的过程中,根据字节码的指令,将上面的变量入栈,再执行指令。如执行复制、交换、求和等操作
    • 动态链接:每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。
    • 方法出口:存放调用该方法的计数器的值;有两种情况,一种是方法正常返回,另一种是方法出先异常的返回。存储在一个异常处理表,方便再发生异常的时候找到处理异常的代码。
  • 本地方法栈:由于Java是由C++语言编写的,里面肯定会调用到C++,故本地方法栈就是存储的是调用C++方法时的变量存储。

三、最后

我是半月,你我一同共勉!!!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-08-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 关于JVM中的几大面试题
    • 一、介绍
      • 二、答疑
        • 1)Java类的加载过程
        • 2)双亲委派机制是什么
        • 3)JVM内存模型
      • 三、最后
      相关产品与服务
      对象存储
      对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档