- JVM
- 1. 什么是JVM?
- 2. 了解过字节码文件的组成吗?
- 3. 什么是运行时数据区
- 4. 哪些区域会出现内存溢出
- 5. JVM在JDK6-8之间在内存区域上有什么不同
- 6. 类的生命周期
- 7. 什么是类加载器?类加载器有哪几种
- 8. 什么是双亲委派机制?有什么好处
- 9. 如何打破双亲委派机制
- 10. 如何判断堆上的对象有没有被引用?
- 11. JVM 中都有哪些引用类型?
- 12. ThreadLocal中为什么要使用弱引用?
- 13. 有哪些常见的垃圾回收算法?
- 14. 分代GC算法为什么将堆分成新生代和老年代?
- 15. 有哪些常用的垃圾回收器
- 16. 内存泄漏的原因
JVM
1. 什么是JVM?
答:
- JVM 是运行 Java字节码文件的虚拟机,字节码文件和不同系统实现的JVM是实现跨平台的关键。
- JVM的功能有三项:
- 第一是解释执行字节码指令;
- 第二是管理内存中对象的分配,完成自动的垃圾回收;
- 第三是优化热点代码提升执行效率 (JIT)。
- JVM组成分为类加载子系统、运行时数据区、执行引擎、本地接口这四部分。
- 常用的JVM是Oracle提供的Hotspot虚拟机,也可以选择GraalVM、龙井(阿里)、OpenJ9等虚拟机。
2. 了解过字节码文件的组成吗?
答:
- 字节码文件包括:
- 基本信息:魔数,字节码文件对应的版本号、父类、接口等信息。
- 常量池:保存了字符串常量、类或接口名、字段名等,主要在字节码指令中被引用。
- 字段:当前类或接口声明的字段信息。
- 方法:当前类或接口声明的方法中的字节码指令。
- 属性:类的属性。
可以使用 javap -v 字节码文件.Class
命令反编译查看对应字节码的信息
3. 什么是运行时数据区
答:
运行时数据区指的是JVM所管理的内存区域,其中分成两大类:
- 线程共享
- 堆内存:创建出来的对象都存在于堆上。
静态变量
也是在堆内存(Class对象中)。 - 方法区:类的基础信息、运行时常量池(保存了字节码文件中的常量池内容)、字符串常量池
- 堆内存:创建出来的对象都存在于堆上。
- 线程不共享
- 程序计数器:记录下一条要执行的字节码指令的地址。
- Java虚拟机栈:记录执行方法的栈帧。
- 本地方法栈:记录native本地方法的栈帧。
4. 哪些区域会出现内存溢出
答:
- 堆:溢出之后会抛出
OutOfMemoryError
,并提示是Java heap Space
导致的。- 调整堆的大小:
-Xmx值
(max的最大值)-Xms值
(初始的total)
- 调整堆的大小:
- 栈:溢出之后会抛出
StackOverflowError
。- 调整虚拟机栈的大小:
-Xss栈大小
- 调整虚拟机栈的大小:
- 方法区:溢出之后会抛出
OutOfMemoryError
,JDK7及之前提示永久代,JDK8及之后提示元空间。- 调整永久代(
-XX:MaxPermSize=值
),调整元空间(-XX:MaxMetaspaceSize=值
)
- 调整永久代(
- 直接内存:溢出之后会抛出
OutOfMemoryError
。- 调整直接内存:
-XX:MaxDirectMemorySize=值
- 调整直接内存:
5. JVM在JDK6-8之间在内存区域上有什么不同
答:
- 方法区的实现
- JDK 7及之前:方法区是在堆中的永久代
- JDK 8之后:方法区是在直接内存的元空间,永久代被移除
- 字符串常量池的位置
- JDK 6及之前:字符串常量池是在方法区上
- JDK 7及之后:字符串常量池从方法区移除,放在堆中
6. 类的生命周期
答:
- 加载
- 类加载器根据类的全限定名以二进制流的方式获取字节码信息。
- 在方法区和堆上创建类的信息。
- 连接
- 验证:验证字节码文件是否符合规范
- 准备:为静态变量(static)分配内存并设置初值。
final
修饰的基本数据类型的静态变量,准备阶段直接会将代码中的值进行赋值。 - 解析:将常量池中的符号引用替换为直接引用
- 初始化
- 初始化阶段会执行静态代码块中的代码,并为静态变量赋值。注意:他们的执行顺序按编写的顺序加载。
- 初始化阶段会执行字节码文件中
clinit
部分的字节码指令。
- 卸载:同时满足以下3个条件可以被卸载
- 此类所有实例对象都已经被回收,在堆中不存在任何该类的实例对象以及子类对象。
- 加载该类的类加载器已经被回收。
- 该类对应的 java.lang.Class 对象没有在任何地方被引用。
7. 什么是类加载器?类加载器有哪几种
答:
类加载器: 负责把字节码文件读取到JVM内存中。
- 启动类加载器(Bootstrap):默认加载Java
安装目录/jre/lib
下的类文件,比如rt.jar,tools.jar,resources.jar等。 - 扩展类加载器:默认加载Java
安装目录/jre/lib/ext
下的类文件 - 应用程序类加载器:默认加载为
应用程序classpath
下的类文件。 - 自定义类加载器:继承
ClassLoader
抽象类,重写findClass
方法。在findClass
方法中,定义从哪里读取字节码文件,然后调用defineClass
方法,在方法区和堆区创建对象。
8. 什么是双亲委派机制?有什么好处
答:
- 当一个类加载器要加载字节码文件时,首先向上查找父类加载器是否加载过,
- 如果加载过,则直接返回。
- 如果一直到顶级类加载器(Bootstrap)也没有加载过,则再从上至下尝试加载。
好处:
- 保证JDK的核心类库不会被替换。
- 避免类的重复加载
9. 如何打破双亲委派机制
答:
- 实现自定义类加载器:首先继承
ClassLoader
抽象类,重写loadClass
方法,将双亲委派机制的代码去除。 - 然后编写从指定位置加载字节码,最后调用
defineClass
方法,在方法区和堆区创建对象。
10. 如何判断堆上的对象有没有被引用?
答:
可以使用引用计数法和可达性分析法来判断
- 引用计数法:每个对象都有一个引用计数器,当对象被引用时加1,取消引用时减1。为0时则说明没有被引用。缺点:存在循环引用。
- 可达性分析法:将对象分类两类,根对象和普通对象。从根对象(线程对象、静态变量、监视器对象等)出发,顺着引用链可以到达某个对象,则该对象说明被引用。
11. JVM 中都有哪些引用类型?
答:
- 强引用:默认就是强引用,即对象被局部变量、静态变量所引用。强引用的对象不会被回收掉。
- 软引用:
SoftReference类
实现。当一个对象只被软引用对象引用,并且内存空间不足时,进行垃圾回收,则会回收被软引用指向的对象。可以把软引用对象本身放到引用队列中,回收软引用对象本身。 - 弱引用:
WeakReference类
实现。不管内存空间够不够,在垃圾回收时,弱引用指向的对象都会被回收。弱引用对象本身也可以使用引用队列回收。 - 虚引用:
PhantomReference类
实现。作用:告诉直接内存,当前指向直接内存的对象不再使用,回收直接内存空间吧。 - 终结器引用:分两次垃圾回收才会把对象回收,不建议使用。
12. ThreadLocal中为什么要使用弱引用?
答:
- 在ThreadLocal内部, 存放了一个
ThreadLocalMap
对象(哈希表),ThreadLocalMap
中存放的是多个Entry
对象。 - 每个
Entry
对象继承自弱引用,指向ThreadLocal对象
。同时强引用指向ThreadLocal对应的value值。 - 如果不使用弱引用的话,假如ThreadLocal对象不再使用了,那么ThreadLocal对象不会被回收,因为被
Entry
对象强引用。
13. 有哪些常见的垃圾回收算法?
答:
1、标记清除算法
根据可达性分析算法,将所有存活的对象进行标记
在清除阶段,将未被标记的对象进行清除
缺点: 容易产生大量的内存碎片
2、复制算法
将堆内存空间划分成两部分,from区和to区
新创建的对象会被放入到from区。进行垃圾回收的时候,将from区中存活的对象复制到to区
然后将from区和to区互相换个名字
缺点: 堆内存空间利用低
3、标记整理算法
根据可达性分析算法,将所有存活的对象进行标记
整理阶段,将所有存活的对象放到堆的一端,之后清理掉这些对象的内存。
缺点: 整理的效率低
4、分代垃圾回收
将堆内存分为新生代、老年代
新生代又分为:伊甸园、幸存区from、幸存区to
新创建的对象会被放到伊甸园中。
如果伊甸园满了,则会进行Minor GC。
将伊甸园和幸存区from中的存活对象复制到幸存区to中。
清理伊甸园和幸存区from。之后幸存区from、幸存区to互换名字
每次发生MInor GC时,存活的对象年龄 + 1,当到达15时,则会被放到老年代中。
如果老年代满了,首先会触发Minor GC,如果新生代还是放不下,则会触发Full GC。
如果Full GC之后,老年代还放不下,则会爆出OOM。
14. 分代GC算法为什么将堆分成新生代和老年代?
答:
- 新生代和老年代可以使用不同的回收算法,更灵活。
- 可以通过调整新生代和老年代大小的比例,来适应不同的应用程序。
15. 有哪些常用的垃圾回收器
答:
单线程的垃圾回收器:
- Serial 回收新生代、采用复制算法
- SerialOld 回收老年代、采用标记-整理算法
- 缺点:单核CPU优异,多核CPU吞吐量不如其他垃圾回收器。
多线程的垃圾回收器:
- ParNew 回收新生代、采用复制算法
- CMS(Concurrent Mark Sweep) 回收老年代、采用标记-清除算法
- 会产生内存碎片
G1垃圾回收器
- JDK 9之后,默认的垃圾回收器
- 回收年轻代、老年代 采用复制算法
16. 内存泄漏的原因
答:
- 大量的数据被静态变量长期引用。
- 资源没有关闭