关于面试:深入浅出2020年春招秋招JVM面试题整理附答案

36次阅读

共计 12862 个字符,预计需要花费 33 分钟才能阅读完成。

运行时数据区是什么?

虚拟机在执行 Java 程序的过程中会把它所治理的内存划分为若干不同的数据区,这些区域有各自的用处、创立和销毁工夫。

线程公有:程序计数器、Java 虚拟机栈、本地办法栈。

线程共享:Java 堆、办法区。

程序计数器是什么?

程序计数器 是一块较小的内存空间,能够看作以后线程所执行字节码的行号指示器。字节码解释器工作时通过扭转计数器的值选取下一条执行指令。分支、循环、跳转、线程复原等性能都须要依赖计数器实现。是惟一在虚拟机标准中没有规定内存溢出状况的区域。

如果线程正在执行 Java 办法,计数器记录正在执行的虚拟机字节码指令地址。如果是本地办法,计数器值为 Undefined。

Java 虚拟机栈的作用?

Java 虚拟机栈 来形容 Java 办法的内存模型。每当有新线程创立时就会调配一个栈空间,线程完结后栈空间被回收,栈与线程领有雷同的生命周期。栈中元素用于反对虚拟机进行办法调用,每个办法在执行时都会创立一个栈帧存储办法的局部变量表、操作栈、动静链接和办法进口等信息。每个办法从调用到执行实现,就是栈帧从入栈到出栈的过程。

有两类异样:① 线程申请的栈深度大于虚拟机容许的深度抛出 StackOverflowError。② 如果 JVM 栈容量能够动静扩大,栈扩大无奈申请足够内存抛出 OutOfMemoryError(HotSpot 不可动静扩大,不存在此问题)。

本地办法栈的作用?

本地办法栈 与虚拟机栈作用类似,不同的是虚拟机栈为虚拟机执行 Java 办法服务,本地办法栈为虚本地办法服务。调用本地办法时虚拟机栈放弃不变,动静链接并间接调用指定本地办法。

虚拟机标准对本地办法栈中办法的语言与数据结构无强制规定,虚拟机可自在实现,例如 HotSpot 将虚拟机栈和本地办法栈合二为一。

本地办法栈在栈深度异样和栈扩大失败时别离抛出 StackOverflowError 和 OutOfMemoryError。

堆的作用是什么?

是虚拟机所治理的内存中最大的一块,被所有线程共享的,在虚拟机启动时创立。堆用来寄存对象实例,Java 里简直所有对象实例都在堆分配内存。堆能够处于物理上不间断的内存空间,逻辑上应该间断,但对于例如数组这样的大对象,少数虚拟机实现出于简略、存储高效的思考会要求间断的内存空间。

堆既能够被实现成固定大小,也能够是可扩大的,可通过 -Xms-Xmx 设置堆的最小和最大容量,以后支流 JVM 都依照可扩大实现。如果堆没有内存实现实例调配也无奈扩大,抛出 OutOfMemoryError。

办法区的作用是什么?

办法区 用于存储被虚拟机加载的类型信息、常量、动态变量、即时编译器编译后的代码缓存等数据。

JDK8 之前应用永恒代实现办法区,容易内存溢出,因为永恒代有 -XX:MaxPermSize 下限,即便不设置也有默认大小。JDK7 把放在永恒代的字符串常量池、动态变量等移出,JDK8 中永恒代齐全废除,改用在本地内存中实现的元空间代替,把 JDK 7 中永恒代残余内容(次要是类型信息)全副移到元空间。

虚拟机标准对办法区的束缚宽松,除和堆一样不须要间断内存和可抉择固定大小 / 可扩大外,还能够不实现垃圾回收。垃圾回收在办法区呈现较少,次要指标针对常量池和类型卸载。如果办法区无奈满足新的内存调配需要,将抛出 OutOfMemoryError。

运行时常量池的作用是什么?

运行时常量池是办法区的一部分,Class 文件中除了有类的版本、字段、办法、接口等形容信息外,还有一项信息是常量池表,用于寄存编译器生成的各种字面量与符号援用,这部分内容在类加载后寄存到运行时常量池。个别除了保留 Class 文件中形容的符号援用外,还会把符号援用翻译的间接援用也存储在运行时常量池。

运行时常量池绝对于 Class 文件常量池的一个重要特色是动态性,Java 不要求常量只有编译期能力产生,运行期间也能够将新的常量放入池中,这种个性利用较多的是 String 的 intern 办法。

运行时常量池是办法区的一部分,受到办法区内存的限度,当常量池无奈再申请到内存时会抛出 OutOfMemoryError。

间接内存是什么?

间接内存不属于运行时数据区,也不是虚拟机标准定义的内存区域,但这部分内存被频繁应用,而且可能导致内存溢出。

JDK1.4 中新退出了 NIO 这种基于通道与缓冲区的 IO,它能够应用 Native 函数库间接调配堆外内存,通过一个堆里的 DirectByteBuffer 对象作为内存的援用进行操作,防止了在 Java 堆和 Native 堆来回复制数据。

间接内存的调配不受 Java 堆大小的限度,但还是会受到本机总内存及处理器寻址空间限度,个别配置虚拟机参数时会依据理论内存设置 -Xmx 等参数信息,但常常疏忽间接内存,使内存区域总和大于物理内存限度,导致动静扩大时呈现 OOM。

由间接内存导致的内存溢出,一个显著的特色是在 Heap Dump 文件中不会看见显著的异样,如果发现内存溢出后产生的 Dump 文件很小,而程序中又间接或间接应用了间接内存(典型的间接应用就是 NIO),那么就能够思考查看间接内存方面的起因。

内存溢出和内存透露的区别?

内存溢出 OutOfMemory,指程序在申请内存时,没有足够的内存空间供其应用。

内存泄露 Memory Leak,指程序在申请内存后,无奈开释已申请的内存空间,内存透露最终将导致内存溢出。

堆溢出的起因?

堆用于存储对象实例,只有一直创建对象并保障 GC Roots 到对象有可达门路防止垃圾回收,随着对象数量的减少,总容量涉及最大堆容量后就会 OOM,例如在 while 死循环中始终 new 创立实例。

堆 OOM 是理论利用中最常见的 OOM,解决办法是通过内存映像剖析工具对 Dump 出的堆转储快照剖析,确认内存中导致 OOM 的对象是否必要,分清到底是内存透露还是内存溢出。

如果是内存透露,通过工具查看透露对象到 GC Roots 的援用链,找到泄露对象是通过怎么的援用门路、与哪些 GC Roots 关联才导致无奈回收,个别能够精确定位到产生内存透露代码的具 * 置。

如果不是内存透露,即内存中对象都必须存活,该当查看 JVM 堆参数,与机器内存相比是否还有向上调整的空间。再从代码查看是否存在某些对象生命周期过长、持有状态工夫过长、存储结构设计不合理等状况,尽量减少程序运行期的内存耗费。

栈溢出的起因?

因为 HotSpot 不辨别虚拟机和本地办法栈,设置本地办法栈大小的参数没有意义,栈容量只能由 -Xss 参数来设定,存在两种异样:

StackOverflowError: 如果线程申请的栈深度大于虚拟机所容许的深度,将抛出 StackOverflowError,例如一个递归办法一直调用本人。该异样有明确谬误堆栈可供剖析,容易定位到问题所在。

OutOfMemoryError: 如果 JVM 栈能够动静扩大,当扩大无奈申请到足够内存时会抛出 OutOfMemoryError。HotSpot 不反对虚拟机栈扩大,所以除非在创立线程申请内存时就因无奈取得足够内存而呈现 OOM,否则在线程运行时是不会因为扩大而导致溢出的。

运行时常量池溢出的起因?

String 的 intern 办法是一个本地办法,作用是如果字符串常量池中已蕴含一个等于此 String 对象的字符串,则返回池中这个字符串的 String 对象的援用,否则将此 String 对象蕴含的字符串增加到常量池并返回此 String 对象的援用。

在 JDK6 及之前常量池调配在永恒代,因而能够通过 -XX:PermSize-XX:MaxPermSize 限度永恒代大小,间接限度常量池。在 while 死循环中调用 intern 办法导致运行时常量池溢出。在 JDK7 后不会呈现该问题,因为寄存在永恒代的字符串常量池曾经被移至堆中。

办法区溢出的起因?

办法区次要寄存类型信息,如类名、拜访修饰符、常量池、字段形容、办法形容等。只有一直在运行时产生大量类,办法区就会溢出。例如应用 JDK 反射或 CGLib 间接操作字节码在运行时生成大量的类。很多框架如 Spring、Hibernate 等对类加强时都会应用 CGLib 这类字节码技术,加强的类越多就须要越大的办法区保障动静生成的新类型能够载入内存,也就更容易导致办法区溢出。

JDK8 应用元空间取代永恒代,HotSpot 提供了一些参数作为元空间进攻措施,例如 -XX:MetaspaceSize 指定元空间初始大小,达到该值会触发 GC 进行类型卸载,同时收集器会对该值进行调整,如果开释大量空间就适当升高该值,如果开释很少空间就适当进步。

创建对象的过程是什么?

字节码角度

  • NEW: 如果找不到 Class 对象则进行类加载。加载胜利后在堆中分配内存,从 Object 到本类门路上的所有属性都要调配。调配结束后进行零值设置。最初将指向实例对象的援用变量压入虚拟机栈顶。
  • DUP: 在栈顶复制援用变量,这时栈顶有两个指向堆内实例的援用变量。两个援用变量的目标不同,栈底的援用用于赋值或保留局部变量表,栈顶的援用作为句柄调用相干办法。
  • INVOKESPECIAL: 通过栈顶的援用变量调用 init 办法。

执行角度

① 当 JVM 遇到字节码 new 指令时,首先将查看该指令的参数是否在常量池中定位到一个类的符号援用,并查看援用代表的类是否已被加载、解析和初始化,如果没有就先执行类加载。

② 在类加载查看通过后虚拟机将为新生对象分配内存。

③ 内存调配实现后虚拟机将成员变量设为零值,保障对象的实例字段能够不赋初值就应用。

④ 设置对象头,包含哈希码、GC 信息、锁信息、对象所属类的类元信息等。

⑤ 执行 init 办法,初始化成员变量,执行实例化代码块,调用类的构造方法,并把堆内对象的首地址赋值给援用变量。

对象分配内存的形式有哪些?

对象所需内存大小在类加载实现后便可齐全确定,调配空间的工作实际上等于把一块确定大小的内存块从 Java 堆中划分进去。

指针碰撞: 假如 Java 堆内存规整,被应用过的内存放在一边,闲暇的放在另一边,两头放着一个指针作为分界指示器,分配内存就是把指针向闲暇方向移动一段与对象大小相等的间隔。

闲暇列表: 如果 Java 堆内存不规整,虚拟机必须保护一个列表记录哪些内存可用,在调配时从列表中找到一块足够大的空间划分给对象并更新列表记录。

抉择哪种调配形式由堆是否规整决定,堆是否规整由垃圾收集器是否有空间压缩能力决定。应用 Serial、ParNew 等收集器时,零碎采纳指针碰撞;应用 CMS 这种基于革除算法的垃圾收集器时,采纳空间列表。

对象分配内存是否线程平安?

对象创立非常频繁,即便批改一个指针的地位在并发下也不是线程平安的,可能正给对象 A 分配内存,指针还没来得及批改,对象 B 又应用了指针来分配内存。

解决办法:① CAS 加失败重试保障更新原子性。② 把内存调配按线程划分在不同空间,即每个线程在 Java 堆中事后调配一小块内存,叫做本地线程调配缓冲 TLAB,哪个线程要分配内存就在对应的 TLAB 调配,TLAB 用完了再进行同步。

对象的内存布局理解吗?

对象在堆内存的存储布局可分为对象头、实例数据和对齐填充。

对象头 占 12B,包含对象标记和类型指针。对象标记存储对象本身的运行时数据,如哈希码、GC 分代年龄、锁标记、偏差线程 ID 等,这部分占 8B,称为 Mark Word。Mark Word 被设计为动静数据结构,以便在极小的空间存储更多数据,依据对象状态复用存储空间。

类型指针是对象指向它的类型元数据的指针,占 4B。JVM 通过该指针来确定对象是哪个类的实例。

实例数据 是对象真正存储的无效信息,即本类对象的实例成员变量和所有可见的父类成员变量。存储程序会受到虚拟机调配策略参数和字段在源码中定义程序的影响。雷同宽度的字段总是被调配到一起寄存,在满足该前提条件的状况下父类中定义的变量会呈现在子类之前。

对齐填充 不是必然存在的,仅起占位符作用。虚拟机的主动内存管理系统要求任何对象的大小必须是 8B 的倍数,对象头已被设为 8B 的 1 或 2 倍,如果对象实例数据局部没有对齐,须要对齐填充补全。

对象的拜访形式有哪些?

Java 程序会通过栈上的 reference 援用操作堆对象,拜访形式由虚拟机决定,支流拜访形式次要有句柄和间接指针。

句柄: 堆会划分出一块内存作为句柄池,reference 中存储对象的句柄地址,句柄蕴含对象实例数据与类型数据的地址信息。长处是 reference 中存储的是稳固句柄地址,在 GC 过程中对象被挪动时只会扭转句柄的实例数据指针,而 reference 自身不须要批改。

间接指针: 堆中对象的内存布局就必须思考如何搁置拜访类型数据的相干信息,reference 存储对象地址,如果只是拜访对象自身就不须要多一次间接拜访的开销。长处是速度更快,节俭了一次指针定位的工夫开销,HotSpot 次要应用间接指针进行对象拜访。

如何判断对象是否是垃圾?

援用计数:在对象中增加一个援用计数器,如果被援用计数器加 1,援用生效时计数器减 1,如果计数器为 0 则被标记为垃圾。原理简略,效率高,然而在 Java 中很少应用,因为存在对象间循环援用的问题,导致计数器无奈清零。

可达性剖析:支流语言的内存治理都应用可达性分析判断对象是否存活。基本思路是通过一系列称为 GC Roots 的根对象作为起始节点集,从这些节点开始,依据援用关系向下搜寻,搜寻过程走过的门路称为援用链,如果某个对象到 GC Roots 没有任何援用链相连,则会被标记为垃圾。可作为 GC Roots 的对象包含虚拟机栈和本地办法栈中援用的对象、类动态属性援用的对象、常量援用的对象。

Java 的援用有哪些类型?

JDK1.2 后对援用进行了裁减,按强度分为四种:

强援用: 最常见的援用,例如 Object obj = new Object() 就属于强援用。只有对象有强援用指向且 GC Roots 可达,在内存回收时即便濒临内存耗尽也不会被回收。

软援用: 弱于强援用,形容非必须对象。在零碎将产生内存溢出前,会把软援用关联的对象退出回收范畴以取得更多内存空间。用来缓存服务器两头计算结果及不须要实时保留的用户行为等。

弱援用: 弱于软援用,形容非必须对象。弱援用关联的对象只能生存到下次 YGC 前,当垃圾收集器开始工作时无论以后内存是否足够都会回收只被弱援用关联的对象。因为 YGC 具备不确定性,因而弱援用何时被回收也不确定。

虚援用: 最弱的援用,定义实现后无奈通过该援用获取对象。惟一目标就是为了能在对象被回收时收到一个零碎告诉。虚援用必须与援用队列联结应用,垃圾回收时如果呈现虚援用,就会在回收对象前把这个虚援用退出援用队列。

有哪些 GC 算法?

标记 - 革除算法

分为标记和革除阶段,首先从每个 GC Roots 登程顺次标记有援用关系的对象,最初革除没有标记的对象。

执行效率不稳固,如果堆蕴含大量对象且大部分须要回收,必须进行大量标记革除,导致效率随对象数量增长而升高。

存在内存空间碎片化问题,会产生大量不间断的内存碎片,导致当前须要调配大对象时容易触发 Full GC。

标记 - 复制算法

为了解决内存碎片问题,将可用内存按容量划分为大小相等的两块,每次只应用其中一块。当应用的这块空间用完了,就将存活对象复制到另一块,再把已应用过的内存空间一次清理掉。次要用于进行新生代。

实现简略、运行高效,解决了内存碎片问题。代价是可用内存放大为原来的一半,节约空间。

HotSpot 把新生代划分为一块较大的 Eden 和两块较小的 Survivor,每次分配内存只应用 Eden 和其中一块 Survivor。垃圾收集时将 Eden 和 Survivor 中依然存活的对象一次性复制到另一块 Survivor 上,而后间接清理掉 Eden 和已用过的那块 Survivor。HotSpot 默认 Eden 和 Survivor 的大小比例是 8:1,即每次新生代中可用空间为整个新生代的 90%。

标记 - 整顿算法

标记 - 复制算法在对象存活率高时要进行较多复制操作,效率低。如果不想节约空间,就须要有额定空间调配担保,应答被应用内存中所有对象都存活的极其状况,所以老年代个别不应用此算法。

老年代应用标记 - 整顿算法,标记过程与标记 - 革除算法一样,但不间接清理可回收对象,而是让所有存活对象都向内存空间一端挪动,而后清理掉边界以外的内存。

标记 - 革除与标记 - 整顿的差别在于前者是一种非移动式算法而后者是移动式的。如果挪动存活对象,尤其是在老年代这种每次回收都有大量对象存活的区域,是一种极为负重的操作,而且挪动必须全程暂停用户线程。如果不挪动对象就会导致空间碎片问题,只能依赖更简单的内存分配器和拜访器解决。

你晓得哪些垃圾收集器?

Serial

最根底的收集器,应用复制算法、单线程工作,只用一个处理器或一条线程实现垃圾收集,进行垃圾收集时必须暂停其余所有工作线程。

Serial 是虚拟机在客户端模式的默认新生代收集器,简略高效,对于内存受限的环境它是所有收集器中额定内存耗费最小的,对于处理器外围较少的环境,Serial 因为没有线程交互开销,可取得最高的单线程收集效率。

ParNew

Serial 的多线程版本,除了应用多线程进行垃圾收集外其余行为完全一致。

ParNew 是虚拟机在服务端模式的默认新生代收集器,一个重要起因是除了 Serial 外只有它能与 CMS 配合。自从 JDK 9 开始,ParNew 加 CMS 不再是官网举荐的解决方案,官网心愿它被 G1 取代。

Parallel Scavenge

新生代收集器,基于复制算法,是可并行的多线程收集器,与 ParNew 相似。

特点是它的关注点与其余收集器不同,Parallel Scavenge 的指标是达到一个可管制的吞吐量,吞吐量就是处理器用于运行用户代码的工夫与处理器耗费总工夫的比值。

Serial Old

Serial 的老年代版本,单线程工作,应用标记 - 整顿算法。

Serial Old 是虚拟机在客户端模式的默认老年代收集器,用于服务端有两种用处:① JDK5 及之前与 Parallel Scavenge 搭配。② 作为 CMS 失败预案。

Parellel Old

Parallel Scavenge 的老年代版本,反对多线程,基于标记 - 整顿算法。JDK6 提供,重视吞吐量可思考 Parallel Scavenge 加 Parallel Old。

CMS

以获取最短回收进展工夫为指标,基于标记 - 革除算法,过程绝对简单,分为四个步骤:初始标记、并发标记、从新标记、并发革除。

初始标记和从新标记须要 STW(Stop The World,零碎进展),初始标记仅是标记 GC Roots 能间接关联的对象,速度很快。并发标记从 GC Roots 的间接关联对象开始遍历整个对象图,耗时较长但不须要进展用户线程。从新标记则是为了修改并发标记期间因用户程序运作而导致标记产生变动的那局部记录。并发革除清理标记阶段判断的已死亡对象,不须要挪动存活对象,该阶段也可与用户线程并发。

毛病:① 对处理器资源敏感,并发阶段尽管不会导致用户线程暂停,但会升高吞吐量。② 无奈解决浮动垃圾,有可能呈现并发失败而导致 Full GC。③ 基于标记 - 革除算法,产生空间碎片。

G1

创始了收集器面向部分收集的设计思路和基于 Region 的内存布局,次要面向服务端,最后设计指标是替换 CMS。

G1 之前的收集器,垃圾收集指标要么是整个新生代,要么是整个老年代或整个堆。而 G1 可面向堆任何局部来组成回收集进行回收,衡量标准不再是分代,而是哪块内存中寄存的垃圾数量最多,回收受害最大。

跟踪各 Region 里垃圾的价值,价值即回收所获空间大小以及回收所需工夫的经验值,在后盾保护一个优先级列表,每次依据用户设定容许的收集进展工夫优先解决回收价值最大的 Region。这种形式保障了 G1 在无限工夫内获取尽可能高的收集效率。

G1 运作过程:

  • 初始标记:标记 GC Roots 能间接关联到的对象,让下一阶段用户线程并发运行时能正确地在可用 Region 中调配新对象。须要 STW 但耗时很短,在 Minor GC 时同步实现。
  • 并发标记:从 GC Roots 开始对堆中对象进行可达性剖析,递归扫描整个堆的对象图。耗时长但可与用户线程并发,扫描实现后要重新处理 SATB 记录的在并发时有变动的对象。
  • 最终标记:对用户线程做短暂暂停,解决并发阶段完结后仍遗留下来的大量 SATB 记录。
  • 筛选回收:对各 Region 的回收价值排序,依据用户冀望进展工夫制订回收打算。必须暂停用户线程,由多条收集线程并行实现。

可由用户指定冀望进展工夫是 G1 的一个弱小性能,但该值不能设得太低,个别设置为 100~300 ms。

ZGC 理解吗?

JDK11 中退出的具备试验性质的低提早垃圾收集器,指标是尽可能在不影响吞吐量的前提下,实现在任意堆内存大小都能够把进展工夫限度在 10ms 以内的低提早。

基于 Region 内存布局,不设分代,应用了读屏障、染色指针和内存多重映射等技术实现可并发的标记 - 整顿,以低提早为首要指标。

ZGC 的 Region 具备动态性,是动态创建和销毁的,并且容量大小也是动态变化的。

你晓得哪些内存调配与回收策略?

对象优先在 Eden 区调配

大多数状况下对象在新生代 Eden 区调配,当 Eden 没有足够空间时将发动一次 Minor GC。

大对象间接进入老年代

大对象指须要大量间断内存空间的对象,典型是很长的字符串或数量宏大的数组。大对象容易导致内存还有不少空间就提前触发垃圾收集以取得足够的间断空间。

HotSpot 提供了 -XX:PretenureSizeThreshold 参数,大于该值的对象间接在老年代调配,防止在 Eden 和 Survivor 间来回复制。

长期存活对象进入老年代

虚拟机给每个对象定义了一个对象年龄计数器,存储在对象头。如果经验过第一次 Minor GC 依然存活且能被 Survivor 包容,该对象就会被挪动到 Survivor 中并将年龄设置为 1。对象在 Survivor 中每熬过一次 Minor GC 年龄就加 1,当减少到肯定水平(默认 15)就会被降职到老年代。对象降职老年代的阈值可通过 -XX:MaxTenuringThreshold 设置。

动静对象年龄断定

为了适应不同内存情况,虚拟机不要求对象年龄达到阈值能力降职老年代,如果在 Survivor 中雷同年龄所有对象大小的总和大于 Survivor 的一半,年龄不小于该年龄的对象就能够间接进入老年代。

空间调配担保

MinorGC 前虚拟机必须查看老年代最大可用间断空间是否大于新生代对象总空间,如果满足则阐明这次 Minor GC 确定平安。

如果不满足,虚构机会查看 -XX:HandlePromotionFailure 参数是否容许担保失败,如果容许会持续查看老年代最大可用间断空间是否大于历次降职老年代对象的均匀大小,如果满足将冒险尝试一次 Minor GC,否则改成一次 FullGC。

冒险是因为新生代应用复制算法,为了内存利用率只应用一个 Survivor,大量对象在 Minor GC 后依然存活时,须要老年代进行调配担保,接管 Survivor 无奈包容的对象。

你晓得哪些故障解决工具?

jps:虚拟机过程情况工具

性能和 ps 命令相似:能够列出正在运行的虚拟机过程,显示虚拟机执行主类名称以及这些过程的本地虚拟机惟一 ID(LVMID)。LVMID 与操作系统的过程 ID(PID)统一,应用 Windows 的工作管理器或 UNIX 的 ps 命令也能够查问到虚拟机过程的 LVMID,但如果同时启动了多个虚拟机过程,必须依赖 jps 命令。

jstat:虚拟机统计信息监督工具

用于监督虚拟机各种运行状态信息。能够显示本地或近程虚拟机过程中的类加载、内存、垃圾收集、即时编译器等运行时数据,在没有 GUI 界面的服务器上是运行期定位虚拟机性能问题的常用工具。

参数含意:S0 和 S1 示意两个 Survivor,E 示意新生代,O 示意老年代,YGC 示意 Young GC 次数,YGCT 示意 Young GC 耗时,FGC 示意 Full GC 次数,FGCT 示意 Full GC 耗时,GCT 示意 GC 总耗时。

jinfo:Java 配置信息工具

实时查看和调整虚拟机各项参数,应用 jps 的 -v 参数能够查看虚拟机启动时显式指定的参数,但如果想晓得未显式指定的参数值只能应用 jinfo 的 -flag 查问。

jmap:Java 内存映像工具

用于生成堆转储快照,还能够查问 finalize 执行队列、Java 堆和办法区的详细信息,如空间使用率,以后应用的是哪种收集器等。和 jinfo 一样,局部性能在 Windows 受限,除了生成堆转储快照的 -dump 和查看每个类实例的 -histo 外,其余选项只能在 Linux 应用。

jhat:虚拟机堆转储快照剖析工具

JDK 提供 jhat 与 jmap 搭配应用剖析 jmap 生成的堆转储快照。jhat 内置了一个微型的 HTTP/Web 服务器,生成堆转储快照的剖析后果后能够在浏览器查看。

jstack:Java 堆栈跟踪工具

用于生成虚拟机以后时刻的线程快照。线程快照就是以后虚拟机内每一条线程正在执行的办法堆栈的汇合,生成线程快照的目标通常是定位线程呈现长时间进展的起因,如线程间死锁、死循环、申请内部资源导致的长时间挂起等。线程呈现进展时通过 jstack 查看各个线程的调用堆栈,能够获知没有响应的线程在后盾做什么或等什么资源。

Java 程序是怎么运行的?

  • 首先通过 Javac 编译器将 .java 转为 JVM 可加载的 .class 字节码文件。

    Javac 是由 Java 编写的程序,编译过程能够分为:① 词法解析,通过空格宰割出单词、操作符、控制符等信息,造成 token 信息流,传递给语法解析器。② 语法解析,把 token 信息流依照 Java 语法规定组装成语法树。③ 语义剖析,查看关键字应用是否正当、类型是否匹配、作用域是否正确等。④ 字节码生成,将后面各个步骤的信息转换为字节码。

    字节码必须通过类加载过程加载到 JVM 后才能够执行,执行有三种模式,解释执行、JIT 编译执行、JIT 编译与解释器混合执行(支流 JVM 默认执行的形式)。混合模式的劣势在于解释器在启动时先解释执行,省去编译工夫。

  • 之后通过即时编译器 JIT 把字节码文件编译成本地机器码。

    Java 程序最后都是通过解释器进行解释执行的,当虚拟机发现某个办法或代码块的运行特地频繁,就会认定其为 ” 热点代码 ”,热点代码的检测次要有基于采样和基于计数器两种形式,为了进步热点代码的执行效率,虚构机会把它们编译成本地机器码,尽可能对代码优化,在运行时实现这个工作的后端编译器被称为即时编译器。

  • 还能够通过动态的提前编译器 AOT 间接把程序编译成与指标机器指令集相干的二进制代码。

类加载是什么?

Class 文件中形容的各类信息都须要加载到虚拟机后能力应用。JVM 把形容类的数据从 Class 文件加载到内存,并对数据进行校验、解析和初始化,最终造成能够被虚拟机间接应用的 Java 类型,这个过程称为虚拟机的类加载机制。

与编译时须要连贯的语言不同,Java 中类型的加载、连贯和初始化都是在运行期间实现的,这减少了性能开销,但却提供了极高的扩展性,Java 动静扩大的语言个性就是依赖运行期动静加载和连贯实现的。

一个类型从被加载到虚拟机内存开始,到卸载出内存为止,整个生命周期经验加载、验证、筹备、解析、初始化、应用和卸载七个阶段,其中验证、解析和初始化三个局部称为连贯。加载、验证、筹备、初始化阶段的程序是确定的,解析则不肯定:可能在初始化之后再开始,这是为了反对 Java 的动静绑定。

类初始化的状况有哪些?

① 遇到 newgetstaticputstaticinvokestatic 字节码指令时,还未初始化。典型场景包含 new 实例化对象、读取或设置动态字段、调用静态方法。

② 对类反射调用时,还未初始化。

③ 初始化类时,父类还未初始化。

④ 虚拟机启动时,会先初始化蕴含 main 办法的主类。

⑤ 应用 JDK7 的动静语言反对时,如果 MethodHandle 实例的解析后果为指定类型的办法句柄且句柄对应的类还未初始化。

⑥ 接口定义了默认办法,如果接口的实现类初始化,接口要在其之前初始化。

其余所有援用类型的形式都不会触发初始化,称为被动援用。被动援用实例:① 子类应用父类的动态字段时,只有父类被初始化。② 通过数组定义应用类。③ 常量在编译期会存入调用类的常量池,不会初始化定义常量的类。

接口和类加载过程的区别:初始化类时如果父类没有初始化须要初始化父类,但接口初始化时不要求父接口初始化,只有在真正应用父接口时(如援用接口中定义的常量)才会初始化。

类加载的过程是什么?

加载

该阶段虚拟机须要实现三件事:① 通过一个类的全限定类名获取定义类的二进制字节流。② 将字节流所代表的动态存储构造转化为办法区的运行时数据区。③ 在内存中生成对应该类的 Class 实例,作为办法区这个类的数据拜访入口。

验证

确保 Class 文件的字节流合乎束缚。如果虚拟机不查看输出的字节流,可能因为载入有谬误或歹意希图的字节流而导致系统受攻打。验证次要蕴含四个阶段:文件格式验证、元数据验证、字节码验证、符号援用验证。

验证重要但非必须,因为只有通过与否的区别,通过后对程序运行期没有任何影响。如果代码已被重复应用和验证过,在生产环境就能够思考敞开大部分验证缩短类加载工夫。

筹备

为类动态变量分配内存并设置零值,该阶段进行的内存调配仅包含类变量,不包含实例变量。如果变量被 final 润饰,编译时 Javac 会为变量生成 ConstantValue 属性,筹备阶段虚构机会将变量值设为代码值。

解析

将常量池内的符号援用替换为间接援用。

符号援用 以一组符号形容援用指标,能够是任何模式的字面量,只有应用时能无歧义地定位指标即可。与虚拟机内存布局无关,援用指标不肯定曾经加载到虚拟机内存。

间接援用 是能够间接指向指标的指针、绝对偏移量或能间接定位到指标的句柄。和虚拟机的内存布局相干,援用指标必须已在虚拟机的内存中存在。

初始化

直到该阶段 JVM 才开始执行类中编写的代码。筹备阶段时变量赋过零值,初始化阶段会依据程序员的编码去初始化类变量和其余资源。初始化阶段就是执行类构造方法中的 <client> 办法,该办法是 Javac 主动生成的。

有哪些类加载器?

自 JDK1.2 起 Java 始终放弃三层类加载器:

  • 启动类加载器

    在 JVM 启动时创立,负责加载最外围的类,例如 Object、System 等。无奈被程序间接援用,如果须要把加载委派给启动类加载器,间接应用 null 代替即可,因为启动类加载器通常由操作系统实现,并不存在于 JVM 体系。

  • 平台类加载器

    从 JDK9 开始从扩大类加载器更换为平台类加载器,负载加载一些扩大的零碎类,比方 XML、加密、压缩相干的性能类等。

  • 利用类加载器

    也称零碎类加载器,负责加载用户类门路上的类库,能够间接在代码中应用。如果没有自定义类加载器,个别状况下利用类加载器就是默认的类加载器。自定义类加载器通过继承 ClassLoader 并重写 findClass 办法实现。

双亲委派模型是什么?

类加载用具有等级制度但非继承关系,以组合的形式复用父加载器的性能。双亲委派模型要求除了顶层的启动类加载器外,其余类加载器都应该有本人的父加载器。

一个类加载器收到了类加载申请,它不会本人去尝试加载,而将该申请委派给父加载器,每层的类加载器都是如此,因而所有加载申请最终都应该传送到启动类加载器,只有当父加载器反馈无奈实现申请时,子加载器才会尝试。

类追随它的加载器一起具备了有优先级的档次关系,确保某个类在各个类加载器环境中都是同一个,保障程序的稳定性。

如何判断两个类是否相等?

任意一个类都必须由类加载器和这个类自身独特确立其在虚拟机中的唯一性。

两个类只有由同一类加载器加载才有比拟意义,否则即便两个类来源于同一个 Class 文件,被同一个 JVM 加载,只有类加载器不同,这两个类就必然不相等。

总结

感激你看到这里,文章有什么有余还请斧正,感觉文章对你有帮忙的话记得给我点个赞,每天都会分享 java 相干技术文章或行业资讯,欢送大家关注和转发文章!
欢送关注公众号:前程有光,支付一线大厂 Java 面试题总结 + 各知识点学习思维导 + 一份 300 页 pdf 文档的 Java 外围知识点总结!

正文完
 0