- 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 是运行 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 6及之前,方法区的实现是在堆中的永久代
- JDK 7,方法区中的字符串常量池被移到了堆中
- JDK8及之后,堆中的永久代移除了,方法区移到了直接内存的元空间
- 方法区的实现
- 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 回收老年代、采用标记-整理算法
- 缺点:吞吐量不如多线程的垃圾回收器。
多线程的垃圾回收器:
- ParNew 回收新生代、采用复制算法
- CMS(Concurrent Mark Sweep) 回收老年代、采用标记-清除算法
- 会产生内存碎片
G1垃圾回收器
- JDK 9之后,默认的垃圾回收器
- 回收年轻代、老年代 采用复制算法
16. 内存泄漏的原因
答:
- 大量的数据被静态变量长期引用。
- 资源没有关闭