2.4 JVM Flashcards

1
Q

Java对象的创建过程

A
  1. 类加载检查
    虚拟机遇到一条 new 指令时,首先将去检查这个指令的参数是否能在常量池中定位到 这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有,那 必须先执行相应的类加载过程。
  2. 分配内存
    在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类 加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分 配方式有 “指针碰撞” 和 “空闲列表” 两种,选择那种分配方式由 Java 堆是否规整决定,而Java堆是 否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。
  3. 初始化零值
    内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象 头),这一步操作保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用,程序能访问到这 些字段的数据类型所对应的零值。
  4. 设置对象头
    初始化零值完成之后,虚拟机要对对象进行必要的设置,例如这个对象是那个类的实 例、如何才能找到类的元数据信息、对象的哈希吗、对象的 GC 分代年龄等信息。这些信息存放在对 象头中。 另外,根据虚拟机当前运行状态的不同,如是否启用偏向锁等,对象头会有不同的设置方 式。
  5. 执行init方法
    在上面工作都完成之后,从虚拟机的视⻆来看,一个新的对象已经产生了,但从 Java 程序的视⻆来看,对象创建才刚开始, 方法还没有执行,所有的字段都还为零。所以一 般来说,执行 new 指令之后会接着执行 方法,把对象按照程序员的意愿进行初始化,这样 一个真正可用的对象才算完全产生出来。
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

简单的介绍一下强引用,软引用,弱引用,虚引用

A

强引用(StrongReference)
以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那 就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空 间不足,Java虚拟机宁愿抛出 OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

软引用(SoftReference)
如果一个对象只具有软引用,那就类似于可有可无的生活用品。如果内存空间足够,垃圾回收器就不会 回收它,如果内存空间不足了,就会回收这些对象的内存。只要垃圾回收器没有回收它,该对象就可以 被程序使用。软引用可用来实现内存敏感的高速缓存。
软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,JAVA 虚拟机就会把这个软引用加入到与之关联的引用队列中。

弱引用(WeakReference)
如果一个对象只具有弱引用,那就类似于可有可无的生活用品。弱引用与软引用的区别在于:只具有弱 引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现 了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一 个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。
弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java 虚拟机就会把这个弱引用加入到与之关联的引用队列中。

4.虚引用(PhantomReference)
“虚引用”顾名思义,就是形同虚设,与其他几种引用都不同,虚引用并不会决定对象的生命周期。如果
一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。

ref: https://juejin.im/post/6844903665241686029

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

如何判断对象是否死亡?(两种方法)

A

引用计数法

给对象中添加一个引用计数器,每当有一个地方引用它,计数器就加1;当引用失效,计数器就减1;任 何时候计数器为0的对象就是不可能再被使用的。

可达性分析算法

这个算法的基本思想就是通过一系列的称为 “GC Roots” 的对象作为起点,从这些节点开始向下搜索, 节点所走过的路径称为引用链,当一个对象到 GC Roots 没有任何引用链相连的话,则证明此对象是不 可用的。

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

2.4.9 如何判断一个类是无用的类?

A

判定一个常量是否是“废弃常量”比􏰀简单,而要判定一个类是否是“无用的类”的条件则相对苛刻许多。 类需要同时满足下面3个条件才能算是 “无用的类” :

  1. 该类所有的实例都已经被回收,也就是 Java 堆中不存在该类的任何实例。
  2. 加载该类的 ClassLoader 已经被回收。
  3. 该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类 的方法。

虚拟机可以对满足上述3个条件的无用类进行回收,这里说的仅仅是“可以”,而并不是和对象一样不使 用了就会必然被回收。

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

2.4.10 垃圾收集有哪些算法,各自的特点?

A

标记-清除算法
复制算法
标记-整理算法
分代收集算法

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

并行和并发概念区别

A

并行(Parallel)指物理上同时执行,

并发(Concurrent)指能够让多个任务在逻辑上交织执行的程序设计

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

常见垃圾回收器有哪些?

A
Serial
ParNew
Parallel Scavenge
CMS
G1
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

下面这个垃圾回收器原理是什么?使用什么垃圾收集算法?具体应用场景是什么?

Serial

A

Serial(串行)收集器收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是 一个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收 集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。

新生代采用复制算法,老年代采用标记-整理算法。

但是Serial收集器有没有优于其他垃圾收集器的地方呢?当然有,它简单而高效(与其他收集器的单线 程相比)。Serial收集器由于没有线程交互的开销,自然可以获得很高的单线程收集效率。Serial收集 器对于运行在Client模式下的虚拟机来说是个不错的选择。

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

下面这个垃圾回收器原理是什么?使用什么垃圾收集算法?具体应用场景是什么?

ParNew

A

ParNew收集器其实就是Serial收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和Serial收集器完全一样。

新生代采用复制算法,老年代采用标记-整理算法。

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

下面这个垃圾回收器原理是什么?使用什么垃圾收集算法?具体应用场景是什么?

Parallel Scavenge

A

Parallel Scavenge 收集器类似于ParNew 收集器。 那么它有什么特别之处呢?

Parallel Scavenge收集器关注点是吞吐量(高效率的利用CPU)。CMS等垃圾收集器的关注点更多的是 用户线程的停顿时间(提高用户体验)。所谓吞吐量就是CPU中用于运行用户代码的时间与CPU总消耗时 间的比值。 Parallel Scavenge收集器提供了很多参数供用户找到最合适的停顿时间或最大吞吐量,如 果对于收集器运作不太了解的话,手工优化存在的话可以选择把内存管理优化交给虚拟机去完成也是一 个不错的选择。

新生代采用复制算法,老年代采用标记-整理算法。

Stop the world. Can’t work with CMS.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

下面这个垃圾回收器原理是什么?使用什么垃圾收集算法?优缺点是什么?

CMS

A

CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它而非常符合在注重用户体验的应用上使用。

CMS(Concurrent Mark Sweep)收集器是HotSpot虚拟机第一款真正意义上的并发收集器,它第一次实 现了让垃圾收集线程与用户线程(基本上)同时工作。

从名字中的Mark Sweep这两个词可以看出,CMS收集器是一种 “标记-清除”算法实现的,它的运作过程 相比于前面几种垃圾收集器来说更加复杂一些。整个过程分为四个步骤:

  1. 初始标记: 暂停所有的其他线程,并记录下直接与root相连的对象,速度很快 ;
  2. 并发标记: 同时开启GC和用户线程,用一个闭包结构去记录可达对象。但在这个阶段结束,这 个闭包结构并不能保证包含当前所有的可达对象。因为用户线程可能会不断的更新引用域,所以 GC线程无法保证可达性分析的实时性。所以这个算法里会跟踪记录这些发生引用更新的地方。
  3. 重新标记: 重新标记阶段就是为了修正并发标记期间因为用户程序继续运行而导致标记产生变 动的那一部分对象的标记记录,这个阶段的停顿时间一般会比初始标记阶段的时间稍⻓,远远比 并发标记阶段时间短
  4. 并发清除: 开启用户线程,同时GC线程开始对为标记的区域做清扫。

从它的名字就可以看出它是一款优秀的垃圾收集器,主要优点:并发收集、低停顿。但是它有下面三个 明显的缺点:

  1. 对CPU资源敏感;
  2. 无法处理浮动垃圾;
  3. 它使用的回收算法-“标记-清除”算法会导致收集结束时会有大量空间碎片产生。
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

下面这个垃圾回收器原理是什么?使用什么垃圾收集算法?具体应用场景是什么?

G1

A

G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征.

G1收集器的运作大致分为以下几个步骤:

  1. 初始标记
  2. 并发标记
  3. 最终标记
  4. 筛选回收

G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region(这 也就是它的名字Garbage-First的由来)。这种使用Region划分内存空间以及有优先级的区域回收方式, 保证了GF收集器在有限时间内可以尽可能高的收集效率(把内存化整为零)。

它具备一下特点:
1. 并行与并发:
G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩 短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程执行的GC动作,G1收集器仍 然可以通过并发的方式让java程序继续执行。

  1. 分代收集:
    虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概 念。
  2. 空间整合:
    与CMS的“标记–清理”算法不同,G1从整体来看是基于“标记整理”算法实现的收集 器;从局部上来看是基于“复制”算法实现的。
  3. 可预测的停顿:
    这是G1相对于CMS的另一个大优势,降低停顿时间是G1 和 CMS 共同的关注点, 但G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个⻓度为M 毫秒的时间片段内。
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

知道类加载的过程吗?

A

类加载过程:
加载 -> 连接 -> 初始化。

连接过程又可分为三步:
验证 -> 准备 -> 解析。

加载:
指的是把class字节码文件从各个来源通过类加载器装载入内存中。

验证:
主要是为了保证加载进来的字节流符合虚拟机规范,不会造成安全错误。

准备:
主要是为类变量(注意,不是实例变量)分配内存,并且赋予初值。

解析:
将常量池内的符号引用替换为直接引用的过程。

初始化:
这个阶段主要是对类变量初始化,是执行类构造器的过程。
换句话说,只对static修饰的变量或语句进行初始化。

ref: https://zhuanlan.zhihu.com/p/33509426

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

JVM有哪些重要的类加载器?各自加载什么?

A
  1. BootstrapClassLoader(启动类加载器) :
    启动类加载器主要加载的是JVM自身需要的类,是虚拟机自身的一部分,负责加载 %JAVA_HOME%/lib 目录下的jar包和类或者或被 -Xbootclasspath 参数指定的路径中的所有类。
  2. ExtensionClassLoader(扩展类加载器) :
    主要负责加载目录 %JRE_HOME%/lib/ext 目录下的jar包和类,或被 java.ext.dirs 系统变量所指定的路径下的jar包。
  3. AppClassLoader(应用程序类加载器) :
    面向我们用户的加载器,负责加载当前应用classpath下的所有jar包和类。
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

双亲委派模型的原理是什么?有什么好处?

A

Parents Delegation Model

在类加载的时候,系统会首先判断当前类是否被加载过。已经被加载的类会直接返回,否则 才会尝试加载。加载的时候,首先会把该请求委派该父类加载器的 loadClass() 处理,因此所有的 请求最终都应该传送到顶层的启动类加载器 BootstrapClassLoader 中。当父类加载器无法处理 时,才由自己来处理。当父类加载器为null时,会使用启动类加载器 BootstrapClassLoader 作为 父类加载器。

自底向上检查类是否被加载
自顶向下尝试加载类

双亲委派模型保证了Java程序的稳定运行,可以避免类的重复加载(JVM 区分不同类的方式不仅仅根据 类名,相同的类文件被不同的类加载器加载产生的是两个不同的类),也保证了 Java 的核心 API 不被篡改。如果不用没有使用双亲委派模型,而是每个类加载器加载自己的话就会出现一些问题,比如我 们编写一个称为 java.lang.Object 类的话,那么程序运行的时候,系统就会出现多个不同的
Object 类。

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

在Java对象创建过程中,内存分配的方式有哪两种?

各自的适用场合,原理和对应的GC收集器有哪些?

A

在类加载检查通过后,接下来虚拟机将为新生对象分配内存。对象所需的内存大小在类 加载完成后便可确定,为对象分配空间的任务等同于把一块确定大小的内存从 Java 堆中划分出来。分配方式有 “指针碰撞” 和 “空闲列表” 两种,选择那种分配方式由 Java 堆是否规整决定,而Java堆是 否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

指针碰撞:
适合场合: 堆内存规整,没有碎片的情况
原理: 用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界值指针,只需要向着没用过的指针方向移动对象内存大小位置即可
GC收集器:Serial, ParNew

空闲列表:
适用场合:堆内存不规整的情况下
原理:虚拟机会维护一个列表,该列表会记录哪些内存块是可用的,在分配的时候,找一块足够大的内存块儿来划分给对象实例,最后更新列表
GC收集器:CMS

17
Q

2.4.3 对象的访问定位方式有哪两种?各自的原理和优点是什么?

A

建立对象就是为了使用对象,我们的Java程序通过栈上的 reference 数据来操作堆上的具体对象。对象的访问方式有虚拟机实现而定,目前主流的访问方式有1使用句柄和2直接指针两种:

  1. 句柄: 如果使用句柄的话,那么Java堆中将会划分出一块内存来作为句柄池,reference 中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自的具体地址信息;
  2. 直接指针: 如果使用直接指针访问,那么 Java 堆对象的布局中就必须考虑如何放置访问类型 数据的相关信息,而reference 中存储的直接就是对象的地址。

这两种对象访问方式各有优势。使用句柄来访问的最大好处是 reference 中存储的是稳定的句柄地 址,在对象被移动时只会改变句柄中的实例数据指针,而 reference 本身不需要修改。使用直接指针 访问方式最大的好处就是速度快,它节省了一次指针定位的时间开销。

18
Q

2.4.5 Minor Gc和Full GC 有什么不同呢?

A

新生代GC(Minor GC):
指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较􏰀快。

老年代GC(Major GC/Full GC):
指发生在老年代的GC,出现了Major GC经常会伴随至少一次的 Minor GC(并非绝对),Major GC的速度一般会比Minor GC的慢10倍以上。

19
Q

介绍下程序运行的内存区域有哪些东西?哪些是线程私有的?哪些是线程共享的?

能否手绘下内存区域的示意图?

A

线程私有的:
程序计数器
虚拟机栈
本地方法栈

线程共享的:

方法区
直接内存(非运行时数据区的一部分)

20
Q

什么是程序计数器?有哪两个作用?

A

程序计数器是一块􏰀小的内存空间。

主要有两个作用:

  1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、 选择、循环、异常处理。
  2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能 够知道该线程上次运行到哪儿了。
21
Q

什么是Java虚拟机栈?

A

Java 内存可以粗糙的区分为堆内存(Heap)和栈内存(Stack),其中栈就是现在说的虚拟机栈,或者说 是虚拟机栈中局部变量表部分。
局部变量表主要存放了编译器可知的各种数据类型(boolean、byte、char、short、int、float、 long、double)、对象引用(reference类型)。

22
Q

Java虚拟机栈会出现什么异常?什么时候会出现异常?

A

Java 虚拟机栈会出现两种异常:StackOverFlowError 和 OutOfMemoryError。

StackOverFlowError:
若Java虚拟机栈的内存大小不允许动态扩展,那么当线程请求栈的深度 超过当前Java虚拟机栈的最大深度的时候,就抛出StackOverFlowError异常。

OutOfMemoryError:
若 Java 虚拟机栈的内存大小允许动态扩展,且当线程请求栈时内存用完 了,无法再动态扩展了,此时抛出OutOfMemoryError异常。

23
Q

什么是(内存)堆?作用是什么?和垃圾收集器的关系?

A

Java 虚拟机所管理的内存中最大的一块,此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数组都在这里分配内存。

Java 堆是垃圾收集器管理的主要区域,因此也被称作GC堆。

24
Q

什么是 方法区?

A

方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

JDK 1.8 的时候,方法区(HotSpot的永久代)被彻底移除了(JDK1.7就已经开始了),取而代之是元
空间,元空间使用的是直接内存。

25
Q

为什么要将 方法区 替换为 元空间?

A

整个方法区有一个 JVM 本身设置固定大小上线,无法进行调整,而元空间使用的是直接内存,受本机 可用内存的限制,并且永远不会得到java.lang.OutOfMemoryError。

26
Q

什么是运行时常量池?常量池包含里哪些内容?

A

运行时常量池是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述信息外,还 有常量池信息(用于存放编译期生成的各种字面量和符号引用)

JDK1.7及之后版本的 JVM 已经将运行时常量池从方法区中移了出来,在 Java 堆(Heap)中开辟了一 块区域存放运行时常量池。

包含 字面量 和 符号引用。

27
Q

什么是 本地方法栈?

A

和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。

28
Q

既然有了字节流,为什么还要有字符流?

A

问题本质想问:不管是文件读写还是网络发送接收,信息的最小存储单元都是字节, 那为什么 I/O 流操作要分为字节流操作和字符流操作呢?”

字符流是由 Java 虚拟机将字节转换得到的,问题就出在这个过程还算是非常耗时,并且,如果我 们不知道编码类型就很容易出现乱码问题。所以, I/O 流就干脆提供了一个直接操作字符的接口, 方便我们平时对字符进行流操作。如果音频文件、图片等媒体文件用字节流比􏰀好,如果涉及到字 符的话使用字符流比􏰀好。