JVM面试点汇总

我们会在这里介绍我所涉及到的JVM相关的面试点内容,本篇内容持续更新

我们会介绍下述JVM的相关面试点:

  • JVM内存结构
  • 内存溢出问题
  • 方法区与永久代和元空间
  • JVM内存参数
  • JVM垃圾回收算法
  • GC和分代回收算法
  • 类加载过程
  • 双亲委派
  • 对象调用类型

JVM内存结构

我们将会介绍JVM的整体内存结构的运行流程

JVM内存结构图

我们首先给出JVM的内存结构图:

JVM内存结构功能

我们针对上述图分别讲解功能部件:

/*Java Source*/

源代码(就是我们书写的代码)

/*Java Class*/

字节码(由源代码转变过来的,跨平台的关键)

/*类加载子系统*/

在正式执行代码前,我们需要先将代码使用的类和方法加载,类加载子系统就是做这个的

它会将类和方法加载到方法区

/*JVM Stacks 虚拟机栈 和 Native Method Stacks 本地方法栈*/

JVM Stacks 虚拟机栈:用来存放代码中所使用的我们所定义的方法,属性等局部变量

Native Method Stacks 本地方法栈:用于存放本地方法

但是上述栈只是定义,具体实现不一定需要分为两个栈,可以统一存放

/*PC Register 程序计数器*/

我们的程序线程并非一直分配CPU,当我们的线程CPU被剥夺后,我们需要记录继续运行时的继续位置

程序计数器用于记录当重新分配CPU后我们需要执行的下一行代码位置

/*堆*/

存储我们new出来的对象

/*方法区*/

方法区用于存放我们所使用的类和类方法

方法区只是定义,具体实现在不同JDK版本不同:7之前为永久代,8之后为元空间

/*GC垃圾回收*/

当我们的内存不足或处于一个需要清理的状态,我们调用GC垃圾回收清除掉目前不使用的数据

/*解释器*/

我们的字节码是不能直接执行的,我们需要先进行解释编译才能执行

/*即时编译器*/

正常情况下我们的字节码由解释器解释后执行,但会耗费一定时间

若使用次数较多,系统会使用即时编译器来编译字节码,并将其储存,每次直接调用,不经过解释器处理

内存溢出问题

我们将会介绍JVM的各部件的内存溢出问题

内存溢出问题

我们分别给出内存溢出的不同情况:

/*内存溢出区域*/

除了程序计数器其他区域均会出现内存溢出

/*OutMemoryError问题*/

问题产生原因:
1.堆内存耗尽:对象越来越多,且均在使用,无法进行GC
2.方法区内存耗尽:加载类越来越多,很多框架都会在执行时动态生成类
3.虚拟机栈累积:每个线程都会占用1M内存,当线程个数越来越多,长时间不销毁导致错误 /*StackOverflowError问题*/ 问题产生原因:
1.虚拟机内部:方法不断执行,执行次数过多

方法区与永久代和元空间

下面我们来介绍方法区与永久代和元空间问题

方法区与永久代和元空间

我们来介绍方法区与永久代和元空间的概念以及注意事项:

/*方法区*/

方法区只是JVM规范中定义的一块内存区域,用来存储类元数据,方法字节码,即时编译器需要的信息等

/*永久代*/

永久代是JDK1.7之前的方法区存放位置,最开始存放在常量池

/*元空间*/

元空间是JDK1.8之后的方法区存放位置,存放于堆中

/*注意点*/

元空间GC条件非常苛刻:
- 仅当堆中所有的类的对象全部清理之后,才能清理元空间数据

JVM内存参数

下面我们介绍一下面试中常考的JVM内存参数

JVM参数展示

我们根据分区展示JVM常用参数:

/*内存区域*/

-Xmx:最大内存

-Xms:最小内存

-Xmn:新生代内存(伊甸园+from+to)

-XX:Survivor:伊甸园和from 的比率(注意:实际划分应该是伊甸园-from-to:n:1:1)

/*元空间*/

元空间分为classspace(类基本信息)和non-classspace(类的注释,字节码等信息)

-XX:CompressedClassSpaceSize classspace最大内存空间
-xx:MaxMetaspaceSize 元空间最大内存空间 /*代码缓存*/ 当代码缓存空间小于240,全部都保存在code cache中 当代码缓存空间大于240,non-nmthods:JVM代码基本信息;profiled-nmthods:部分优化信息;non-profiled-nmthods:完整信息 -XX:ReservedCodeCacheSize 设置代码缓存区内存大小 /*线程*/ -Xss:线程占用内存大小,默认1M

JVM垃圾回收算法

下面我们介绍面试中常问的三种垃圾回收算法

标记操作

在开始前我们先回顾标记操作:

/*标记操作*/

1. 找到Root根对象(Root根对象就是一定不会被垃圾回收的对象,包括但不限于正在使用的对象,静态对象等)

2. 根据Root根对象向下蔓延,标记延申的对象,该类对象将不被回收

标记清除

我们简述标记清除操作:

/*标记清除操作*/

1. 先进行标记处理

2. 直接在内存中将未标记的数据清除(实际上就是标记为空白数据)

/*优缺点*/

1. 执行速度极快

2. 但会产生内存碎片,当内存碎片逐渐增多会导致问题

注意:基本不再使用该GC处理

标记整理

我们简述标记整理操作:

/*标记整理操作*/

1. 先进行标记处理

2. 将未标记的数据清除,同时将标记的数据重新排序,紧密排列

/*优缺点*/

1. 不会产生内存碎片

2. 耗时,需要重新复制粘贴数据更换位置

注意:该算法经常用于老年代的GC处理

标记复制

我们简述标记复制操作:

/*标记复制操作*/

1. 准备两块相同大小的区域,分为from和to区域,我们的信息都会存放在from区域

2. 首先对from区域进行标记处理

3. 直接将from区域的标记数据移动到to区域,按顺序排列,然后对调from和to区域

/*优缺点*/

1. 速度快,不会产生内存碎片

2. 占用双倍内存

注意:该算法经常用于新生代的GC处理

GC和分代回收算法

下面我们介绍GC和分代回收算法

GC概述

首先我们对GC做一个简单的总结:

/*GC目的*/

GC的目的在于实现无用对象内存自动释放,减少内存碎片,加快分配速度

/*GC要点*/

1. 回收区域是堆内存,不包括虚拟机栈,在方法调用结束会自动释放方法占用内存

2. 判断无用对象,使用可达性分析算法和三色标记法标记存活对象,回收未标记对象

3. GC的具体实现称为垃圾回收器

4. GC大多数都采用了分代回收思想,分为新生代和老年代,新生代又分为伊甸园,幸存区;不同区域有不同的回收策略

5. 根据GC规模可以分为Minor GC,Mixed GC,Full GC

/*GC不同规模*/

Minor GC:发生在新生代的垃圾回收,暂停时间短

Mixed GC:G1垃圾回收器特有,新生代整体垃圾回收,老年代部分垃圾回收,可以设置垃圾回收时间,当时间不足,系统会优先回收回收收益大的

Full GC:新生代和老年代的完整垃圾回收,暂停时间长,应极力避免

分代回收思想

我们首先来介绍分代回收思想:

/*分代回收区域*/

新生代:
伊甸园
幸存区
from
to 老年代 /*具体介绍*/ 新生代和老年代属于两块大区域 新生代:用于存储较新的数据,经过层层筛选进入老年代 伊甸园:新生代的一块区域,用于存放所有新进入的数据 幸存区:用于存放经过GC的数据,采用from,to也就是标记复制的方法保存 老年代:用于存储经过多次GC还未回收的数据,GC条件苛刻 /*跳转介绍*/ 新生代GC:
新生代的伊甸园内存塞满后,进行一次新生代的GC,进行筛选,将保存的数据放入幸存区的from
新生代第二次GC,同样筛选,注意伊甸园和幸存区都需要筛选,然后将保存的数据放入幸存区的to,然后调换from和to 新生代->老年代:
当新生代经过多次GC,数据经历了多次GC仍未被处理,且次数超过一个阈值,就放入老年代中
当新插入的数据过大,新生代无法存储,就直接放入老年代存储

三色标记和并发漏标问题

我们在标记过程中经常会采用三色标记法来标记:

/*三色标记法*/

黑色-已标记
灰色-标记中
白色-未标记 系统会统计Root,然后从Root往下延申,标记过的部分标记为黑色,正在标记的部分未灰色,直到所有Root走完 这时我们需要保留的数据为黑色,我们不需要的数据为白色,标记完成 /*处理并发漏标问题*/ 如果我们线程并发处理,我们在GC过程中,另一个线程调用了新的类,这时该类未被标记为黑色,就会导致将我们需要的数据删除 存在两种处理方式: 1.Incremental Update:
只要赋值发生,被赋值的对象就会被记录(类的Root),在三色标记结束后重新遍历记录的对象 2.Snapshot At The Beginning:
新加对象会被记录(类)
被删除引用关系的对象也会被记录
最后同样在三色标记结束后,也会全部遍历处理

垃圾回收器

我们介绍三种垃圾回收器:

/*Parallel GC*/

eden 内存不足时发生Minor GC,标记复制STW(STW:Stop The World,停止其他线程的操作)

old 内存不足时发生Full GC,标记整理STW

该垃圾回收器注意吞吐量

/*ConcurrentMarkSweep GC*/

old 并发标记,重新标记时需要STW,并发清除(并发操作时,其他线程不需要停止操作)

Failback Full GC:当垃圾回收失败时,存在保底策略Failback Full GC

该垃圾回收器注意响应时间

/*G1 GC*/

响应时间与吞吐量兼顾

划分为多个区域,每个区域都可以充当eden,survivor,old,humongous(存放大型数据的位置,减少复制操作,减少时间损耗)

新生代回收:eden内存不足,标记复制STW

并发标记:old 并发标记,重新标记时需要STW

混合收集:并发标记完成,开始混合收集,参与复制的有eden,survivor,old,其中old会根据暂停时间目标,选择部分回收价值高的区域,复制期间STW

Failback Full GC

类加载过程

我们下面来介绍一下类加载过程

类加载流程

类加载主要分为三个步骤:

/*加载*/

1. 将该类的字节码载入方法区,先创建类.class对象

2. 如果此类的父类没有加载,先加载父类

3. 加载是懒惰执行

/*链接*/

1. 验证-验证类是否符合Class规范,合法性,安全性检查

2. 准备-为static变量分配空间,设置默认值

3. 解析-将常量池的符号引用解析为直接引用

/*初始化*/

1. 执行静态代码块与非final静态变量的赋值

2. 初始化是懒惰执行

类加载解释

我们对上述流程的部分内容进行解释:

/*final值处理*/

针对final的基本类型的处理在类的声明阶段就已经进行赋值了(默认为常量)

我们如果直接在main方法中调用类的final的基本类型,既不会触发类初始化也不会触发类加载(直接从常量池取数据或者提前保存到底层)

/*静态变量处理*/

针对静态变量static的声明和分配空间都是在链接阶段进行,但只会赋默认值

针对final引用类型,静态变量static和static静态代码块的信息都是在初始化阶段才会赋值
(将其按从上到下的顺序保存到一个static方法中统一执行) 针对final引用类型和静态变量在main中引用的,都会触发类的加载和初始化 /*链接的符号引用变为直接引用*/ 在未进行链接前,我们的常量池和底层代码中都会保存类的符号引用(仅仅是一个占位符) 在链接之后,该占位符就会变成类的引用地址

双亲委派

下面我们来简单介绍一下双亲委派及相关面试点

双亲委派概述

我们首先介绍双亲委派:

/*双亲委派*/

针对类,优先委派上级类加载器进行加载:
1.当上级类加载器能找到这个类,由上级加载,加载后该类对下级加载器也可见
2.当上级类加载器找不到这个类,下级加载器才有资料加载该类

四种类加载器

我们来介绍四种类加载器:

名称 加载哪的类 说明
Bootstrap ClassLoader JAVA_HOME/jre/lib 无法直接访问
Extension ClassLoader JAVA_HOME/jre/lib/ext 上级为 Bootstrap,显示为 null
Application ClassLoader classpath 上级为 Extension
自定义类加载器 自定义 上级为 Application

我们简单介绍一下运行机制:

  • 首先我们需要知道Bootstrap ClassLoader 是不可访问的,当我们查找到该层级时会显示null
  • 我们如果需要加载一个类,会先检测是否有上级,如果有上级就到上级中去,如果没有就在本层查找是否有该类
  • 意思就是以最高级的加载器中的类为最高标准,如果同时存在多个类,我们会选择最高级的类加载器中的类来运行

双亲委派逻辑问题

我们提出一个简单的双亲委派相关的逻辑问题:

/*问题*/

我们能够自己编写类加载器来加载一个假冒的java.lang.System吗?

/*解题*/

不能

1.假设你编写的类加载器走双亲委派的流程,那么就会优先启动真正的Java.lang.System,不会加载自己书写的类加载器

2.假设你编写的类加载器不走双亲委派流程,那么你的类加载器加载到假冒的System时,需要先加载父类Java.lang.Object,但没有委派流程,所以你是找不到Objet类的

3.此外,在JDK9之后,针对特殊的包名Java.,Java.lang等都已经进行了警告提示,编写是不会被通过的

/*双亲委派目的*/

1.为了让上级类加载器的类作用于下级类加载器,即瓤你的类能够依赖到JDK提供的核心类

2.让类的加载有优先次序,保证核心类优先加载

对象引用类型

下面我们来介绍对象引用类型

四种常见对象引用类型

我们首先介绍四种常见的对象引用类型:

/*强引用*/

普通变量赋值即为强引用:A a = new A();

通过GC Root的引用链,如果强引用不到对象,该对象就可以被回收

/*软引用SoftReference*/

需要采用软引用对象连接真正的对象:SoftReference a = new SoftReference(new A());

如果仅有软引用引用该对象,则首次GC不会回收该对象,但GC过后若内存仍不足,该对象就会被回收

软引用对象本身需要利用引用队列ReferenceQueue来进行回收

典型例子是反射数据

/*弱引用WeakReference*/

需要采用弱引用对象连接真正的对象:WeakReference a = new WeakReferenec(new A());

如果仅有弱引用引用该对象,则只要发生GC,就直接回收该对象

弱引用对象本身需要利用引用队列ReferenceQueue来进行回收

典型例子是ThreadLocalMap中的Entry对象(key)

/*虚引用PhantomReference*/

虚引用也需要虚引用对象来连接真正的对象:PhantomReference a = new PhantomReference(new A());

该虚引用对象必须联合引用队列ReferenceQueue来执行,系统会一直检测是否存在虚引用对象,当引用的真正对象被回收后,虚引用对象就会被放置到ReferenceQueue中,由ReferenceHandler线程释放其关联的资源

典型例子是Cleaner释放DirectByteBuffer占用的直接内存

/*Cleaner的使用*/

1.创建一个Cleaner:
Cleaner cleaner = Cleaner.create(); 2.Cleaner使用:
Cleaner.register(对象,对象回收后方法); 其中,对象就是我们需要回收的对象;后面的方法我们可以采用Lambda表达式实现() -> {}

Finalize

Finalize也被称为终结器引用,其实是一种已经过时的引用类型:

/*Finalize认知*/

属于Object的方法,子类重写后,在该子类被垃圾回收后就会调用,可以执行一些资源释放和清理工作

/*深层认知*/

Finalize不适合完成资源释放和清理工作,因为十分影响性能,甚至严重时引起OOM

/*具体原因*/

// 方法不佳

1.FinalizerThread是守护线程,代码有可能还未执行就结束了,导致资源没有释放

2.Finalize会吞掉异常,我们无法判断资源释放过程中是否出现异常

// 影响性能

1.重写了Finalize的对象在第一次GC时不能被回收,会被FinalizerThread调用finalize方法,将他从unfinalized队列去除后才能释放

2.GC本身就是因为内存不足调用,但是Finalize由于调用过慢(串行执行,锁)导致不能及时释放内存,导致资源放入老年代,导致Full GC

// 质疑

1.Finalizer线程的优先级其实为8,相对而言比较高的,但是由于finalize执行过慢导致跟不上主线程的步伐

结束语

目前关于JVM的面试点就总结到这里,该篇文章会持续更新~

附录

参考资料:

  1. 黑马Java八股文面试题视频教程:虚拟机-01-jvm内存结构_代码执行流程_哔哩哔哩_bilibili

JVM面试点汇总的更多相关文章

  1. Java面试知识点汇总

    Java面试知识点汇总 置顶 2019年05月07日 15:36:18 温柔的谢世杰 阅读数 21623 文章标签: 面经java 更多 分类专栏: java 面试 Java面试知识汇总   版权声明 ...

  2. JVM 面试题汇总

    JVM 面试题汇总 1.什么是 JVM?它有什么作用? 答:JVM 是 Java Virtual Machine(Java 虚拟机)的缩写,顾名思义它是一个虚拟计算机,也是 Java 程序能够实现跨平 ...

  3. 2016年Web前端面试题目汇总

    转载: 2016年Web前端面试题目汇总 以下是收集一些面试中经常会遇到的经典面试题以及自己面试过程中未解决的问题,通过对知识的整理以及经验的总结,重新巩固自身的前端基础知识,如有错误或更好的答案,欢 ...

  4. 前端面试题目汇总摘录(JS 基础篇)

    JS 基础 JavaScript 的 typeof 返回那些数据类型 object number function boolean undefined string typeof null; // o ...

  5. jvm面试常见题

    背景:jvm相关题目面试必问,后面要深入的进行总结. JVM 面试知识整理 jvm调优命令 调优工具 Minor GC ,Full GC 触发条件 Minor GC触发条件:当Eden区满时,触发Mi ...

  6. JVM(七),JVM面试小知识

    七.JVM面试小知识 1.JVM三大性能调优参数 -Xms -Xmx -Xss 的含义 2.java内存模型中堆和栈的区别 3.不同JDK版本中的intern()方法的区别

  7. Java基础技术JVM面试【笔记】

    Java基础技术JVM面试[笔记] JVM JVM 对 java 类的使用总体上可以分为两部分:一是把静态的 class 文件加载到 JVM 内存,二是在 JVM 内存中进行 Java 类的生命周期管 ...

  8. 前端教程&开发模块化/规范化/工程化/优化&工具/调试&值得关注的博客/Git&面试-资源汇总

    内容精简 资源这么多,多看看多学习再总结肯定是好的.多读读就算看重了不算浪费时间,毕竟一千个读者就有一千个林黛玉,还有温故而知新,说不定多读一些内容,就发现惊喜了呢.不过,在此也精简一些内容,就1~2 ...

  9. 【转】Java方向如何准备BAT技术面试答案(汇总版)

    原文地址:http://www.jianshu.com/p/1f1d3193d9e3 这个主题的内容之前分三个篇幅分享过,导致网络上传播的比较分散,所以本篇做了一个汇总,同时对部分内容及答案做了修改, ...

  10. Java面试问题汇总

    转一些面试经验 刚看到下面这份面试清单,从个人的开发面试经历看,里面总结的大部分内容还是很不错的.年后想跳槽的朋友可以选取里面的问题准备一下. GitHub上的面试总结帖 Interview-Note ...

随机推荐

  1. MongoDB 单实例节点主机的用户和权限一般操作步骤

    步骤总结: 1.先正常启动 2.创建用户,创建数据库病授权用户 3.关闭程序,开启安全,然后启动 4.使用账号和密码连接 按未开启认证的方式(配置文件中没开启安全选项并且启动命令中不添加 --auth ...

  2. day46-JDBC和连接池02

    JDBC和连接池02 3.ResultSet[结果集] 基本介绍 表示数据库结果集的数据表,通常通过执行查询数据库的语句生成 ResultSet对象保持一个光标指向其当前的数据行,最初,光标位于第一行 ...

  3. bean文档类型定义

    ELEMENT:表示当前 (bean*):表示0到多个bean元素 (property*):表示0到多个property元素 ATTLIST:表示属性 #REQUIRED:表示不可缺少 #IMPLIE ...

  4. abstract关键字的使用

    1.abstract:抽象的 2.abstract可以用来修饰的结构:类.方法 3.abstract修饰类:抽象类 此类不能实例化 抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全 ...

  5. HTML基础知识(3)浮动、塌陷问题

    1.浮动 1.1 代码 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> & ...

  6. 齐博x1如何录制阿里等第三方直播流

    暂时只支持windows录制,可以在你本地电脑进行录制体验.点击下面的网址,下载录制程序文件,直接解压到任何一个目录都可以.里边同时包含自建直播服务器的功能,不用自建直播流的话,就可以忽视相关配置即可 ...

  7. 10.-ORM-基础字段及选项

    一.ORM-基础字段及选项 任何关于表结构的修改,务必在对应模型类上修改 例如 给 books_book表添加一个info字段 varchar(100) 1.在模型中添加对应的类属性 2.执行数据库迁 ...

  8. 驱动开发:内核监控FileObject文件回调

    本篇文章与上一篇文章<驱动开发:内核注册并监控对象回调>所使用的方式是一样的都是使用ObRegisterCallbacks注册回调事件,只不过上一篇博文中LyShark将回调结构体OB_O ...

  9. <一>从指令角度了解函数堆栈调用过程

    代码 点击查看代码 #include <iostream> using namespace std; int sum(int a,int b){ int temp=0; temp= a + ...

  10. Scrapy 发送Request Payload

    Scrapy 发送Request Payload 首先要打开 F12 进入调试模式 然后 查看是用什么方法获取的 如果是Json: 1. json.dumps 转化成Json yield Reques ...