JVM面试点汇总

JVM面试点汇总

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

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

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

JVM内存结构

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

JVM内存结构图

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

JVM面试点汇总

JVM内存结构功能

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

/*Java Source*/

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

/*Java Class*/

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

/*类加载子系统*/

<details><summary>*<font color='gray'>[En]</font>*</summary>*<font color='gray'></font>*</details>

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

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

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

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

<details><summary>*<font color='gray'>[En]</font>*</summary>*<font color='gray'></font>*</details>

/*PC Register 程序计数器*/

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

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

/*堆*/

存储我们new出来的对象

/*方法区*/

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

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

/*GC垃圾回收*/

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

/*解释器*/

<details><summary>*<font color='gray'>[En]</font>*</summary>*<font color='gray'></font>*</details>

/*即时编译器*/

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

<details><summary>*<font color='gray'>[En]</font>*</summary>*<font color='gray'></font>*</details>

内存溢出问题

我们将会介绍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走完

<details><summary>*<font color='gray'>[En]</font>*</summary>*<font color='gray'></font>*</details>

/*处理并发漏标问题*/

如果我们线程并发处理,我们在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中引用的,都会触发类的加载和初始化

/*链接的符号引用变为直接引用*/

<details><summary>*<font color='gray'>[En]</font>*</summary>*<font color='gray'></font>*</details>

在链接之后,该占位符就会变成类的引用地址

双亲委派

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

双亲委派概述

我们首先介绍双亲委派:

/*双亲委派*/

针对类,优先委派上级类加载器进行加载:
    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
*[En]*

**

*[En]*

**

双亲委派逻辑问题

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

/*问题*/

我们能够自己编写类加载器来加载一个假冒的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

Original: https://www.cnblogs.com/qiuluoyuweiliang/p/16943334.html
Author: qiuluoyuweiliang
Title: JVM面试点汇总



相关阅读

Title: spring中InitializingBean的使用

1、InitializingBean接口

InitializingBean接口中只包含一个afterPropertiesSet()方法,继承该接口的类,在初始化bean的时候都会执行该方法,且只执行一次
测试如下

spring中InitializingBean的使用
package com.rookie.bigdata.Initializingbean;

import org.springframework.beans.factory.InitializingBean;

/**
 * @author
 * @date 2019/5/22
 */
public class AfterInitializingBean implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {

        System.out.println("&#x6267;&#x884C;afterPropertiesSet()&#x65B9;&#x6CD5;");
    }

    public void init() {
        System.out.println("&#x6267;&#x884C;&#x4E86;init&#x7684;&#x65B9;&#x6CD5;");
    }
}

application-bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemalocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
        http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd">

    <bean id="AfterInitializingBean" class="com.rookie.bigdata.Initializingbean.AfterInitializingBean" init-method="init"></bean>

</beans>

测试类及测试结果

    @Test
    public void afterPropertiesSet() throws Exception {

        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("com/rookie/bigdata/Initializingbean/application-bean.xml");
    }

spring中InitializingBean的使用
在spring初始化bean的时候,如果该bean是实现了InitializingBean接口,并且同时在配置文件中指定了init-method,系统则是先调用afterPropertiesSet方法,然后在调用init-method中指定的方法

2、AbstractAutowireCapableBeanFactory

出现上面结果的原因为在加载bean的时候执行了AbstractAutowireCapableBeanFactory类中的invokeInitMethods()方法

protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
            throws Throwable {

            //&#x5224;&#x65AD;&#x7ED9;bean&#x5BF9;&#x8C61;&#x662F;&#x5426;&#x5B9E;&#x73B0;&#x4E86;InitializingBean&#xFF0C;&#x5982;&#x679C;&#x5B9E;&#x73B0;&#x4E86;&#x8BE5;&#x63A5;&#x53E3;&#xFF0C;&#x5C31;&#x8C03;&#x7528;afterPropertiesSet&#x65B9;&#x6CD5;
        boolean isInitializingBean = (bean instanceof InitializingBean);
        if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
            if (logger.isTraceEnabled()) {
                logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
            }
            if (System.getSecurityManager() != null) {
                try {
                    AccessController.doPrivileged((PrivilegedExceptionAction<object>) () -> {
            //&#x8C03;&#x7528;afterPropertiesSet&#x65B9;&#x6CD5;
                        ((InitializingBean) bean).afterPropertiesSet();
                        return null;
                    }, getAccessControlContext());
                }
                catch (PrivilegedActionException pae) {
                    throw pae.getException();
                }
            }
            else {
            //&#x8C03;&#x7528;afterPropertiesSet&#x65B9;&#x6CD5;
                ((InitializingBean) bean).afterPropertiesSet();
            }
        }

        if (mbd != null && bean.getClass() != NullBean.class) {
            String initMethodName = mbd.getInitMethodName();
            //&#x5224;&#x65AD;&#x662F;&#x5426;&#x6307;&#x5B9A;&#x4E86;init-method&#x65B9;&#x6CD5;&#xFF0C;&#x5982;&#x679C;&#x6307;&#x5B9A;&#x4E86;init-method&#x65B9;&#x6CD5;&#xFF0C;&#x5219;&#x518D;&#x8C03;&#x7528;&#x5236;&#x5B9A;&#x7684;init-method
            if (StringUtils.hasLength(initMethodName) &&
                    !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
                    !mbd.isExternallyManagedInitMethod(initMethodName)) {
                invokeCustomInitMethod(beanName, bean, mbd);
            }
        }
    }
</object>

3、总结

1、spring执行的时候,首先会调用afterPropertiesSet方法,其次当配置文件中配置了init-method,会调用init-method指定的方法
2、spring执行的时候,首先会调用afterPropertiesSet方法,当配置文件中配置了init-method,且方法为afterPropertiesSet时,该方法只调用一次

Original: https://www.cnblogs.com/haizhilangzi/p/10907892.html
Author: 海之浪子
Title: spring中InitializingBean的使用

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/222823/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

最近整理资源【免费获取】:   👉 程序员最新必读书单  | 👏 互联网各方向面试题下载 | ✌️计算机核心资源汇总