【面试】Java JVM篇(一)

0、题目大纲

1、强引用、软引用、弱引用、幻象引用有什么区别?【第4讲】

2、请介绍类加载过程,什么是双亲委派模型?【第23讲】

3、谈谈JVM内存区域的划分,哪些区域可能发生OutOfMemoryError?【第25讲】

4、Java常见的垃圾收集器有哪些?【第27讲】
 - 追问1:说说垃圾收集大概过程?
 - 追问2:常见垃圾收集算法有哪些?【理解原理和优缺点足够】
 - 追问3:新生代为什么用复制算法?(*1- 
5、谈谈你的GC调优思路?【第28讲】

6、Java内存模型中的happen-before是什么?【第29讲】

7、你用过哪些JVM参数?

一、内容

1、强引用、软引用、弱引用、幻象引用有什么区别?【第4讲】

主要是对象可达性和对垃圾收集的影响

  • 1)强引用是指向一个对象,就表明对象还“活着”,垃圾收集器不会碰。 普通的对象若没引用关系,只要超过引用作用域或显式赋值为null,就可以被垃圾收集,具体回收时机要看垃圾收集策略。

  • 2)软引用可以让对象豁免一些垃圾收集,只有当 JVM 认为内存不足才会去试图回收。 JVM 在抛出 OutOfMemoryError 前会清理。软引用常用来实现内存敏感的缓存,内存空闲会暂时保留缓存,不足清理。

  • 3)弱引用不能使对象豁免垃圾收集,仅提供一种访问对象的途径。 用来构建一种没有特定约束的关系,比如,维护非强制性映射关系,若试图获取时,对象在,就使用,不在重现实例化。

  • 4)幻象/虚引用,不能通过它访问对象,仅提供对象被 finalize 后,做某些事情的机制,如监控对象的创建和销毁、Post-Mortem 清理和、Cleaner 机制。

2、请介绍类加载过程,什么是双亲委派模型?【第23讲】

类加载过程:3个主要步骤:加载、链接、初始化

  • 1)加载:将字节码从不同数据源读到 JVM 并映射为数据结构(Class 对象)。数据源可能是各种形态,如 jar 文件、class 文件、网络数据源等;如果不是 ClassFile 结构会抛出 ClassFormatError;可自定义类加载器。

  • 2)链接:[核心步骤] 把原始类定义信息平滑转化入 JVM 运行的过程中。可细分为三个步骤:

  • 验证:核验字节信息是否符合规范,不符被认为 VerifyError。防止恶意信息或不合规信息危害 JVM 的运行,验证阶段有可能触发更多 class 的加载。
  • 准备:创建类或接口中的静态变量,并初始化静态变量的值。[ 分配所需要的内存空间,不会执行进一步 JVM 指令。]
  • 解析:将常量池的符号引用替换为直接引用。在Java 虚拟机规范中,详细介绍了类、接口、方法和字段等各个方面的解析。
  • 3)初始化:执行类初始化的代码,包括静态字段赋值、类定义静态初始化块内,编译器在编译阶段就会整理好这些,父类型比当前类型的初始化逻辑优先。

双亲委派模型:当类加载器试图加载某个类型时除非父加载器找不到相应类型,否则尽量将这个任务代理给当前加载器的父加载器去做。目的是避免重复加载 Java 类型。

3、谈谈JVM内存区域的划分,哪些区域可能发生OutOfMemoryError?【第25讲】

JVM内存区域

1. 所有线程共享数据区有堆、方法区。

扫描二维码关注公众号,回复: 12816307 查看本文章
  • 1)堆:内存管理的核心区域。放置 (几乎所有创建的)Java 对象实例。
    堆也是垃圾收集器重点照顾区域,堆内空间被不同垃圾收集器进一步细分,有名的划分是新生代、老年代。

  • 2)方法区:存储元(Meta)数据,如类结构信息、对应运行时常量池、字段、方法代码等。
    由Hotspot JVM 实现,习惯将方法区称为永久代。Oracle JDK 8 中将永久代移除,同时增加了元数据区(Metaspace)。

    • 运行时常量池:方法区的一部分。存放各种常量信息,不管是编译期生成的各种字面量,还是运行时决定的符号引用,比一般语言符号表存储信息更加宽泛。

2. 线程隔离的数据区有程序计数器、虚拟机栈、本地方法栈。

  • 1)程序计数器:在 JVM 规范中,每个线程都有自己的程序计数器,并且任何时间一个线程都只有一个方法在执行,即当前方法。程序计数器会存储当前线程正执行方法的 JVM 指令地址;或是执行本地方法的未指定值(undefined)。

  • 2)虚拟机栈:每个线程在创建时都会创建一个虚拟机栈,内部保存一个个栈帧(Stack ,对应着Java 方法调用。

  • 3)本地方法栈:和虚拟机栈非常相似,支持本地方法调用,每个线程都创建一个。
    简单内存结构图

4、Java常见的垃圾收集器有哪些?【第27讲】

3

垃圾收集器名称 使用算法 适用年龄代 特性
Serial 复制算法 新生代 单线程,Client模式下的默认新生代收集器
ParNew 复制算法 新生代 Serial收集器的多线程版本,Server模式下的首选新生代收集器,常配合老年代的CMS GC工作
Parallel Scavenge 复制算法 新生代 多线程,吞吐率优先
Serial Old 标记 - 整理 老年代 单线程,主要在Client模式下使用
Parallel Old 标记 - 整理(和Serial类似,实现更复杂) 老年代 多线程,吞吐量优先
CMS 标记 - 清除 老年代 多线程, 停顿时间短,响应速度快
G1 - - 可预期的GC停顿周期,分代收集,内存碎片整理
G1 GC:仍然存在着年代的概念,但是其内存结构并不是简单的条带式划分,而是类似棋盘的一个个 region。Region 之间是复制算法,但整体上实际可看作是标记 - 整理(Mark-Compact)算法,

CMS GC:常用于Web。存在内存碎片化问题,难以避免长时间运行等情况发生full GC,影响并发,占用CPU资源,和用户线程争抢
追问1:说说垃圾收集大概过程?

4

过程:
1、创建对象分配到Eden区域,空间占用达到阈值,触发minor GC;
2、再次minor GC后,Eden区域存活存活对象都会复制到to区域,存活年龄+13、第 2 步发生多次,年龄超过阈值的对象会晋升到老年代;
4、老年代GC[不同GC有差异]:老年代无用对象清除后会整理对象,防止内存碎片化。

1
2

追问2:常见垃圾收集算法有哪些?【理解原理和优缺点足够】

主要分三类:

  • 1)复制(Copying)算法:将活着对象复制到 to 区域,拷贝过程中将对象顺序放置,避免内存碎片化。代价是要提前预留内存空间,有一定浪费;另外,对G1 这种分拆成为大量 region 的 GC,复制而不是移动,意味着要维护 region 之间对象引用关系,内存和时间开销不小。(新生代 GC基本都基于复制算法)

  • 2)标记 - 清除(Mark-Sweep):先进行标记工作,标识出所有要回收的对象,然后进行清除。这么做除了标记、清除过程效率有限,另外就是不可避免的出现碎片化问题,不适合特别大的堆,因为一旦出现 Full GC,暂停时间可能无法接受。

  • 3)标记 - 整理(Mark-Compact):类似于标记 - 清除,但为避免内存碎片化,它会在清理过程中将对象移动,以确保移动后的对象占用连续的内存空间。

追问3:新生代为什么用复制算法?(*1)

拷贝过程中将对象顺序放置,避免内存碎片化。

5、谈谈你的GC调优思路?【第28讲】

调优思路总结:

  • 1)理解需求,确定目标。
性能考虑:内存占用(footprint)、延时(latency)和吞吐量(throughput)。大多数会侧重1-2方面的目标。其他如OOM 可能与不合理 GC 参数有关;或应用启动速度方面需求。
  • 2)确认必要性,定位问题。 掌握 JVM 和 GC 状态,确认GC 调优必要,定位具体问题。
1、假设,我们开发了一个应用服务,但发现偶尔会出现性能抖动,出现较长的服务停顿。评估用户可接受的响应时间和业务量,将目标简化为,希望 GC 暂停尽量控制在 200ms 以内,并且保证一定标准的吞吐量。
2、具体方法如 jstat 等工具查看 GC 状态,开启 GC 日志,或用操作系统提供诊断工具等。例如,通过追踪 GC 日志,就可以查找是不是 GC 在特定时间发生了长时间的暂停,进而导致了应用响应不及时。
3、需要思考选择GC 类型是否符合应用特征,如果是,具体问题表现在哪里,是 Minor GC 过长,还是 Mixed GC 等出现异常停顿情况;如果不是,考虑切换到什么类型,如 CMS 和 G1 都是更侧重于低延迟的 GC 选项。
  • 3)分析调整,验证目标。 分析问题,确定调整参数或软硬件配置;验证是否达到调优目标,是则结束,否则重复分析、调整、验证过程。

6、Java内存模型中的happen-before是什么?【第29讲】

真正含义即关闭所有的编译器、操作系统和处理器的优化,所有指令顺序全部按照程序代码书写的顺序执行。 去掉CPU高速缓存,让CPU的每次读写操作都直接与主存交互。

……

补充:Happen-before规则内容

程序次序规则:在单线程中,代码的执行是有序的,虽然可能会存在运行指令的重排序,但最终执行的结果和顺序执行的结果是一致的;

锁定规则:一个锁处于被一个线程锁定占用状态,那么只有当这个线程释放锁之后,其它线程才能再次获取锁操作;

volatile 变量规则:如果一个线程正在写 volatile 变量,其它线程读取该变量会发生在写入之后;

线程启动规则:Thread 对象的 start() 方法先行发生于此线程的其它每一个动作;

线程终结规则:线程中的所有操作都先行发生于对此线程的终止检测;

对象终结规则:一个对象的初始化完成先行发生于它的 finalize() 方法的开始;

传递性:如果操作 A happens-before 操作 B,操作 B happens-before 操作 C,那么操作 A happens-before 操作 C;

线程中断规则:对线程 interrupt() 方法的调用先行发生于被中断线程的代码检测到中断事件的发生。

7、你用过哪些JVM参数?

JVM参数 作用
-Xms 堆的最小空间大小
-Xmx 设置堆最大空间大小
-XX:NewSize 设置新生代最小空间大小
-XX:MaxNewSize 设置新生代最小空间大小

二、参考

1、被说烂了的Java垃圾回收算法,我带来了最“清新脱俗”的详细图解
2、可见性有序性,Happens-before来搞定
3、从Java多线程可见性谈Happens-Before原则
4、Java 内存区域概述
5、1.JVM中的五大内存区域划分详解及快速扫盲
6、深入理解JVM(1) : Java内存区域划分
7、JVM-新生代垃圾回收算法:复制算法

猜你喜欢

转载自blog.csdn.net/HeavenDan/article/details/112614161
今日推荐