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讲】
1. 所有线程共享数据区有堆、方法区。
-
1)堆:内存管理的核心区域。放置 (几乎所有创建的)Java 对象实例。
堆也是垃圾收集器重点照顾区域,堆内空间被不同垃圾收集器进一步细分,有名的划分是新生代、老年代。 -
2)方法区:存储元(Meta)数据,如类结构信息、对应运行时常量池、字段、方法代码等。
由Hotspot JVM 实现,习惯将方法区称为永久代。Oracle JDK 8 中将永久代移除,同时增加了元数据区(Metaspace)。 -
- 运行时常量池:方法区的一部分。存放各种常量信息,不管是编译期生成的各种字面量,还是运行时决定的符号引用,比一般语言符号表存储信息更加宽泛。
2. 线程隔离的数据区有程序计数器、虚拟机栈、本地方法栈。
-
1)程序计数器:在 JVM 规范中,每个线程都有自己的程序计数器,并且任何时间一个线程都只有一个方法在执行,即当前方法。程序计数器会存储当前线程正执行方法的 JVM 指令地址;或是执行本地方法的未指定值(undefined)。
-
2)虚拟机栈:每个线程在创建时都会创建一个虚拟机栈,内部保存一个个栈帧(Stack ,对应着Java 方法调用。
-
3)本地方法栈:和虚拟机栈非常相似,支持本地方法调用,每个线程都创建一个。
4、Java常见的垃圾收集器有哪些?【第27讲】
垃圾收集器名称 | 使用算法 | 适用年龄代 | 特性 |
---|---|---|---|
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:说说垃圾收集大概过程?
过程:
1、创建对象分配到Eden区域,空间占用达到阈值,触发minor GC;
2、再次minor GC后,Eden区域存活存活对象都会复制到to区域,存活年龄+1;
3、第 2 步发生多次,年龄超过阈值的对象会晋升到老年代;
4、老年代GC[不同GC有差异]:老年代无用对象清除后会整理对象,防止内存碎片化。
追问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-新生代垃圾回收算法:复制算法