面渣逆袭:JVM经典五十问,这下面试稳了
引言
1.什么是JVM?
内存管理
2.能说一下JVM的内存区域吗?
3.说一下JDK1.6、1.7、1.8内存区域的变化?
4.为什么使用元空间替代永久代作为方法区的实现?
5.对象创建的过程了解吗?
6.什么是指针碰撞?什么是空闲列表?
7.JVM 里 new 对象时,堆会发生抢占吗?JVM是怎么设计来保证线程安全的?
8.能说一下对象的内存布局吗?
9.对象怎么访问定位?
10.内存溢出和内存泄漏是什么意思?
11.能手写内存溢出的例子吗?
12.内存泄漏可能由哪些原因导致呢?
13.如何判断对象仍然存活?
14.Java中可作为GC Roots的对象有哪几种?
15.说一下对象有哪几种引用?
16.finalize()方法了解吗?有什么作用?
17.Java堆的内存分区了解吗?
18.垃圾收集算法了解吗?
19.说一下新生代的区域划分?
20.Minor GC/Young GC、Major GC/Old GC、Mixed GC、Full GC都是什么意思?
21.Minor GC/Young GC什么时候触发?
22.什么时候会触发Full GC?
23.对象什么时候会进入老年代?
24.知道有哪些垃圾收集器吗?
25.什么是Stop The World ? 什么是 OopMap ?什么是安全点?
26.能详细说一下CMS收集器的垃圾收集过程吗?
27.G1垃圾收集器了解吗?
28.有了CMS,为什么还要引入G1?
29.你们线上用的什么垃圾收集器?为什么要用它?
30.垃圾收集器应该如何选择?
31.对象一定分配在堆中吗?有没有了解逃逸分析技术?
JVM调优
32.有哪些常用的命令行性能监控和故障处理工具?
33.了解哪些可视化的性能监控和故障处理工具?
34.JVM的常见参数配置知道哪些?
35.有做过JVM调优吗?
36.线上服务CPU占用过高怎么排查?
37.内存飙高问题怎么排查?
38.频繁 minor gc 怎么办?
39.频繁Full GC怎么办?
40.有没有处理过内存泄漏问题?是如何定位的?
41.有没有处理过内存溢出问题?
42.能说一下类的生命周期吗?
43.类加载的过程知道吗?
44.类加载器有哪些?
45.什么是双亲委派机制?
46.为什么要用双亲委派机制?
47.如何破坏双亲委派机制?
48.历史上有哪几次双亲委派机制的破坏?
49.你觉得应该怎么实现一个热部署功能?
50.Tomcat的类加载机制了解吗?
1.什么是JVM?
JVM——Java虚拟机,它是Java实现平台无关性的基石。
Java程序运行的时候,编译器将Java文件编译成平台无关的Java字节码文件(.class),接下来对应平台JVM对字节码文件进行解释,翻译成对应平台匹配的机器指令并运行。
Java语言编译运行
同时JVM也是一个跨语言的平台,和语言无关,只和class的文件格式关联,任何语言,只要能翻译成符合规范的字节码文件,都能被JVM运行。
JVM跨语言
内存管理
2.能说一下JVM的内存区域吗?
JVM内存区域最粗略的划分可以分为堆和栈,当然,按照虚拟机规范,可以划分为以下几个区域:
Java虚拟机运行时数据区
JVM内存分为线程私有区和线程共享区,其中方法区和堆是线程共享区,虚拟机栈、本地方法栈和程序计数器是线程隔离的数据区。
1、程序计数器
程序计数器(Program Counter Register)也被称为PC寄存器,是一块较小的内存空间。
它可以看作是当前线程所执行的字节码的行号指示器。
2、Java虚拟机栈
Java虚拟机栈(Java Virtual Machine Stack)也是线程私有的,它的生命周期与线程相同。
Java虚拟机栈描述的是Java方法执行的线程内存模型:方法执行时,JVM会同步创建一个栈帧,用来存储局部变量表、操作数栈、动态连接等。
Java虚拟机栈
3、本地方法栈
本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别只是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务。
Java 虚拟机规范允许本地方法栈被实现成固定大小的或者是根据计算动态扩展和收缩的。
4、Java堆
对于Java应用程序来说,Java堆(Java Heap)是虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,Java里“几乎”所有的对象实例都在这里分配内存。
Java堆是垃圾收集器管理的内存区域,因此一些资料中它也被称作“GC堆”(Garbage Collected Heap,)。从回收内存的角度看,由于现代垃圾收集器大部分都是基于分代收集理论设计的,所以Java堆中经常会出现新生代、老年代、Eden空间、From Survivor空间、To Survivor空间等名词,需要注意的是这种划分只是根据垃圾回收机制来进行的划分,不是Java虚拟机规范本身制定的。
Java 堆内存结构
5.方法区
方法区是比较特别的一块区域,和堆类似,它也是各个线程共享的内存区域,用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等数据。
它特别在Java虚拟机规范对它的约束非常宽松,所以方法区的具体实现历经了许多变迁,例如jdk1.7之前使用永久代作为方法区的实现。
3.说一下JDK1.6、1.7、1.8内存区域的变化?
JDK1.6、1.7/1.8内存区域发生了变化,主要体现在方法区的实现:
- JDK1.6使用永久代实现方法区:
- JDK1.7时发生了一些变化,将字符串常量池、静态变量,存放在堆上
JDK 1.7内存区域
- 在JDK1.8时彻底干掉了永久代,而在直接内存中划出一块区域作为元空间,运行时常量池、类常量池都移动到元空间。JDK 1.8内存区域
4.为什么使用元空间替代永久代作为方法区的实现?
Java虚拟机规范规定的方法区只是换种方式实现。有客观和主观两个原因。
- 客观上使用永久代来实现方法区的决定的设计导致了Java应用更容易遇到内存溢出的问题(永久代有-XX:MaxPermSize的上限,即使不设置也有默认大小,而J9和JRockit只要没有触碰到进程可用内存的上限,例如32位系统中的4GB限制,就不会出问题),而且有极少数方法 (例如String::intern())会因永久代的原因而导致不同虚拟机下有不同的表现。
- 主观上当Oracle收购BEA获得了JRockit的所有权后,准备把JRockit中的优秀功能,譬如Java Mission Control管理工具,移植到HotSpot 虚拟机时,但因为两者对方法区实现的差异而面临诸多困难。考虑到HotSpot未来的发展,在JDK 6的 时候HotSpot开发团队就有放弃永久代,逐步改为采用本地内存(Native Memory)来实现方法区的计划了,到了JDK 7的HotSpot,已经把原本放在永久代的字符串常量池、静态变量等移出,而到了 JDK 8,终于完全废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间(Meta-space)来代替,把JDK 7中永久代还剩余的内容(主要是类型信息)全部移到元空间中。
5.对象创建的过程了解吗?
在JVM中对象的创建,我们从一个new指令开始:
- 首先检查这个指令的参数是否能在常量池中定位到一个类的符号引用
- 检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,就先执行相应的类加载过程
- 类加载检查通过后,接下来虚拟机将为新生对象分配内存。
- 内存分配完成之后,虚拟机将分配到的内存空间(但不包括对象头)都初始化为零值。
- 接下来设置对象头,请求头里包含了对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。
这个过程大概图示如下:
对象创建过程
6.什么是指针碰撞?什么是空闲列表?
内存分配有两种方式,指针碰撞(Bump The Pointer)、空闲列表(Free List)。
指针碰撞和空闲列表
- 指针碰撞:假设Java堆中内存是绝对规整的,所有被使用过的内存都被放在一边,空闲的内存被放在另一边,中间放着一个指针作为分界点的指示器,那所分配内存就仅仅是把那个指针向空闲空间方向挪动一段与对象大小相等的距离,这种分配方式称为“指针碰撞”。
- 空闲列表:如果Java堆中的内存并不是规整的,已被使用的内存和空闲的内存相互交错在一起,那就没有办法简单地进行指针碰撞了,虚拟机就必须维护一个列表,记录上哪些内存块是可用的,在分配的时候从列表中找到一块足够大的空间划分给对象实例,并更新列表上的记录,这种分配方式称为“空闲列表”。
- 两种方式的选择由Java堆是否规整决定,Java堆是否规整是由选择的垃圾收集器是否具有压缩整理能力决定的。