[线程与网络] Java虚拟机常考面试题(线程与网络完结)

2024-06-09 19:36

本文主要是介绍[线程与网络] Java虚拟机常考面试题(线程与网络完结),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

🌸个人主页:https://blog.csdn.net/2301_80050796?spm=1000.2115.3001.5343
🏵️热门专栏:🍕 Collection与数据结构 (92平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm=1001.2014.3001.5482
🧀线程与网络(96平均质量分) https://blog.csdn.net/2301_80050796/category_12643370.html?spm=1001.2014.3001.5482
🍭MySql数据库(93平均质量分)https://blog.csdn.net/2301_80050796/category_12629890.html?spm=1001.2014.3001.5482
🍬算法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12676091.html?spm=1001.2014.3001.5482
感谢点赞与关注~~~
在这里插入图片描述

目录

  • 1. JVM内存划分
    • 1.1 堆区
    • 1.2 程序计数器
    • 1.3 元数据区
    • 1.4 Java虚拟机栈
    • 1.5 本地方法栈
    • 1.6 不同形态的变量在数据区中的存储
  • 2. JVM类加载
    • 2.1 类加载过程
    • 2.2 双亲委派模型
      • 2.2.1 什么是双亲委派模型
      • 2.2.2 工作过程
  • 3. JVM垃圾回收机制(GC)
    • 3.1 什么是GC
    • 3.2 GC的工作流程
      • 3.2.1 判断谁是垃圾
      • 3.2.2 释放对应内存(垃圾回收)

1. JVM内存划分

JVM本质上是一个Java进程,这个进程一旦跑起来,就会从操作系统中申请一大块内存空间.那么JVM接下来就要对这一大块空间进行划分,每个区域都有不同的功能.划分出来的也叫JVM运行时数据区或者也叫内存布局.
在这里插入图片描述

1.1 堆区

  • 堆区是各个数据区中最大的区域.
  • 他的作用是:程序中创建的所有对象都保存在堆中.
  • 这个区域是所有线程共享的.
  • 堆中分为两个区域,一个是新生代,一个是老年代,新生代中还有三个区域,一个是伊甸区(Eden),两个生存区(s1/s0).具体这几个区域中都存放什么样的对象,我们后面讲到垃圾回收机制的时候再说.
    在这里插入图片描述

1.2 程序计数器

  • 这个区域是数据区域中最小的区域.
  • 他的作用是:用来记录当前线程执行的行号.也就是当前要执行的下一条指令(JVM字节码)的地址,这个地址就是元数据区中的一个地址.
  • 这块区域是各个线程都有自己单独的一块,也就是线程私有的.

1.3 元数据区

  • 他的作用是:用来存储被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据.
  • 创建一个类之后产生的类对象就存储在这里.
  • 方法相关的信息也存储在这里,类中都有一些成员方法,每个方法都代表一系列"指令集合"(JVM字节码指令).
  • 这里还存储着常量池,不仅仅是string类型,也可能是数字等其他类型.
  • 这个区域是各个线程共享的.

1.4 Java虚拟机栈

  • 线程私有
  • Java虚拟机栈分为一下四部分:
    1. 局部变量表:存放了编译器可知的各种基本数据类型(8大基本数据类型)、对象引用。局部变量表所需的内存空间在编译期间完成分配,当进⼊⼀个方法时,这个方法需要在帧中分配多大的局部变量空间是完全确定的,在执行期间不会改变局部变量表大小。简单来说就是存放方法参数和局部变量
    2. 操作栈:每个方法会⽣成⼀个先进后出的操作栈。
    3. 动态链接:指向运行时常量池的⽅法引用。
    4. ⽅法返回地址:PC寄存器的地址。
      在这里插入图片描述

1.5 本地方法栈

  • 和Java虚拟机栈类似,
  • 也是线程私有的
  • 他两的区别就在于,Java虚拟机栈是给JVM使用的,而本地方法栈是给本地方法使用的.

1.6 不同形态的变量在数据区中的存储

基本原则:一个对象在哪个区域,取决于对应变量的形态.局部变量在栈上,成员变量在堆上,静态成员变量在元数据区.下面我们来举例说明:

class Test2{}
class Test{int a;Test2 t2 = new Test2();String s = "hello";static int b;
}
public static void main(String[] args){Test t = new Test();
}
  • main方法中的t变量在栈上,用来保存对象的首地址.(堆上的地址).
  • new Test()在堆上.是通过new创建出的一个对象.
  • new Test2()在堆上,和上一个是同理,也是通过new创建出的一个对象.
  • Test类中的成员变量都在堆上.
  • static修饰的是属于类的属性,就会出现在元数据区的类对象中.
    在这里插入图片描述

2. JVM类加载

一个Java进程要想跑起来,就要把Java先变成**.class文件.加载到内存中**,得到"类对象".这个所谓的跑起来,就是执行指令,要执行的CPU指令,都是通过字节码让JVM翻译出来的.

2.1 类加载过程

对于类加载的过程,总共分为一下几个步骤:

  1. 加载: 在硬盘上找到.class文件,读取文件内容.
  2. 连接
    • 验证:.class里的内容,是否符合要求.class文件格式的内容在官方文档中有明确的规定,把读取到的内容,往这一套标准中套入,即可判断是否符合要求
    • 准备:给类对象分配内存空间,分配之后,把这个空间里的数据先全部填充为0.
    • 解析:针对字符串常量初始化,把刚才.class文件中常量内容取出,放到元数据区.
  3. 初始化:针对类对象的初始化,其中就包含对静态成员初始化,执行初始化,执行静态代码块.注意不是针对对象的初始化.
    在这里插入图片描述

2.2 双亲委派模型

2.2.1 什么是双亲委派模型

在上述三部中,“加载"的过程中.会根据代码中"全限定类名”(包名+类名)找到对应的.class文件.在JVM加载.class文件,并找到.class文件的时候,就要用到双亲委派模型.
在JVM中包含这样一个特定的模块,叫做"类加载器",这个类负责完成后续的类加载工作.
如果⼀个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每⼀个层次的类加载器都是如此.

2.2.2 工作过程

在这里插入图片描述

  • 这其中有三个类加载器: AppClassLoader,ExtClassLoader,BootStrapClassLoader.这三个类加载器存在父子关系,如上图所示.
    [注意] 这里的父子关系不是通过类的继承方式来表示的,而是通过类加载器中的"parent"字段指向自己的父亲.
  • 三个类加载器的作用:
    • BootStrapClassLoader:加载标准库中的类.
    • ExtClassLoader:加载JVM扩展库中的类.(各JVM厂商,在上述的标准上,对JVM进行了扩展,这样的扩展在JVM标准之外,但是在安装了JVM就有.
    • AppClassLoader:加载第三方库的类和自己写的代码的类.
  • 他们几个类加载器的工作过程就类似于望父成龙的过程.
    • 工作从AppClassLoader开始,这个类加载器并不会立即搜索第三方库相关的目录.而是把任务交给自己的父亲来进行处理.也就是ExtClassLoader.
    • 工作到了ExtClassLoader,也不会立即对自己负责的扩展库进行搜索,也是把任务交给自己的父亲来处理.
    • 工作到了BootStrapClassLoader,BootStrapClassLoader也想交给自己的父亲进行处理.但是它的parent指向null,只能自己处理.在BootStrapClassLoader负责的标准库中的路径中搜索上述的类.
    • 如果找到了,就搜索完成了,类加载器负责打开文件,读取文件等后续操作.
    • 如果没有找到,任务还是要交给儿子来处理.
    • 工作回到ExtClassLoader,搜索扩展库的类,如果找到了,搜索完成,如果没找到,继续交给儿子处理.
    • 工作回到AppClassLoader,搜索第三方库中的类和自己创建的类.

举例说明:上司与下属处理问题
下属在发现一个问题的时候,不可以自己擅自处理,需要向上级上报,如果上报给上级之后,上级觉得这个问题非常重要,上级就会亲自处理,如果问题不是很重要,上级就会对下属说:"你自己看着办吧."于是下属就有权利处理这个问题了.

  • 双亲委派模型适用于:自己写的类和标准库/扩展库冲突,JVM会确保加载的类是标准库中的类.
  • 双亲委派模型可以破坏掉,我们可以通过自己实现类加载器来破坏模型.

3. JVM垃圾回收机制(GC)

3.1 什么是GC

这是Java提供的对于内存自动回收的机制.更本质地来说,GC其实回收的是"对象",回收的是"堆上的内存".回收对象的时候,一定是一次回收一个完整的对象,不能回收半个.
在这里插入图片描述

为什么GC只回收堆上的内存,不回收其他区域的?

  • 程序计数器:不需要额外回收,他是每个线程私有的,线程销毁之后,自然就回收了.
  • 栈:不需要额外回收,他是每个线程私有的,线程销毁之后,自然就回收了.
  • 元数据区:一般也不需要,都是加载类,很少卸载类.

3.2 GC的工作流程

GC的工作流程分为两步:

  1. 找到谁是垃圾
  2. 释放对应的内存

3.2.1 判断谁是垃圾

判定一个对象是否是垃圾,判定的方式比较保守.判定某个对象是否存在引用指向它.使用对象,都是通过引用的方式来使用的,如果没有引用指向这个对象,意味着这个对象无法再代码中使用.于是就可以视为垃圾.

  • 引用计数算法
    [注意] 这种算法是PHP和python采用的算法,不是JVM采用的算法.
    给对象增加⼀个引用计数器,每当有⼀个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任何时刻计数器为0的对象就是不能再被使用的,即对象已"死".
    在这里插入图片描述
    但是这种方法存在很大的缺点,所以JVM才没有采用这样的方式.
    • 额外消耗的存储空间较大
    • 引用计数无法解决对象的循环引用问题
      什么是对象循环引用问题,我们来看下面这样一段代码.
    • 首先创建了两个对象,并使用a,b引用指向他们,对象的计数+1.
    • 之后两个对象中的t成员变量分别指向对方的引用,对象的计数+1.
    • 之后让a,b指向null,a,b指向的对象的计数-1,但是还没有减为零,所以两个对象没有被视为垃圾回收掉.
class Test{Test t;
}
public static void main(String[] args){Test a = new Test();Test b = new Test();a.t = b;b.t = a;a = null;b = null;
}

在这里插入图片描述

  • 可达性分析算法
    为了避免上述的两个问题,所以JVM引入了可达性分析算法.解决了空间和循环引用的问题.但是也付出了时间上的代价.
    这种算法的核心思想就是:遍历.
    JVM把对象之间的引用关系,理解为一个树形结构.JVM会不停遍历这样的结构,把所有可能遍历访问到的对象标记为"可达",剩下的是"不可达".
    例如下面这棵树:
    在这里插入图片描述

从root出发,任何一个树上的结点都可达.
如果现在令c.right = null,这样的话,f也无法访问到了,f就会被标记为"不可达",就会把f标记为垃圾.a.right = null,此时c就不可达,f也不可达,c和f也会被标记为垃圾.
JVM会周期性对所有的树进行遍历,不停标记可达/不可达.

3.2.2 释放对应内存(垃圾回收)

把垃圾标记出来之后,就要对垃圾进行回收操作了.

  • 标记-清除算法
    这个算法分为标记和清除两个阶段.首先需要标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象.
    但是这种算法有一些不足:
    • 效率问题:标记和清除这两个过程的效率都不高.
    • 空间问题:标记清除之后会产生大量的不连续的内存碎片,空闲的内存被分成了一个碎片,不集中.空间碎片太多可能会导致以后子啊程序运行的时候需要分配较大对象的时候,无法找到足够连续的内存.
      在这里插入图片描述
  • 复制算法
    当这块内存需要进行垃圾回收时,会将此区域还存活着的对象复制到另⼀块上面,然后再把已经使用过的内存区域⼀次清理掉.
    在这里插入图片描述
    但是这种做法也有一个致命的缺点:复制的时间开销很大.
  • 标记-整理算法
    标记过程仍与"标记-清除"过程⼀致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向⼀端移动,然后直接清理掉端边界以外的内存.;类似于顺序表删除中间元素的操作.
    在这里插入图片描述
    这种算法实质上和复制算法差别不大,也是会涉及到复制操作,也会产生较大的时间开销.
  • 分代算法
    这种算法就是根据对象的年龄进行垃圾回收.所谓的年龄就是经过GC扫描的次数.按照年龄,把对象分为新生代和老年带,其中新生代又分为伊甸区和生存区.这就和我们前面所提到的堆的数据区接轨了.
    在这里插入图片描述
    • 新创建的对象,会被放到伊甸区,伊甸区的大部分对象,生命周期都比较短,第一轮GC就有可能成为垃圾,只有极少数可以活过第一轮来到生存区.
    • 生存区有两个,每经过一轮GC,生存区都会淘汰一批对象,剩下的进入到另一个生存区,到达另一个生存区是通过复制实现的.此外还有伊甸区新来的对象.每复制一次,年龄+1.就这样在两个生存区之间来回周转.
    • 老年代对象也要经过GC,但是老年代的生命周期更长,就可以降低GC的频率.这里对老年代的GC,是通过标记=整理算法来完成的.

举例说明:校招找工作

  • 伊甸区:投放简历,大量简历被直接淘汰
  • 生存区:简历通过,要经过笔试,技术面,HR面等多轮筛选.
  • 老年代:进入公司,但是会定期考核.

这篇关于[线程与网络] Java虚拟机常考面试题(线程与网络完结)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1046098

相关文章

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

Springboot @Autowired和@Resource的区别解析

《Springboot@Autowired和@Resource的区别解析》@Resource是JDK提供的注解,只是Spring在实现上提供了这个注解的功能支持,本文给大家介绍Springboot@... 目录【一】定义【1】@Autowired【2】@Resource【二】区别【1】包含的属性不同【2】@

springboot循环依赖问题案例代码及解决办法

《springboot循环依赖问题案例代码及解决办法》在SpringBoot中,如果两个或多个Bean之间存在循环依赖(即BeanA依赖BeanB,而BeanB又依赖BeanA),会导致Spring的... 目录1. 什么是循环依赖?2. 循环依赖的场景案例3. 解决循环依赖的常见方法方法 1:使用 @La

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

Elasticsearch 在 Java 中的使用教程

《Elasticsearch在Java中的使用教程》Elasticsearch是一个分布式搜索和分析引擎,基于ApacheLucene构建,能够实现实时数据的存储、搜索、和分析,它广泛应用于全文... 目录1. Elasticsearch 简介2. 环境准备2.1 安装 Elasticsearch2.2 J

Java中的String.valueOf()和toString()方法区别小结

《Java中的String.valueOf()和toString()方法区别小结》字符串操作是开发者日常编程任务中不可或缺的一部分,转换为字符串是一种常见需求,其中最常见的就是String.value... 目录String.valueOf()方法方法定义方法实现使用示例使用场景toString()方法方法

Java中List的contains()方法的使用小结

《Java中List的contains()方法的使用小结》List的contains()方法用于检查列表中是否包含指定的元素,借助equals()方法进行判断,下面就来介绍Java中List的c... 目录详细展开1. 方法签名2. 工作原理3. 使用示例4. 注意事项总结结论:List 的 contain