JVM是可运行Java代码的假想计算机 ,包括一套字节码指令集、一组寄存器、一个栈、 一个垃圾回收,堆 和 一个存储方法域。JVM是运行在操作系统之上的,它与硬件没有直接 的交互。
一、JDK和JRE,JVM的区别
二、如何理解Java是跨平台的语言
Java语言本质上是不能跨平台的,真正实现跨平台的是JVM,也就是Java虚拟机。写好的Java源文件通过Javac命令编译生成class文件(中间文件),然后JVM对class文件进行执行生成机器语言然后机器语言在平台中操作,Java在不同的平台下都有对应的不同版本的JVM,JVM可以识别字节码文件从而运行。
二、JAVA运行时数据区包括哪几部分
- PC 寄存器(PC Register),也叫程序计数器(Program Counter Register),是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的信号指示器。
- JVM 栈(Java Virtual Machine Stack),与 PC 寄存器一样,JVM 栈也是线程私有的。每一个 JVM 线程都有自己的 JVM 栈(也叫方法栈),这个栈与线程同时创建,它的生命周期与线程相同。
- 本地方法栈(Native Method Stack),JVM 可能会使用到传统的栈来支持 Native 方法open in new window的执行,这个栈就是本地方法栈。
- 堆(Heap),在 JVM 中,堆是可供各条线程共享的运行时内存区域,也是供所有类实例和数据对象分配内存的区域。
- 方法区(Method area),JDK 8 开始,使用元空间取代了永久代。方法区是 JVM 中的一个逻辑区域,用于存储类的结构信息,包括类的定义、方法的定义、字段的定义以及字节码指令。不同的是,元空间不再是 JVM 内存的一部分,而是通过本地内存(Native Memory)来实现的。
- 运行时常量池open in new window,运行时常量池是每一个类或接口的常量在运行时的表现形式,它包括了编译器可知的数值字面量,以及运行期解析后才能获得的方法或字段的引用。简而言之,当一个方法或者变量被引用时,JVM 通过运行时常量区来查找方法或者变量在内存里的实际地址。
其中 PC 寄存器、虚拟机栈以及本地方法栈属于线程私有的,堆以及元空间(方法区的实现)属于共享数据区,不同的线程共享这两部分内存数据。
三、java和C++比速度
为了优化 Java 的性能 ,JVM 在解释器之外引入了 JIT 编译器:当程序运行时,解释器首先发挥作用,代码可以直接执行。随着时间推移,即时编译器逐渐发挥作用,把越来越多的代码编译优化成本地代码,来获取更高的执行效率。解释器这时可以作为编译运行的降级手段,在一些不可靠的编译优化出现问题时,再切换回解释执行,保证程序可以正常运行。
逃逸分析(Escape Analysis)是一种编译器优化技术,用于判断对象的作用域和生命周期。如果编译器确定一个对象不会逃逸出方法或线程的范围,它可以选择在栈上分配这个对象,而不是在堆上。这样做可以减少垃圾回收的压力,并提高性能。
四、虚拟机栈帧包括哪些
五、字节码文件有哪些部分组成
六、常量池中有哪些信息
字面量
字面量就是指由字母、数字等构成的字符串或者数值常量。
字面量只可以右值出现,所谓右值是指等号右边的值
如:int a=1 这里的a为左值,1为右值。在这个例子中1就是字面量。
符号引用
符号引用是编译原理中的概念,是相对于直接引用来说的。主要包括了以下三类常量:
类和接口的全限定名
字段的名称和描述符
方法的名称和描述符
类加载器将字节码的信息写入到方法区中(1.8后叫做永久代),通过堆区有个lang对象访问这个Kclass
七、类的生命周期
类的初始化阶段是执行静态代码块(clinit方法),并为静态变量赋值。
八、类加载器的分类
九、GC Roots 到底是什么东西呢,哪些对象可以作为 GC Root 呢?
-
虚拟机栈(栈帧中的本地变量表)中引用的对象
-
本地方法栈中 JNI(即一般说的 Native 方法)引用的对象
-
方法区中类静态属性引用的对象
-
方法区中常量引用的对象
十、垃圾收集算法
标记清除算法
赋值算法
标记整理算法
分代收集算法
新生代和老年代
十一、垃圾收集器
1、CMS
CMS 垃圾收集器通过三色标记算法,实现了垃圾回收线程与用户线程的并发执行,从而极大地降低了系统响应时间,提高了强交互应用程序的体验。它的运行过程分为 4 个步骤,包括:
- 初始标记
- 并发标记
- 重新标记
- 并发清除
初始标记,指的是寻找所有被 GCRoots 引用的对象,该阶段需要「Stop the World」。这个步骤仅仅只是标记一下 GC Roots 能直接关联到的对象,并不需要做整个引用的扫描,因此速度很快。
并发标记,指的是对「初始标记阶段」标记的对象进行整个引用链的扫描,该阶段不需要「Stop the World」。 对整个引用链做扫描需要花费非常多的时间,因此通过垃圾回收线程与用户线程并发执行,可以降低垃圾回收的时间。
这也是 CMS 能极大降低 GC 停顿时间的核心原因,但这也带来了一些问题,即:并发标记的时候,引用可能发生变化,因此可能发生漏标(本应该回收的垃圾没有被回收)和多标(本不应该回收的垃圾被回收)了。
重新标记,指的是对「并发标记」阶段出现的问题进行校正,该阶段需要「Stop the World」。正如并发标记阶段说到的,由于垃圾回收算法和用户线程并发执行,虽然能降低响应时间,但是会发生漏标和多标的问题。所以对于 CMS 来说,它需要在这个阶段做一些校验,解决并发标记阶段发生的问题。
并发清除,指的是将标记为垃圾的对象进行清除,该阶段不需要「Stop the World」。 在这个阶段,垃圾回收线程与用户线程可以并发执行,因此并不影响用户的响应时间。
CMS 的优点是:并发收集、低停顿。但缺点也很明显:
①、对 CPU 资源非常敏感,因此在 CPU 资源紧张的情况下,CMS 的性能会大打折扣。
默认情况下,CMS 启用的垃圾回收线程数是(CPU数量 + 3)/4
,当 CPU 数量很大时,启用的垃圾回收线程数占比就越小。但如果 CPU 数量很小,例如只有 2 个 CPU,垃圾回收线程占用就达到了 50%,这极大地降低系统的吞吐量,无法接受。
②、CMS 采用的是「标记-清除」算法,会产生大量的内存碎片,导致空间不连续,当出现大对象无法找到连续的内存空间时,就会触发一次 Full GC,这会导致系统的停顿时间变长。
③、CMS 无法处理浮动垃圾,当 CMS 在进行垃圾回收的时候,应用程序还在不断地产生垃圾,这些垃圾会在 CMS 垃圾回收结束之后产生,这些垃圾就是浮动垃圾,CMS 无法处理这些浮动垃圾,只能在下一次 GC 时清理掉。
G1
G1 有专门分配大对象的 Region 叫 Humongous 区,而不是让大对象直接进入老年代的 Region 中。在 G1 中,大对象的判定规则就是一个大对象超过了一个 Region 大小的 50%,比如每个 Region 是 2M,只要一个对象超过了 1M,就会被放入 Humongous 中,而且一个大对象如果太大,可能会横跨多个 Region 来存放。
G1 会根据各个区域的垃圾回收情况来决定下一次垃圾回收的区域,这样就避免了对整个堆内存进行垃圾回收,从而降低了垃圾回收的时间。
②、增量:G1 可以以增量方式执行垃圾回收,这意味着它不需要一次性回收整个堆空间,而是可以逐步、增量地清理。有助于控制停顿时间,尤其是在处理大型堆时。
③、并行:G1 垃圾回收器可以并行回收垃圾,这意味着它可以利用多个 CPU 来加速垃圾回收的速度,这一特性在年轻代的垃圾回收(Minor GC)中特别明显,因为年轻代的回收通常涉及较多的对象和较高的回收速率。
④、标记整理:在进行老年代的垃圾回收时,G1 使用标记-整理算法。这个过程分为两个阶段:标记存活的对象和整理(压缩)堆空间。通过整理,G1 能够避免内存碎片化,提高内存利用率。
年轻代的垃圾回收(Minor GC)使用复制算法,因为年轻代的对象通常是朝生夕死的。
⑤、STW:G1 也是基于「标记-清除」算法,因此在进行垃圾回收的时候,仍然需要「Stop the World」。不过,G1 在停顿时间上添加了预测机制,用户可以指定期望停顿时间。
G1 中存在三种 GC 模式,分别是 Young GC、Mixed GC 和 Full GC。
当 Eden 区的内存空间无法支持新对象的内存分配时,G1 会触发 Young GC。
当需要分配对象到 Humongous 区域或者堆内存的空间占比超过 -XX:G1HeapWastePercent
设置的 InitiatingHeapOccupancyPercent 值时,G1 会触发一次 concurrent marking,它的作用就是计算老年代中有多少空间需要被回收,当发现垃圾的占比达到 -XX:G1HeapWastePercent
中所设置的 G1HeapWastePercent 比例时,在下次 Young GC 后会触发一次 Mixed GC。
Mixed GC 是指回收年轻代的 Region 以及一部分老年代中的 Region。Mixed GC 和 Young GC 一样,采用的也是复制算法。
在 Mixed GC 过程中,如果发现老年代空间还是不足,此时如果 G1HeapWastePercent 设定过低,可能引发 Full GC。-XX:G1HeapWastePercent
默认是 5,意味着只有 5% 的堆是“浪费”的。如果浪费的堆的百分比大于 G1HeapWastePercent,则运行 Full GC。
在以 Region 为最小管理单元以及所采用的 GC 模式的基础上,G1 建立了停顿预测模型,即 Pause Prediction Model 。这也是 G1 非常被人所称道的特性。
我们可以借助 -XX:MaxGCPauseMillis
来设置期望的停顿时间(默认 200ms),G1 会根据这个值来计算出一个合理的 Young GC 的回收时间,然后根据这个时间来制定 Young GC 的回收计划。
十二、类加载器包括哪些什么是双亲委派?
双亲委派模型的工作过程如下:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,因此所有的加载请求最终都应该传送到最顶层的启动类加载器,只有当父加载器反馈自己无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
即时编译器的分层编译
要编译出优化程度越高的代码通常都需要越长的编译时间,为了在程序启动速度与运行效率之间达到最佳平衡,HotSpot 在编译子系统中加入了分层编译(Tiered Compilation):
- 第 0 层:程序纯解释执行,并且解释器不开启性能监控功能;
- 第 1 层:使用客户端编译器将字节码编译为本地代码来运行,进行简单可靠的稳定优化,不开启性能监控功能;
- 第 2 层:仍然使用客户端编译执行,仅开启方法及回边次数统计等有限的性能监控;
- 第 3 层:仍然使用客户端编译执行,开启全部性能监控;
- 第 4 层:使用服务端编译器将字节码编译为本地代码,其耗时更长,并且会根据性能监控信息进行一些不可靠的激进优化。
十三、方法内联
最重要的优化手段,它会将目标方法中的代码原封不动地 “复制” 到发起调用的方法之中,避免发生真实的方法调用,并采用名为类型继承关系分析(Class Hierarchy Analysis,CHA)的技术来解决虚方法(Java 语言中默认的实例方法都是虚方法)的内联问题。
十四、逃逸分析
逃逸行为主要分为以下两类:
- 方法逃逸:当一个对象在方法里面被定义后,它可能被外部方法所引用,例如作为调用参数传递到其他方法中,此时称为方法逃逸;
- 线程逃逸:当一个对象在方法里面被定义后,它可能被外部线程所访问,例如赋值给可以在其他线程中访问的实例变量,此时称为线程逃逸,其逃逸程度高于方法逃逸。
-
如果能证明一个对象不会逃逸到方法或线程之外,或者逃逸程度比较低(只逃逸出方法而不会逃逸出线程),则可以对这个对象实例采取不同程序的优化:
- 栈上分配 (Stack Allocations) :如果一个对象不会逃逸到线程外,那么将会在栈上分配内存来创建这个对象,而不是 Java 堆上,此时对象所占用的内存空间就会随着栈帧的出栈而销毁,从而可以减轻垃圾回收的压力。
- 标量替换 (Scalar Replacement) :如果一个数据已经无法再分解成为更小的数据类型,那么这些数据就称为标量(如 int、long 等数值类型及 reference 类型等);反之,如果一个数据可以继续分解,那它就被称为聚合量(如对象)。如果一个对象不会逃逸外方法外,那么就可以将其改为直接创建若干个被这个方法使用的成员变量来替代,从而减少内存占用。
- 同步消除 (Synchronization Elimination) :如果一个变量不会逃逸出线程,那么对这个变量实施的同步措施就可以消除掉。
十五、内存泄漏
内存泄漏通常是指堆内存,通常是指一些大对象不被回收的情况
1 、通过 jmap 或设置 jvm 参数获取堆内存快照 dump
2 、通过工具, VisualVM 去分析 dump 文件, VisualVM 可以加载离线的 dump
文件
3 、通过查看堆信息的情况,可以大概定位内存溢出是哪行代码出了问题
4 、找到对应的代码,通过阅读上下文的情况,进行修复即可
十六、cpu百分百如和检查
1. 使用 top 命令查看占用 cpu 的情况
2. 通过 top 命令查看后,可以查看是哪一个进程占用 cpu 较高
3. 使用 ps 命令查看进程中的线程信息ps H -eo pid,tid,%cpu | grep 40940
4. 使用 jstack 命令查看进程中哪些线程出现了问题,最终定位问题