乐趣区

关于java:连肝三个通宵JVM77道高频面试题详细分析就这

为不便大家记忆,记得珍藏加关注哦,须要下载 PDF 版本请在公众号【程序员空间】回复“材料”即可获取下载方式,你也能够 点在文末微信扫描二维码关注!

1、java 中会存在内存透露吗,请简略形容。

会。本人实现堆载的数据结构时有可能会呈现内存泄露。

2、64 位 JVM 中,int 的长度是少数?

Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。意思就是说,在 32 位 和 64 位 的 Java 虚拟机中,int 类型的长度是雷同的。

3、Serial 与 Parallel GC 之间的不同之处?

Serial 与 Parallel 在 GC 执行的时候都会引起 stop-the-world。它们之间次要不同 serial 收集器是默认的复制收集器,执行 GC 的时候只有一个线程,而 parallel 收集器应用多个 GC 线程来执行。

4、32 位和 64 位的 JVM,int 类型变量的长度是少数?

32 位和 64 位的 JVM 中,int 类型变量的长度是雷同的,都是 32 位或者 4 个字节。

5、Java 中 WeakReference 与 SoftReference 的区别?

尽管 WeakReference 与 SoftReference 都有利于进步 GC 和 内存的效率,然而 WeakReference,一旦失去最初一个强援用,就会被 GC 回收,而软援用尽管不能阻止被回收,然而能够提早到 JVM 内存不足的时候。

6、JVM 选项 -XX:+UseCompressedOops 有什么作用?为什么要应用

当你将你的利用从 32 位的 JVM 迁徙到 64 位的 JVM 时,因为对象的指针从 32 位减少到了 64 位,因而堆内存会忽然减少,差不多要翻倍。这也会对 CPU 缓存(容量比内存小很多)的数据产生不利的影响。因为,迁徙到 64 位的 JVM 次要动机在于能够指定最大堆大小,通过压缩 OOP 能够节俭肯定的内存。通过 -XX:+UseCompressedOops 选项,JVM 会应用 32 位的 OOP,而不是 64 位的 OOP。

7、怎么通过 Java 程序来判断 JVM 是 32 位 还是 64 位?

你能够查看某些零碎属性如 sun.arch.data.model 或 os.arch 来获取该信息。

8、32 位 JVM 和 64 位 JVM 的最大堆内存别离是少数?

实践上说上 32 位的 JVM 堆内存能够达到 2^32,即 4GB,但实际上会比这个小很多。不同操作系统之间不同,如 Windows 零碎大概 1.5GB,Solaris 大概 3GB。64 位 JVM 容许指定最大的堆内存,实践上能够达到 2^64,这是一个十分大的数字,实际上你能够指定堆内存大小到 100GB。甚至有的 JVM,如 Azul,堆内存到 1000G 都是可能的。

9、JRE、JDK、JVM 及 JIT 之间有什么不同?

JRE 代表 Java 运行时(Java run-time),是运行 Java 援用所必须的。JDK 代表 Java 开发工具(Java development kit),是 Java 程序打开发工具,如 Java 编译器,它也蕴含 JRE。JVM 代表 Java 虚拟机(Java virtual machine),它的责任是运行 Java 利用。JIT 代表即时编译(Just In Time compilation),当代码执行的次数超过肯定的阈值时,会将 Java 字节码转换为本地代码,如,次要的热点代码会被准换为本地代码,这样无利大幅度提高 Java 利用的性能。

10、解释 Java 堆空间及 GC?

当通过 Java 命令启动 Java 过程的时候,会为它分配内存。内存的一部分用于创立堆空间,当程序中创建对象的时候,就从对空间中分配内存。GC 是 JVM 外部的一个过程,回收有效对象的内存用于未来的调配。

11、JVM 内存区域

JVM 内存区域次要分为线程公有区域【程序计数器、虚拟机栈、本地办法区】、线程共享区域【JAVA 堆、办法区】、间接内存。

线程公有数据区域生命周期与线程雷同, 依赖用户线程的启动 / 完结 而 创立 / 销毁(在 Hotspot VM 内, 每个线程都与操作系统的本地线程间接映射, 因而这部分内存区域的存 / 否追随本地线程的生 / 死对应)。

线程共享区域随虚拟机的启动 / 敞开而创立 / 销毁。

间接内存并不是 JVM 运行时数据区的一部分, 但也会被频繁的应用: 在 JDK 1.4 引入的 NIO 提供了基于 Channel 与 Buffer 的 IO 形式, 它能够应用 Native 函数库间接调配堆外内存, 而后应用 DirectByteBuffer 对象作为这块内存的援用进行操作(详见: Java I/O 扩大), 这样就防止了在 Java 堆和 Native 堆中来回复制数据, 因而在一些场景中能够显著进步性能。

12、程序计数器(线程公有)

一块较小的内存空间, 是以后线程所执行的字节码的行号指示器,每条线程都要有一个独立的程序计数器,这类内存也称为“线程公有”的内存。

正在执行 java 办法的话,计数器记录的是虚拟机字节码指令的地址(以后指令的地址)。如果还是 Native 办法,则为空。

这个内存区域是惟一一个在虚拟机中没有规定任何 OutOfMemoryError 状况的区域。

13、虚拟机栈(线程公有)

是形容 java 办法执行的内存模型,每个办法在执行的同时都会创立一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动静链接、办法进口等信息。每一个办法从调用直至执行实现的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

栈帧(Frame)是用来存储数据和局部过程后果的数据结构,同时也被用来解决动静链接(Dynamic Linking)、办法返回值和异样分派(Dispatch Exception)。栈帧随着办法调用而创立,随着办法完结而销毁——无论办法是失常实现还是异样实现(抛出了在办法内未被捕捉的异样)都算作办法完结。

14、本地办法区(线程公有)

本地办法区和 Java Stack 作用相似, 区别是虚拟机栈为执行 Java 办法服务, 而本地办法栈则为 Native 办法服务, 如果一个 VM 实现应用 C-linkage 模型来反对 Native 调用, 那么该栈将会是一个 C 栈,但 HotSpot VM 间接就把本地办法栈和虚拟机栈合二为一。

15、你能保障 GC 执行吗?

不能,尽管你能够调用 System.gc() 或者 Runtime.gc(),然而没有方法保障 GC 的执行。

16、怎么获取 Java 程序应用的内存?堆应用的百分比?

能够通过 java.lang.Runtime 类中与内存相干办法来获取残余的内存,总内存及最大堆内存。通过这些办法你也能够获取到堆应用的百分比及堆内存的残余空间。Runtime.freeMemory() 办法返回残余空间的字节数,Runtime.totalMemory()办法总内存的字节数,Runtime.maxMemory() 返回最大内存的字节数。

17、Java 中堆和栈有什么区别?

JVM 中堆和栈属于不同的内存区域,应用目标也不同。栈罕用于保留办法帧和局部变量,而对象总是在堆上调配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。

18、形容一下 JVM 加载 class 文件的原理机制

JVM 中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中各类加载器是一个重要的 Java 运行时零碎组件,它负责在运行时查找和装入类文件中的类。

因为 Java 的跨平台性,通过编译的 Java 源程序并不是一个可执行程序,而是一个或多个类文件。当 Java 程序须要应用某个类时,JVM 会确保这个类曾经被加载、连贯(验证、筹备和解析)和初始化。类的加载是指把类的.class 文件中的数据读入到内存中,通常是创立一个字节数组读入.class 文件,而后产生与所加载类对应的 Class 对象。

加载实现后,Class 对象还不残缺,所以此时的类还不可用。当类被加载后就进入连贯阶段,这一阶段包含验证、筹备(为动态变量分配内存并设置默认的初始值)和解析(将符号援用替换为间接援用)三个步骤。最初 JVM 对类进行初始化,包含:1)如果类存在间接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就顺次执行这些初始化语句。

类的加载是由类加载器实现的,类加载器包含:根加载器(BootStrap)、扩大加载器(Extension)、零碎加载器(System)和用户自定义类加载器(java.lang.ClassLoader 的子类)。

从 Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM 更好的保障了 Java 平台的安全性,在该机制中,JVM 自带的 Bootstrap 是根加载器,其余的加载器都有且仅有一个父类加载器。类的加载首先申请父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM 不会向 Java 程序提供对 Bootstrap 的援用。上面是对于几个类

加载器的阐明:

(1)Bootstrap:个别用本地代码实现,负责加载 JVM 根底外围类库(rt.jar);

(2)Extension:从 java.ext.dirs 零碎属性所指定的目录中加载类库,它的父加载器是 Bootstrap;

(3)System:又叫利用类加载器,其父类是 Extension。它是利用最宽泛的类加载器。它从环境变量 classpath 或者零碎属性

java.class.path 所指定的目录中记录类,是用户自定义加载器的默认父加载器。

19、GC 是什么?为什么要有 GC?

GC 是垃 圾收 集的 意思,内存 解决 是编 程人 员容 易出 现问 题的 中央,遗记 或者 谬误的内 存回 收会 导致 程序 或系 统的 不稳 定甚 至崩 溃,Java 提供 的 GC 性能 能够 主动监测 对象 是否 超过 作用 域从 而达 到自 动回 收内 存的 目标,Java 语言 没有 提供 开释已分配内存的 显示 操作 办法。Java 程序 员不 用担 心内 存管 理,因为 垃圾 收集 器会主动 进行 治理。要 申请 垃圾 收集,可 以调 用下 面的 办法 之一:System.gc() 或 Runtime.getRuntime().gc(),但 JVM 能够 屏蔽 掉线 示的 垃圾 回收 调用。

垃圾回收能够无效的避免内存泄露,无效的应用能够应用的内存。垃圾回收器通常是作为一个独自的低优先级的线程运行,不可预知的状况下对内存堆中曾经死亡的或者长时间没有应用的对象进行革除和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。在 Java 诞生初期,垃圾回收是 Java 最大的亮点之一,因为服务器端的编程须要无效的避免内存泄露问题,然而时过境迁,现在 Java 的垃圾回收机制曾经成为被诟病的东。挪动智能终端用户通常感觉 iOS 的零碎比 Android 零碎有更好的用户体验,其中一个深层次的起因就在于 Android 零碎中垃圾回收的不可预知性。

20、堆(Heap- 线程共享)- 运行时数据区

是被线程共享的一块内存区域,创立的对象和数组都保留在 Java 堆内存中,也是垃圾收集器进行垃圾收集的最重要的内存区域。因为古代 VM 采纳分代收集算法, 因而 Java 堆从 GC 的角度还能够细分为: 新生代 (Eden 区、From Survivor 区和 To Survivor 区) 和老年代。

21、办法区 / 永恒代(线程共享)

即咱们常说的永恒代(Permanent Generation), 用于存储被 JVM 加载的类信息、常量、动态变量即、时编译器编译后的代码等数据.HotSpot VM 把 GC 分代收集扩大至办法区, 即应用 Java 堆的永恒代来实现办法区, 这样 HotSpot 的垃圾收集器就能够像治理 Java 堆一样治理这部分内存, 而不用为办法区开发专门的内存管理器(永恒带的内存回收的次要指标是针对常量池的回收和类型的卸载, 因而收益个别很小)。

运行时常量池(Runtime Constant Pool)是办法区的一部分。Class 文件中除了有类的版本、字段、办法、接口等形容等信息外,还有一项信息是常量池(Constant Pool Table),用于寄存编译期生成的各种字面量和符号援用,这部分内容将在类加载后寄存到办法区的运行时常量池中。Java 虚拟机对 Class 文件的每一部分(天然也包含常量池)的格局都有严格的规定,每一个字节用于存储哪种数据都必须符合规范上的要求,这样才会被虚拟机认可、装载和执行。

22、JVM 运行时内存

Java 堆从 GC 的角度还能够细分为: 新生代 (Eden 区、From Survivor 区和 To Survivor 区) 和老年代。

23、新生代

是用来寄存新生的对象。个别占据堆的 1/3 空间。因为频繁创建对象,所以新生代会频繁触发 MinorGC 进行垃圾回收。新生代又分为 Eden 区、ServivorFrom、ServivorTo 三个区。

Eden 区

Java 新对象的出生地(如果新创建的对象占用内存很大,则间接调配到老年代)。当 Eden 区内存不够的时候就会触发 MinorGC,对新生代区进行一次垃圾回收。

ServivorFrom

上一次 GC 的幸存者,作为这一次 GC 的被扫描者。

ServivorTo

保留了一次 MinorGC 过程中的幸存者。

MinorGC 的过程(复制 -> 清空 -> 调换)

MinorGC 采纳复制算法。

(1)eden、servicorFrom 复制到 ServicorTo,年龄 +1

首先,把 Eden 和 ServivorFrom 区域中存活的对象复制到 ServicorTo 区域(如果有对象的年龄以及达到了老年的规范,则赋值到老年代区),同时把这些对象的年龄 +1(如果 ServicorTo 不够地位了就放到老年区);

(2)清空 eden、servicorFrom

而后,清空 Eden 和 ServicorFrom 中的对象;

(3)ServicorTo 和 ServicorFrom 调换

最初,ServicorTo 和 ServicorFrom 调换,原 ServicorTo 成为下一次 GC 时的 ServicorFrom 区。

24、老年代

次要寄存应用程序中生命周期长的内存对象。

老年代的对象比较稳定,所以 MajorGC 不会频繁执行。在进行 MajorGC 前个别都先进行了一次 MinorGC,使得有新生代的对象晋身入老年代,导致空间不够用时才触发。当无奈找到足够大的间断空间调配给新创建的较大对象时也会提前触发一次 MajorGC 进行垃圾回收腾出空间。

MajorGC 采纳标记革除算法:首先扫描一次所有老年代,标记出存活的对象,而后回收没有标记的对象。ajorGC 的耗时比拟长,因为要扫描再回收。MajorGC 会产生内存碎片,为了缩小内存损耗,咱们个别须要进行合并或者标记进去不便下次间接调配。当老年代也满了装不下的时候,就会抛出 OOM(Out of Memory)异样。

25、永恒代

指内存的永恒保留区域,次要寄存 Class 和 Meta(元数据)的信息,Class 在被加载的时候被放入永恒区域,它和和寄存实例的区域不同,GC 不会在主程序运行期对永恒区域进行清理。所以这也导致了永恒代的区域会随着加载的 Class 的增多而胀满,最终抛出 OOM 异样。

26、JAVA8 与元数据

在 Java8 中,永恒代曾经被移除,被一个称为“元数据区”(元空间)的区域所取代。元空间的实质和永恒代相似,元空间与永恒代之间最大的区别在于:元空间并不在虚拟机中,而是应用本地内存。因而,默认状况下,元空间的大小仅受本地内存限度。类的元数据放入 nativememory, 字符串池和类的动态变量放入 java 堆中,这样能够加载多少类的元数据就不再由 MaxPermSize 管制, 而由零碎的理论可用空间来管制。

27、援用计数法

在 Java 中,援用和对象是有关联的。如果要操作对象则必须用援用进行。因而,很显然一个简略的方法是通过援用计数来判断一个对象是否能够回收。简略说,即一个对象如果没有任何与之关联的援用,即他们的援用计数都不为 0,则阐明对象不太可能再被用到,那么这个对象就是可回收对象。

28、可达性剖析

为了解决援用计数法的循环援用问题,Java 应用了可达性剖析的办法。通过一系列的“GC roots”对象作为终点搜寻。如果在“GC roots”和一个对象之间没有可达门路,则称该对象是不可达的。要留神的是,不可达对象不等价于可回收对象,不可达对象变为可回收对象至多要通过两次标记过程。两次标记后依然是可回收对象,则将面临回收。

29、标记革除算法(Mark-Sweep)

最根底的垃圾回收算法,分为两个阶段,标注和革除。标记阶段标记出所有须要回收的对象,革除阶段回收被标记的对象所占用的空间。如图

从图中咱们就能够发现,该算法最大的问题是内存碎片化重大,后续可能产生大对象不能找到可利用空间的问题。

30、复制算法(copying)

为了解决 Mark-Sweep 算法内存碎片化的缺点而被提出的算法。按内存容量将内存划分为等大小的两块。每次只应用其中一块,当这一块内存满后将尚存活的对象复制到另一块下来,把已应用的内存清掉,如图:

这种算法尽管实现简略,内存效率高,不易产生碎片,然而最大的问题是可用内存被压缩到了本来的一半。且存活对象增多的话,Copying 算法的效率会大大降低。

31、标记整顿算法(Mark-Compact)

联合了以上两个算法,为了防止缺点而提出。标记阶段和 Mark-Sweep 算法雷同,标记后不是清理对象,而是将存活对象移向内存的一端。而后革除端边界外的对象。如图:

32、分代收集算法

分代收集法是目前大部分 JVM 所采纳的办法,其核心思想是依据对象存活的不同生命周期将内存划分为不同的域,个别状况下将 GC 堆划分为老生代 (Tenured/Old Generation) 和新生代(YoungGeneration)。老生代的特点是每次垃圾回收时只有大量对象须要被回收,新生代的特点是每次垃圾回收时都有大量垃圾须要被回收,因而能够依据不同区域抉择不同的算法。

33、新生代与复制算法

目前大部分 JVM 的 GC 对于新生代都采取 Copying 算法,因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比拟少,但通常并不是依照 1:1 来划分新生代。个别将新生代划分为一块较大的 Eden 空间和两个较小的 Survivor 空间(From Space, To Space),每次应用 Eden 空间和其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另一块 Survivor 空间中。

34、老年代与标记复制算法

而老年代因为每次只回收大量对象,因此采纳 Mark-Compact 算法。

(1)JAVA 虚拟机提到过的处于办法区的永生代(Permanet Generation),它用来存储 class 类,常量,办法形容等。对永生代的回收次要包含废除常量和无用的类。

(2)对象的内存调配次要在新生代的 Eden Space 和 Survivor Space 的 From Space(Survivor 目前寄存对象的那一块),多数状况会间接调配到老生代。

(3)当新生代的 Eden Space 和 From Space 空间有余时就会产生一次 GC,进行 GC 后,EdenSpace 和 From Space 区的存活对象会被挪到 To Space,而后将 Eden Space 和 FromSpace 进行清理。

(4)如果 To Space 无奈足够存储某个对象,则将这个对象存储到老生代。

(5)在进行 GC 后,应用的便是 Eden Space 和 To Space 了,如此重复循环。

(6)当对象在 Survivor 去躲过一次 GC 后,其年龄就会 +1。默认状况下年龄达到 15 的对象会被移到老生代中。

35、JAVA 强援用

在 Java 中最常见的就是强援用,把一个对象赋给一个援用变量,这个援用变量就是一个强援用。当一个对象被强援用变量援用时,它处于可达状态,它是不可能被垃圾回收机制回收的,即便该对象当前永远都不会被用到 JVM 也不会回收。因而强援用是造成 Java 内存透露的次要起因之一。

36、JAVA 软援用

软援用须要用 SoftReference 类来实现,对于只有软援用的对象来说,当零碎内存足够时它不会被回收,当零碎内存空间有余时它会被回收。软援用通常用在对内存敏感的程序中。

37、JAVA 弱援用

弱援用须要用 WeakReference 类来实现,它比软援用的生存期更短,对于只有弱援用的对象来说,只有垃圾回收机制一运行,不论 JVM 的内存空间是否足够,总会回收该对象占用的内存。

38、JAVA 虚援用

虚援用须要 PhantomReference 类来实现,它不能独自应用,必须和援用队列联结应用。虚援用的次要作用是跟踪对象被垃圾回收的状态。

39、分代收集算法

以后支流 VM 垃圾收集都采纳”分代收集”(Generational Collection)算法, 这种算法会依据对象存活周期的不同将内存划分为几块, 如 JVM 中的新生代、老年代、永恒代,这样就能够依据各年代特点别离采纳最适当的 GC 算法

40、在新生代 - 复制算法

每次垃圾收集都能发现少量对象已死, 只有大量存活. 因而选用复制算法, 只须要付出大量存活对象的复制老本就能够实现收集

41、在老年代 - 标记整顿算法

因为对象存活率高、没有额定空间对它进行调配担保, 就必须采纳“标记—清理”或“标记—整顿”算法来进行回收, 不用进行内存复制, 且间接腾出闲暇内存。

42、分区收集算法

分区算法则将整个堆空间划分为间断的不同小区间, 每个小区间独立应用, 独立回收. 这样做的益处是能够管制一次回收多少个小区间 , 依据指标进展工夫, 每次正当地回收若干个小区间(而不是整个堆), 从而缩小一次 GC 所产生的进展。

43、GC 垃圾收集器

Java 堆内存被划分为新生代和年轻代两局部,新生代次要应用复制和标记 - 革除垃圾回收算法;年轻代次要应用标记 - 整顿垃圾回收算法,因而 java 虚构中针对新生代和年轻代别离提供了多种不同的垃圾收集器,JDK1.6 中 Sun HotSpot 虚拟机的垃圾收集器如下:

44、Serial 垃圾收集器(单线程、复制算法)

Serial(英文间断)是最根本垃圾收集器,应用复制算法,已经是 JDK1.3.1 之前新生代惟一的垃圾收集器。Serial 是一个单线程的收集器,它岂但只会应用一个 CPU 或一条线程去实现垃圾收集工作,并且在进行垃圾收集的同时,必须暂停其余所有的工作线程,直到垃圾收集完结。

Serial 垃圾收集器尽管在收集垃圾过程中须要暂停所有其余的工作线程,然而它简略高效,对于限定单个 CPU 环境来说,没有线程交互的开销,能够取得最高的单线程垃圾收集效率,因而 Serial 垃圾收集器仍然是 java 虚拟机运行在 Client 模式下默认的新生代垃圾收集器。

45、ParNew 垃圾收集器(Serial+ 多线程)

ParNew 垃圾收集器其实是 Serial 收集器的多线程版本,也应用复制算法,除了应用多线程进行垃圾收集之外,其余的行为和 Serial 收集器齐全一样,ParNew 垃圾收集器在垃圾收集过程中同样也要暂停所有其余的工作线程。

ParNew 收集器默认开启和 CPU 数目雷同的线程数,能够通过 -XX:ParallelGCThreads 参数来限度垃圾收集器的线程数。【Parallel:平行的】

ParNew 尽管是除了多线程外和 Serial 收集器简直齐全一样,然而 ParNew 垃圾收集器是很多 java 虚拟机运行在 Server 模式下新生代的默认垃圾收集器。

46、Parallel Scavenge 收集器(多线程复制算法、高效)

Parallel Scavenge 收集器也是一个新生代垃圾收集器,同样应用复制算法,也是一个多线程的垃圾收集器,它重点关注的是程序达到一个可管制的吞吐量(Thoughput,CPU 用于运行用户代码的工夫 /CPU 总耗费工夫,即吞吐量 = 运行用户代码工夫 /(运行用户代码工夫 + 垃圾收集工夫)),高吞吐量能够最高效率地利用 CPU 工夫,尽快地实现程序的运算工作,次要实用于在后盾运算而不须要太多交互的工作。自适应调节策略也是 ParallelScavenge 收集器与 ParNew 收集器的一个重要区别。

47、Serial Old 收集器(单线程标记整顿算法)

Serial Old 是 Serial 垃圾收集器年轻代版本,它同样是个单线程的收集器,应用标记 - 整顿算法,这个收集器也次要是运行在 Client 默认的

java 虚拟机默认的年轻代垃圾收集器。在 Server 模式下,次要有两个用处:

(1)在 JDK1.5 之前版本中与新生代的 Parallel Scavenge 收集器搭配应用。

(2)作为年轻代中应用 CMS 收集器的后备垃圾收集计划。新生代 Serial 与年轻代 Serial Old 搭配垃圾收集过程图:

新生代 Parallel Scavenge 收集器与 ParNew 收集器工作原理相似,都是多线程的收集器,都应用的是复制算法,在垃圾收集过程中都须要暂停所有的工作线程。新生代 ParallelScavenge/ParNew 与年轻代 Serial Old 搭配垃圾收集过程图:

48、Parallel Old 收集器(多线程标记整顿算法)

Parallel Old 收集器是 Parallel Scavenge 的年轻代版本,应用多线程的标记 - 整顿算法,在 JDK1.6 才开始提供。

在 JDK1.6 之前,新生代应用 ParallelScavenge 收集器只能搭配年轻代的 Serial Old 收集器,只能保障新生代的吞吐量优先,无奈保障整体的吞吐量,Parallel Old 正是为了在年轻代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比拟高,能够优先思考新生代 Parallel Scavenge 和年轻代 Parallel Old 收集器的搭配策略。

新生代 Parallel Scavenge 和年轻代 Parallel Old 收集器搭配运行过程图

49、CMS 收集器(多线程标记革除算法)

Concurrent mark sweep(CMS)收集器是一种年轻代垃圾收集器,其最次要指标是获取最短垃圾回收进展工夫,和其余年轻代应用标记 - 整顿算法不同,它应用多线程的标记 - 革除算法。最短的垃圾收集进展工夫能够为交互比拟高的程序进步用户体验。CMS 工作机制相比其余的垃圾收集器来说更简单。整个过程分为以下 4 个阶段:

初始标记

只是标记一下 GC Roots 能间接关联的对象,速度很快,依然须要暂停所有的工作线程。

并发标记

进行 GC Roots 跟踪的过程,和用户线程一起工作,不须要暂停工作线程。

从新标记

为了修改在并发标记期间,因用户程序持续运行而导致标记产生变动的那一部分对象的标记记录,依然须要暂停所有的工作线程。

并发革除

革除 GC Roots 不可达对象,和用户线程一起工作,不须要暂停工作线程。因为耗时最长的并发标记和并发革除过程中,垃圾收集线程能够和用户当初一起并发工作,所以总体上来看 CMS 收集器的内存回收和用户线程是一起并发地执行。CMS 收集器工作过程

50、G1 收集器

Garbage first 垃圾收集器是目前垃圾收集器实践倒退的最前沿成绩,相比与 CMS 收集器,G1 收集器两个最突出的改良是:

(1)基于标记 - 整顿算法,不产生内存碎片。

(2)能够十分准确管制进展工夫,在不就义吞吐量前提下,实现低进展垃圾回收。G1 收集器防止全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后盾保护一个优先级列表,每次依据所容许的收集工夫,优先回收垃圾最多的区域。区域划分和优先级区域回收机制,确保 G1 收集器能够在无限工夫取得最高的垃圾收集效率

51、JVM 类加载机制

JVM 类加载机制分为五个局部:加载,验证,筹备,解析,初始化,上面咱们就别离来看一下这五个过程。

加载

加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的 java.lang.Class 对象,作为办法区这个类的各种数据的入口。留神这里不肯定非得要从一个 Class 文件获取,这里既能够从 ZIP 包中读取(比方从 jar 包和 war 包中读取),也能够在运行时计算生成(动静代理),也能够由其它文件生成(比方将 JSP 文件转换成对应的 Class 类)。

验证

这一阶段的次要目标是为了确保 Class 文件的字节流中蕴含的信息是否合乎以后虚拟机的要求,并且不会危害虚拟机本身的平安。

筹备

筹备阶段是正式为类变量分配内存并设置类变量的初始值阶段,即在办法区中调配这些变量所应用的内存空间。留神这里所说的初始值概念,比方一个类变量定义为:

实际上变量 v 在筹备阶段过后的初始值为 0 而不是 8080,将 v 赋值为 8080 的 put static 指令是程序被编译后,寄存于类结构器办法之中。

然而留神如果申明为:

public static final int v = 8080;

在编译阶段会为 v 生成 ConstantValue 属性,在筹备阶段虚构机会依据 ConstantValue 属性将 v 赋值为 8080。

解析

解析阶段是指虚拟机将常量池中的符号援用替换为间接援用的过程。符号援用就是 class 文件中的

public static int v = 8080;

实际上变量 v 在筹备阶段过后的初始值为 0 而不是 8080,将 v 赋值为 8080 的 put static 指令是程序被编译后,寄存于类结构器办法之中。然而留神如果申明为:

在编译阶段会为 v 生成 ConstantValue 属性,在筹备阶段虚构机会依据 ConstantValue 属性将 v 赋值为 8080。

解析

解析阶段是指虚拟机将常量池中的符号援用替换为间接援用的过程。符号援用就是 class 文件中的

public static final int v = 8080;

在编译阶段会为 v 生成 ConstantValue 属性,在筹备阶段虚构机会依据 ConstantValue 属性将 v 赋值为 8080。

解析

解析阶段是指虚拟机将常量池中的符号援用替换为间接援用的过程。符号援用就是 class 文件中的:

(1)CONSTANT_Class_info

(2)CONSTANT_Field_info

(3)CONSTANT_Method_info

等类型的常量。

符号援用

符号援用与虚拟机实现的布局无关,援用的指标并不一定要曾经加载到内存中。各种虚拟机实现的内存布局能够各不相同,然而它们能承受的符号援用必须是统一的,因为符号援用的字面量模式明确定义在 Java 虚拟机标准的 Class 文件格式中。

间接援用

间接援用能够是指向指标的指针,绝对偏移量或是一个能间接定位到指标的句柄。如果有了间接援用,那援用的指标必然曾经在内存中存在。

初始化

初始化阶段是类加载最初一个阶段,后面的类加载阶段之后,除了在加载阶段能够自定义类加载器以外,其它操作都由 JVM 主导。到了初始阶段,才开始真正执行类中定义的 Java 程序代码。

类结构器

初始化阶段是执行类结构器办法的过程。办法是由编译器主动收集类中的类变量的赋值操作和动态语句块中的语句合并而成的。虚构机会保障子办法执行之前,父类的办法曾经执行结束,如果一个类中没有对动态变量赋值也没有动态语句块,那么编译器能够不为这个类生成 () 办法。留神以下几种状况不会执行类初始化:

(1)通过子类援用父类的动态字段,只会触发父类的初始化,而不会触发子类的初始化。

(2)定义对象数组,不会触发该类的初始化。

(3)常量在编译期间会存入调用类的常量池中,实质上并没有间接援用定义常量的类,不会触发定义常量所在的类。

(4)通过类名获取 Class 对象,不会触发类的初始化。

(5)通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初始化,其实这个参数是通知虚拟机,是否要对类进行初始化。

(6)通过 ClassLoader 默认的 loadClass 办法,也不会触发初始化动作。

52、类加载器

虚拟机设计团队把加载动作放到 JVM 内部实现,以便让应用程序决定如何获取所需的类,JVM 提供了 3 品种加载器:

启动类加载器(Bootstrap ClassLoader)

负责加载 JAVA_HOMElib 目录中的,或通过 -Xbootclasspath 参数指定门路中的,且被虚拟机认可(按文件名辨认,如 rt.jar)的类。

扩大类加载器(Extension ClassLoader)

负责加载 JAVA_HOMElibext 目录中的,或通过 java.ext.dirs 零碎变量指定门路中的类库。

应用程序类加载器(Application ClassLoader):

负责加载用户门路(classpath)上的类库。JVM 通过双亲委派模型进行类的加载,当然咱们也能够通过继承 java.lang.ClassLoader 实现自定义的类加载器。

53、双亲委派

当一个类收到了类加载申请,他首先不会尝试本人去加载这个类,而是把这个申请委派给父类去实现,每一个档次类加载器都是如此,因而所有的加载申请都应该传送到启动类加载其中,只有当父类加载器反馈本人无奈实现这个申请的时候(在它的加载门路下没有找到所需加载的 Class),子类加载器才会尝试本人去加载。

采纳双亲委派的一个益处是比方加载位于 rt.jar 包中的类 java.lang.Object,不论是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,这样就保障了应用不同的类加载器最终失去的都是同样一个 Object 对象

54、OSGI(动静模型零碎)

OSGi(Open Service Gateway Initiative),是面向 Java 的动静模型零碎,是 Java 动态化模块化零碎的一系列标准。

55、动静扭转结构

OSGi 服务平台提供在多种网络设备上无需重启的动静扭转结构的性能。为了最小化耦合度和促使这些耦合度可治理,OSGi 技术提供一种面向服务的架构,它能使这些组件动静地发现对方。

56、模块化编程与热插拔

OSGi 旨在为实现 Java 程序的模块化编程提供根底条件,基于 OSGi 的程序很可能能够实现模块级的热插拔性能,当程序降级更新时,能够只停用、重新安装而后启动程序的其中一部分,这对企业级程序开发来说是十分具备诱惑力的个性。

OSGi 描述了一个很美妙的模块化开发指标,而且定义了实现这个指标的所须要服务与架构,同时也有成熟的框架进行实现反对。但并非所有的利用都适宜采纳 OSGi 作为基础架构,它在提供弱小性能同时,也引入了额定的复杂度,因为它不恪守了类加载的双亲委托模型。

57、JVM 内存模型

线程独占: 栈, 本地办法栈, 程序计数器

线程共享: 堆, 办法区

58、栈

又称办法栈, 线程公有的, 线程执行办法是都会创立一个栈阵, 用来存储局部变量表, 操作栈, 动静链接, 办法进口等信息. 调用办法时执行入栈, 办法返回式执行出栈.

59、本地办法栈

与栈相似, 也是用来保留执行办法的信息. 执行 Java 办法是应用栈, 执行 Native 办法时应用本地办法栈.

60、程序计数器

保留着以后线程执行的字节码地位, 每个线程工作时都有独立的计数器, 只为执行 Java 办法服务, 执行 Native 办法时, 程序计数器为空.

61、堆

JVM 内存治理最大的一块, 对被线程共享, 目标是寄存对象的实例, 简直所欲的对象实例都会放在这里, 当堆没有可用空间时, 会抛出 OOM 异样. 依据对象的存活周期不同,JVM 把对象进行分代治理, 由垃圾回收器进行垃圾的回收治理

62、办法区

又称非堆区, 用于存储已被虚拟机加载的类信息, 常量, 动态变量, 即时编译器优化后的代码等数据.1.7 的永恒代和 1.8 的元空间都是办法区的一种实现。

63、分代回收

分代回收基于两个事实: 大部分对象很快就不应用了, 还有一部分不会立刻无用, 但也不会继续很长时间

年老代 -> 标记 - 复制

老年代 -> 标记 - 革除

64、堆和栈的区别

栈是运行时单位,代表着逻辑,内含根本数据类型和堆中对象援用,所在区域间断,没有碎片;堆是存储单位,代表着数据,可被多个栈共享(包含成员中根本数据类型、援用和援用对象),所在区域不间断,会有碎片。

(1)性能不同

栈内存用来存储局部变量和办法调用,而堆内存用来存储 Java 中的对象。无论是成员变量,局部变量,还是类变量,它们指向的对象都存储在堆内存中。

(2)共享性不同

栈内存是线程公有的。

堆内存是所有线程共有的。

(3)异样谬误不同

如果栈内存或者堆内存不足都会抛出异样。

栈空间有余:java.lang.StackOverFlowError。

堆空间有余:java.lang.OutOfMemoryError。

(4)空间大小

栈的空间大小远远小于堆的

65、什么时候会触发 FullGC

除间接调用 System.gc 外,触发 Full GC 执行的状况有如下四种。

(1)旧生代空间有余

旧生代空间只有在新生代对象转入及创立为大对象、大数组时才会呈现有余的景象,当执行 Full GC 后空间依然有余,则抛出如下错

误:

java.lang.OutOfMemoryError: Java heap space

为防止以上两种情况引起的 FullGC,调优时应尽量做到让对象在 Minor GC 阶段被回收、让对象在新生代多存活一段时间及不要创立过大的对象及数组。

(2)Permanet Generation 空间满

PermanetGeneration 中寄存的为一些 class 的信息等,当零碎中要加载的类、反射的类和调用的办法较多时,Permanet Generation 可能会被占满,在未配置为采纳 CMS GC 的状况下会执行 Full GC。如果通过 Full GC 依然回收不了,那么 JVM 会抛出如下错误信息:

java.lang.OutOfMemoryError: PermGen space

为防止 Perm Gen 占满造成 Full GC 景象,可采纳的办法为增大 Perm Gen 空间或转为应用 CMS GC。

(3)CMS GC 时呈现 promotion failed 和 concurrent mode failure

对于采纳 CMS 进行旧生代 GC 的程序而言,尤其要留神 GC 日志中是否有 promotion failed 和 concurrent mode failure 两种情况,当这两种情况呈现时可能会触发 Full GC。

promotionfailed 是在进行 Minor GC 时,survivor space 放不下、对象只能放入旧生代,而此时旧生代也放不下造成的;concurrentmode failure 是在执行 CMS GC 的过程中同时有对象要放入旧生代,而此时旧生代空间有余造成的。

应答措施为:增大 survivorspace、旧生代空间或调低触发并发 GC 的比率,但在 JDK 5.0+、6.0+ 的版本中有可能会因为 JDK 的 bug29 导致 CMS 在 remark 结束后很久才触发 sweeping 动作。对于这种情况,可通过设置 -XX:CMSMaxAbortablePrecleanTime=5(单位为 ms)来防止。

(4)统计失去的 Minor GC 降职到旧生代的均匀大小大于旧生代的残余空间

这是一个较为简单的触发状况,Hotspot 为了防止因为新生代对象降职到旧生代导致旧生代空间有余的景象,在进行 Minor GC 时,做了一个判断,如果之前统计所失去的 Minor GC 降职到旧生代的均匀大小大于旧生代的残余空间,那么就间接触发 Full GC。

例如程序第一次触发 MinorGC 后,有 6MB 的对象降职到旧生代,那么当下一次 Minor GC 产生时,首先查看旧生代的残余空间是否大于 6MB,如果小于 6MB,则执行 Full GC。

当新生代采纳 PSGC 时,形式稍有不同,PS GC 是在 Minor GC 后也会查看,例如下面的例子中第一次 Minor GC 后,PS GC 会查看此时旧生代的残余空间是否大于 6MB,如小于,则触发对旧生代的回收。除了以上 4 种情况外,对于应用 RMI 来进行 RPC 或治理的 Sun JDK 利用而言,默认状况下会一小时执行一次 Full GC。可通过在启动时通过 - java-Dsun.rmi.dgc.client.gcInterval=3600000 来设置 Full GC 执行的间隔时间或通过 -XX:+ DisableExplicitGC 来禁止 RMI 调用 System.gc

66、什么是 Java 虚拟机?为什么 Java 被称作是“平台无关的编程语言”?

Java 虚拟机是一个能够执行 Java 字节码的虚拟机过程。Java 源文件被编译成能被 Java 虚拟机执行的字节码文件。Java 被设计成容许应用程序能够运行在任意的平台,而不须要程序员为每一个平台独自重写或者是从新编译。Java 虚拟机让这个变为可能,因为它晓得底层硬件平台的指令长度和其余个性。

67、对象调配规定

(1)对象优先调配在 Eden 区,如果 Eden 区没有足够的空间时,虚拟机执行一次 Minor GC。

(2)大对象间接进入老年代(大对象是指须要大量间断内存空间的对象)。这样做的目标是防止在 Eden 区和两个 Survivor 区之间产生大量的内存拷贝(新生代采纳复制算法收集内存)。

(3)长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象通过了 1 次 Minor GC 那么对象会进入 Survivor 区,之后每通过一次 Minor GC 那么对象的年龄加 1,晓得达到阀值对象进入老年区。

(4)动静判断对象的年龄。如果 Survivor 区中雷同年龄的所有对象大小的总和大于 Survivor 空间的一半,年龄大于或等于该年龄的对象能够间接进入老年代。

(5)空间调配担保。每次进行 Minor GC 时,JVM 会计算 Survivor 区移至老年区的对象的均匀大小,如果这个值大于老年区的残余值大小则进行一次 Full GC,如果小于查看 HandlePromotionFailure 设置,如果 true 则只进行 Monitor GC, 如果 false 则进行 Full GC

68、形容一下 JVM 加载 class 文件的原理机制?

JVM 中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java 中的类加载器是一个重要的 Java 运行时零碎组件,它负责在运行时查找和装入类文件中的类。

因为 Java 的跨平台性,通过编译的 Java 源程序并不是一个可执行程序,而是一个或多个类文件。当 Java 程序须要应用某个类时,JVM 会确保这个类曾经被加载、连贯(验证、筹备和解析)和初始化。

类的加载是指把类的.class 文件中的数据读入到内存中,通常是创立一个字节数组读入.class 文件,而后产生与所加载类对应的 Class 对象。加载实现后,Class 对象还不残缺,所以此时的类还不可用。

当类被加载后就进入连贯阶段,这一阶段包含验证、筹备(为动态变量分配内存并设置默认的初始值)和解析(将符号援用替换为间接援用)三个步骤。最初 JVM 对类进行初始化,

包含:

(1)如果类存在间接的父类并且这个类还没有被初始化,那么就先初始化父类;

(2)如果类中存在初始化语句,就顺次执行这些初始化语句。类的加载是由类加载器实现的,类加载器包含:根加载器(BootStrap)、扩大加载器(Extension)、零碎加载器(System)和用户自定义类加载器(java.lang.ClassLoader 的子类)。

从 Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM 更好的保障了 Java 平台的安全性,在该机制中,JVM 自带的 Bootstrap 是根加载器,其余的加载器都有且仅有一个父类加载器。类的加载首先申请父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM 不会向 Java 程序提供对 Bootstrap 的援用。上面是对于几个类加载器的阐明

Bootstrap:个别用本地代码实现,负责加载 JVM 根底外围类库(rt.jar);

Extension:从 java.ext.dirs 零碎属性所指定的目录中加载类库,它的父加载器是 Bootstrap;

System:又叫利用类加载器,其父类是 Extension。它是利用最宽泛的类加载器。它从环境变量 classpath 或者零碎属性 java.class.path 所指定的目录中记录类,是用户自定义加载器的默认父加载器。

69、Java 对象创立过程

(1)JVM 遇到一条新建对象的指令时首先去查看这个指令的参数是否能在常量池中定义到一个类的符号援用。而后加载这个类(类加载过程在后边讲)

(2)为对象分配内存。一种方法“指针碰撞”、一种方法“闲暇列表”,最终罕用的方法“本地线程缓冲调配(TLAB)”

(3)将除对象头外的对象内存空间初始化为 0

(4)对对象头进行必要设置

70、简述 Java 的对象构造

Java 对象由三个局部组成:对象头、实例数据、对齐填充。

对象头由两局部组成,第一局部存储对象本身的运行时数据:哈希码、GC 分代年龄、锁标识状态、线程持有的锁、偏差线程 ID(个别占 32/64 bit)。第二局部是指针类型,指向对象的类元数据类型(即对象代表哪个类)。如果是数组对象,则对象头中还有一部分用来记录数组长度。

实例数据用来存储对象真正的无效信息(包含父类继承下来的和本人定义的)

对齐填充:JVM 要求对象起始地址必须是 8 字节的整数倍(8 字节对齐 )

71、如何判断对象能够被回收

判断对象是否存活个别有两种形式:

援用计数:每个对象有一个援用计数属性,新增一个援用时计数加 1,援用开释时计数减 1,计数为 0 时能够回收。此办法简略,无奈解决对象互相循环援用的问题。

可达性剖析(Reachability Analysis):从 GC Roots 开始向下搜寻,搜寻所走过的门路称为援用链。当一个对象到 GC Roots 没有任何援用链相连时,则证实此对象是不可用的,不可达对象。

72、JVM 的永恒代中会产生垃圾回收么

垃圾回收不会产生在永恒代,如果永恒代满了或者是超过了临界值,会触发齐全垃圾回收(Full GC)。如果你认真查看垃圾收集器的输入信息,就会发现永恒代也是被回收的。这就是为什么正确的永恒代大小对防止 Full GC 是十分重要的起因。请参考下 Java8:从永恒代到元数据区 (注:Java8 中曾经移除了永恒代,新加了一个叫做元数据区的 native 内存区)

73、垃圾收集算法

GC 最根底的算法有三种:标记 - 革除算法、复制算法、标记 - 压缩算法,咱们罕用的垃圾回收器个别都采纳分代收集算法。

标记 - 革除算法

“标记 - 革除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“革除”两个阶段:首先标记出所有须要回收的对象,在标记实现后对立回收掉所有被标记的对象。

复制算法

“复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只应用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块下面,而后再把已应用过的内存空间一次清理掉。

标记 - 压缩算法

标记过程依然与“标记 - 革除”算法一样,但后续步骤不是间接对可回收对象进行清理,而是让所有存活的对象都向一端挪动,而后间接清理掉端边界以外的内存

分代收集算法

“分代收集”(Generational Collection)算法,把 Java 堆分为新生代和老年代,这样就能够依据各个年代的特点采纳最适当的收集算法

74、调优命令有哪些?

Sun JDK 监控和故障解决命令有 jps jstat jmap jhat jstack jinfo

(1)jps,JVM Process Status Tool, 显示指定零碎内所有的 HotSpot 虚拟机过程。

(1)jstat,JVM statistics Monitoring 是用于监督虚拟机运行时状态信息的命令,它能够显示出虚拟机过程中的类装载、内存、垃圾收集、JIT 编译等运行数据。

(3)jmap,JVM Memory Map 命令用于生成 heap dump 文件

(4)jhat,JVM Heap Analysis Tool 命令是与 jmap 搭配应用,用来剖析 jmap 生成的 dump,jhat 内置了一个微型的 HTTP/HTML 服务器,生成 dump 的剖析后果后,能够在浏览器中查看

(5)jstack,用于生成 java 虚拟机以后时刻的线程快照。

(6)jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数

75、调优工具

罕用调优工具分为两类,jdk 自带监控工具:jconsole 和 jvisualvm,第三方有:MAT(Memory AnalyzerTool)、GChisto。

(1)jconsole,Java Monitoring and Management Console 是从 java5 开始,在 JDK 中自带的 java 监控和治理控制台,用于对 JVM 中内存,线程和类等的监控

(2)jvisualvm,jdk 自带全能工具,能够剖析内存快照、线程快照;监控内存变动、GC 变动等。

(3)MAT,Memory Analyzer Tool,一个基于 Eclipse 的内存剖析工具,是一个疾速、功能丰富的 Javaheap 剖析工具,它能够帮忙咱们查找内存透露和缩小内存耗费

(4)GChisto,一款业余剖析 gc 日志的工具

76、Minor GC 与 Full GC 别离在什么时候产生?

新生代内存不够用时候产生 MGC 也叫 YGC,JVM 内存不够的时候产生 FGC

77、你晓得哪些 JVM 性能调优

设定堆内存大小

-Xmx:堆内存最大限度。

设定新生代大小。新生代不宜太小,否则会有大量对象涌入老年代

-XX:NewSize:新生代大小

-XX:NewRatio 新生代和老生代占比

-XX:SurvivorRatio:伊甸园空间和幸存者空间的占比

设定垃圾回收器 年老代用 -XX:+UseParNewGC 年轻代用 -XX:+UseConcMarkSweepGC

退出移动版