2.4 JVM Flashcards
Java对象的创建过程
- 类加载检查
虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到 这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那 必须先执行相应的类加载过程。 - 分配内存
在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类 加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分 配方式有 “指针碰撞” 和 “空闲列表” 两种,选择那种分配方式由 Java 堆是否规整决定,而Java堆是 否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。 - 初始化零值
内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象 头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这 些字段的数据类型所对应的零值。 - 设置对象头
初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实 例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。这些信息存放在对 象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方 式。 - 执行init方法
在上面工作都完成之后,从虚拟机的视⻆来看,一个新的对象已经产生了,但从 Java 程序的视⻆来看,对象创建才刚开始, 方法还没有执行,所有的字段都还为零。所以一 般来说,执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化,这样 一个真正可用的对象才算完全产生出来。
简单的介绍一下强引用,软引用,弱引用,虚引用
强引用(StrongReference)
以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那 就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出 OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。
软引用(SoftReference)
如果一个对象只具有软引用,那就类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会 回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以 被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。
弱引用(WeakReference)
如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱 引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现 了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一 个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。
4.虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果
一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。
ref: https://juejin.im/post/6844903665241686029
如何判断对象是否死亡?(两种方法)
引用计数法
给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任 何时候计数器为0的对象就是不可能再被使用的。
可达性分析算法
这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索, 节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不 可用的。
2.4.9 如何判断一个类是无用的类?
判定一个常量是否是“废弃常量”比简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。 类需要同时满足下面3个条件才能算是 “无用的类” :
- 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
- 加载该类的 ClassLoader 已经被回收。
- 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类 的方法。
虚拟机可以对满足上述3个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使 用了就会必然被回收。
2.4.10 垃圾收集有哪些算法,各自的特点?
标记-清除算法
复制算法
标记-整理算法
分代收集算法
并行和并发概念区别
并行(Parallel)指物理上同时执行,
并发(Concurrent)指能够让多个任务在逻辑上交织执行的程序设计
常见垃圾回收器有哪些?
Serial ParNew Parallel Scavenge CMS G1
下面这个垃圾回收器原理是什么?使用什么垃圾收集算法?具体应用场景是什么?
Serial
Serial(串行)收集器收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是 一个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收 集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。
新生代采用复制算法,老年代采用标记-整理算法。
但是Serial收集器有没有优于其他垃圾收集器的地方呢?当然有,它简单而高效(与其他收集器的单线 程相比)。Serial收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial收集 器对于运行在Client模式下的虚拟机来说是个不错的选择。
下面这个垃圾回收器原理是什么?使用什么垃圾收集算法?具体应用场景是什么?
ParNew
ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和Serial收集器完全一样。
新生代采用复制算法,老年代采用标记-整理算法。
下面这个垃圾回收器原理是什么?使用什么垃圾收集算法?具体应用场景是什么?
Parallel Scavenge
Parallel Scavenge 收集器类似于ParNew 收集器。 那么它有什么特别之处呢?
Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)。CMS等垃圾收集器的关注点更多的是 用户线程的停顿时间(提高用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时 间的比值。 Parallel Scavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如 果对于收集器运作不太了解的话,手工优化存在的话可以选择把内存管理优化交给虚拟机去完成也是一 个不错的选择。
新生代采用复制算法,老年代采用标记-整理算法。
Stop the world. Can’t work with CMS.
下面这个垃圾回收器原理是什么?使用什么垃圾收集算法?优缺点是什么?
CMS
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用。
CMS(Concurrent Mark Sweep)收集器是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实 现了让垃圾收集线程与用户线程(基本上)同时工作。
从名字中的Mark Sweep这两个词可以看出,CMS收集器是一种 “标记-清除”算法实现的,它的运作过程 相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:
- 初始标记: 暂停所有的其他线程,并记录下直接与root相连的对象,速度很快 ;
- 并发标记: 同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这 个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
- 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变 动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍⻓,远远比 并发标记阶段时间短
- 并发清除: 开启用户线程,同时GC线程开始对为标记的区域做清扫。
从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面三个 明显的缺点:
- 对CPU资源敏感;
- 无法处理浮动垃圾;
- 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
下面这个垃圾回收器原理是什么?使用什么垃圾收集算法?具体应用场景是什么?
G1
G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征.
G1收集器的运作大致分为以下几个步骤:
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region(这 也就是它的名字Garbage-First的由来)。这种使用Region划分内存空间以及有优先级的区域回收方式, 保证了GF收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。
它具备一下特点:
1. 并行与并发:
G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩 短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍 然可以通过并发的方式让java程序继续执行。
- 分代收集:
虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概 念。 - 空间整合:
与CMS的“标记–清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集 器;从局部上来看是基于“复制”算法实现的。 - 可预测的停顿:
这是G1相对于CMS的另一个大优势,降低停顿时间是G1 和 CMS 共同的关注点, 但G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个⻓度为M 毫秒的时间片段内。
知道类加载的过程吗?
类加载过程:
加载 -> 连接 -> 初始化。
连接过程又可分为三步:
验证 -> 准备 -> 解析。
加载:
指的是把class字节码文件从各个来源通过类加载器装载入内存中。
验证:
主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。
准备:
主要是为类变量(注意,不是实例变量)分配内存,并且赋予初值。
解析:
将常量池内的符号引用替换为直接引用的过程。
初始化:
这个阶段主要是对类变量初始化,是执行类构造器的过程。
换句话说,只对static修饰的变量或语句进行初始化。
ref: https://zhuanlan.zhihu.com/p/33509426
JVM有哪些重要的类加载器?各自加载什么?
- BootstrapClassLoader(启动类加载器) :
启动类加载器主要加载的是JVM自身需要的类,是虚拟机自身的一部分,负责加载 %JAVA_HOME%/lib 目录下的jar包和类或者或被 -Xbootclasspath 参数指定的路径中的所有类。 - ExtensionClassLoader(扩展类加载器) :
主要负责加载目录 %JRE_HOME%/lib/ext 目录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的jar包。 - AppClassLoader(应用程序类加载器) :
面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类。
双亲委派模型的原理是什么?有什么好处?
Parents Delegation Model
在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则 才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 loadClass() 处理,因此所有的 请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader 中。当父类加载器无法处理 时,才由自己来处理。当父类加载器为null时,会使用启动类加载器 BootstrapClassLoader 作为 父类加载器。
自底向上检查类是否被加载
自顶向下尝试加载类
双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据 类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果不用没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我 们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的
Object 类。