1.入门部分
1.1为什么要学习JVM?
- 可以帮助我们从平台角度提高解决问题的能力
有效防止内存泄漏(Memory leak)。
优化线程锁的使用 (Thread Lock)。
科学进行垃圾回收 (Garbage collection)。
提高系统吞吐量 (throughput)。
降低延迟(Delay),提高其性能(performance)。
优化线程锁的使用 (Thread Lock)。
科学进行垃圾回收 (Garbage collection)。
提高系统吞吐量 (throughput)。
降低延迟(Delay),提高其性能(performance)。
提高系统吞吐量 (throughput)。
降低延迟(Delay),提高其性能(performance)。
2.企业面试需要,可以提高自身竞争力
1.2.你了解哪些JVM产品?HotSpot VM (Sun公司研发,后在2010年由Oracle公司收购)。
JRockit VM (BEA公司研发,后在2008年由Oracle公司收购)。
J9 VM (IBM 内部使用)。
TaobaoJVM(AliJVM团队开发,基于OpenJDK开发了AlibabaJDK)
5)…
说明:HotSpot目前是应用最官方,最主要的一款JVM虚拟机。
1.3.JVM的构成有哪几部分?类加载系统 (ClassLoader System) :负责加载类到内存。
运行时数据区 (Runtime Data Area):负责存储数据信息(对象、方法等)。
执行引擎 (Execution Engine):负责解释执行字节码、执行GC操作等。
本地库接口(Native Interface) 负责融合不同的编程语言为 Java 所用。
2.类加载部分
2.1你知道哪些类加载器?BootstrapClassLoader(根加载器)
ExlClassLoader(扩展类加载器)
AppClassLoader(应用类加载器,自定义的类一般使用此类加载器)
2.2什么是双亲委派类加载模型?如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行;
如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器;
如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式。
父类加载器一层一层往下分配任务,如果子类加载器能加载,则加载此类,如果将加载任务分配至系统类加载器也无法加载此类,则抛出异常。
2.3双亲委派方式加载类有什么优势、劣势?优势:
避免类的重复加载, 确保一个类的全局唯一性
保护程序安全, 防止核心 API 被随意篡改
劣势:
检查类是否加载的委派过程是单向的, 这个方式虽然从结构上说比较清晰,
使各个 ClassLoader 的职责非常明确, 但是同时会带来一个问题, 即顶层的
ClassLoader 无法访问底层的 ClassLoader 所加载的类
1.通过一个类的全限定名(类全名)来获取其定义的二进制字节流。
2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
3.在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口,如图所示。
2.5什么情况下会触发类的加载?1.调用静态成员时
2.创建子类实例时,父类会被先加载
3.创建对象的实例,即New对象时
4.反射
2.6类加载时静态代码块一定会执行吗?不一定,类只执行加载连接,不初始化时,静态块不会被执行,因为静态块是在初始化阶段执行
2.7如何理解类的主动加载和被动加载?1.主动使用:会执行加载、连接、初始化静态域
2.被动使用:只执行加载、连接,不初始化类静态域
2.8为什么要自己定义类加载器,如何定义?原因:
1.修改类的加载方式(打破类的双亲委派模型)
2. 扩展加载源(例如从数据库中加载类)
3. 防止源码泄漏(对字节码文件进行加密,用时再通过自定义类加载器对其进行解密)
4.隔离类的加载(不同框架有相同全限定名的类)
定义方法:
1.继承URLClassLoader
2.重写findClass方法
3.字节码增强部分
3.1为何要学习字节码?
对于开发人员,了解字节码可以更准确、直观地理解Java语言中更深层次的东西,比如通过字节码,可以很直观地看到Volatile关键字如何在字节码上生效。另外,字节码增强技术在Spring AOP、各种ORM框架、热部署中的应用屡见不鲜,深入理解其原理对于我们来说大有裨益。
3.2如何解读字节码内容?
可以通过notepad++(需要安装一下HEX-Editor插件)打开IntTests.class文件,文件内容默认是一种16进制的格式
在IntTests.class目录使用如下代码对类进行反编译,例如:
javap -verbose IntTests.class //可以使用javap –help查看帮助
3.3字节码内容由哪几部分构成?
magic(魔数)
minor_version(次版本号)
major_version(主版本号)
constant_pool_count(常量池计数器)
constant_pool[constant_pool_count-1](常量池)
access_flags(类的访问标志)
this_class(当前类名索引值)
super_class(父类名索引值)
interfaces_count(接口计数)
interfaces[interfaces_count](接口数组)
fields_count(成员变量计数)
fields[fields_count](成员变量数组)
methods_count(方法计数)
methods[methods_count](方法数组)
attributes_count(属性计数)
attributes[attributes_count](属性数组)
3.4什么是字节码增强?
字节码增强技术相当于是一把打开运行时JVM的钥匙,利用它可以对现有字节码进行修改或者动态生成新的字节码,进而对运行中的程序做修改,实现热部署。也可以跟踪JVM运行中程序的状态,进行性能诊断等。
3.5为什么要进行字节码增强?
掌握字节码增强后可以高效地定位并快速修复一些棘手的问题(如线上性能问题、方法出现不可控的出入参需要紧急加日志等问题),也可以在开发中减少冗余代码,大大提高开发效率。
3.6你了解哪些字节码增强技术?
ASM技术
Javassist技术
Java Agent技术
3.7什么是热替换以及如何实现?
在一个持续运行,并已经加载了所有类的JVM中,还能利用字节码增强技术,对其中的类行为做替换并重新加载,这个过程我们有时通常称之为热替换。
实现:
创建自定义的ClassLoader实例
加载需要热替换的类
创建新的ClassLoader实例,创建类的对象
运行新对象的方法
4.JVM运行内存部分
4.1JVM运行内存是如何划分的?
1.程序计数器
2.Java虚拟机栈
3.堆内存
4.方法区
4.2JVM中的程序计数器用于做什么?
程序计数器(Program Counter Register)也称之为PC寄存器,是一块较小的内存空间,用来存储指向下一条指令的地址,也可以看作是当前线程执行的字节码的行号指示器。
4.3JVM虚拟机栈的结构是怎样的?
栈中的数据都是以栈帧形式存在,包含
局部变量表
操作数栈
本动态链接
方法返回地址
4.4JVM虚拟机栈中局部变量表的作用是什么?
用于存放方法参数和方法内部定义的局部变量信息
4.5JVM虚拟机栈中操作数栈的做用时什么?
操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)和 出栈(pop)
4.6JVM堆的构成是怎样的?
JAVA堆内存在JVM中可分为年轻代和老年代。年轻代又分为Eden和两个Survivor区
4.7Java对象分配内存的过程是怎样的?
1.编译器通过逃逸分析(JDK8已默认开启),确定对象是在栈上分配还是在堆上分配。
2.如果是在堆上分配,则首先检测是否可在TLAB(Thread Local Allocation Buffer)上直接分配。
3.如果TLAB上无法直接分配则在Eden加锁区进行分配(线程共享区)。
4.如果Eden区无法存储对象,则执行Yong GC(Minor Collection)。
5.如果Yong GC之后Eden区仍然不足以存储对象,则直接分配在老年代。
4.8JVM年轻代幸存区设置的比较小会有什么问题?
伊甸园区被回收时,对象要拷贝到幸存区,假如幸存区比较小,拷贝的对象比较大,对象就会直接存储到老年代,这样会增加老年代GC的频率。而分代回收的思想就会被弱化。
4.9JVM年轻代伊甸园区设置的比例比较小会有什么问题?
伊甸园设置的比较小,会增加GC的频率,可能会导致STW的时间边长,影响系统性能。
4.10JVM堆内存为什么要分成年轻代和老年代?
为了更好的实现垃圾回收。
4.11如何理解JVM方法区以及它的构成是怎样的?
方法区(Methed Area)是一种规范,用于存储已被虚拟机加载的类信息、常 量、静态变量、即时编译后的代码等数据。
构成:
1.类信息包括对每个加载的类型(类class、接口interface、枚举enum、注解annotation)以及属性和方法信息。
2.常量信息可以看做是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型。
4.12什么是逃逸分析以及可以解决什么问题?
逃逸分析一种数据分析算法,基于此算法可以有效减少Java对象在堆内存中的分配。Hotspot虚拟机的编译器能够分析出一个新对象的引用范围,然后决定是否要将这个对象分配到堆上。例如:
1)当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。
2)当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸。
解决问题:
1)栈上分配:将堆分配转化为栈分配。如果一个对象在方法内创建,要使指向该对象的引用不会发生逃逸,对象可能是栈上分配的候选。
2)同步省略:如果一个对象被发现只有一个线程被访问到,那么对于这个对象的操作可以不考虑同步。
3)分离对象或标量替换:有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。
4.13何为内存溢出以及导致内存溢出的原因?
内存中剩余的内存不足以分配给新的内存请求就会内存溢出。内存溢出可能直接导致系统崩溃。
内存泄漏是导致内存溢出的一种原因,但内存溢出不全是由内存泄漏引起的,还可能是:
1)创建的对象太大导致堆内存溢出
2)创建的对象太多导致堆内存溢出
3)方法出现了无限递归调用导致栈内存溢出
4)方法区内存空间不足导致内存溢出。
4.14何为内存泄漏以及内存泄漏的原因是什么?
动态分配的内存空间,在使用完毕后未得到释放,结果导致一直占据该内存单元,直到程序结束。这个现象称之为内存泄漏。因此良好的代码规范,可以有效地避免这些错误。
导致内存泄漏的原因
1)大量使用静态变量(静态变量与程序生命周期一样)
2)IO/连接资源用完没关闭(记得执行close操作)
3)内部类的使用方式存在问题(实力内部类或默认引用外部类对象)
4)缓存(Cache)应用不当(尽量不要使用强引用)
5)ThreadLocal应用不当(用完记得执行remove操作)
4.15JAVA中的四大引用你知道多少?
强引用:不会被回收
软引用:内存不够了,被GC
弱引用:GC发生时
虚引用:任何时候
5.JVM垃圾回收部分
5.1何为GC以及为和要GC?
GC(Garbage Collection)称之为垃圾回收,是对内存中的垃圾对象,采用一定的算法进行内存回收的一个动作。
深入理解GC的工作机制,可以帮你写出更好的Java应用,提高开发效率,同时也是进军大规模应用开发的一个前提。
5.2你知道哪些GC算法?
引用计数法
可达性分析法
标记-清除法
标记-复制法
标记-整理法
5.3JVM中有哪些垃圾回收器?
年轻代和老年代的串行收集器: Serial GC)
年轻代和老年代的并行收集器:(Parallel GC)
年轻代的并行收集器 (Parallel New) + 老年代的并发收集器(CMS-Concurrent Mark and Sweep)。
年轻代和老年代的G1收集器, 负责回收年轻代和老年代。
5.4服务频繁fullgc,younggc次数较少,可能的原因?
1:系统承载高并发请求,或者处理数据量过大,导致Young GC很贫乏,而且每次Young GC过后存活对象太多,内存分配不合理,Survivor区过小,导致对象频繁进入老年代,频繁触发Full GC。
2:系统一次性加载过多数据进内存,搞出来很多大对象,导致频繁有大对象进入老年带,必然频繁触发Full GC。
3:系统发生了内存泄漏,莫名其妙创建大量的对象,始终无法回收,一直占用在老年代里,必然频繁触发Full GC。
4:Metaspace(永久代)因为加载类过多触发Full GC。
5:误调用System.gc()触发Full GC。
对于开发人员,了解字节码可以更准确、直观地理解Java语言中更深层次的东西,比如通过字节码,可以很直观地看到Volatile关键字如何在字节码上生效。另外,字节码增强技术在Spring AOP、各种ORM框架、热部署中的应用屡见不鲜,深入理解其原理对于我们来说大有裨益。
3.2如何解读字节码内容?可以通过notepad++(需要安装一下HEX-Editor插件)打开IntTests.class文件,文件内容默认是一种16进制的格式
在IntTests.class目录使用如下代码对类进行反编译,例如:
javap -verbose IntTests.class //可以使用javap –help查看帮助
3.3字节码内容由哪几部分构成?magic(魔数)
minor_version(次版本号)
major_version(主版本号)
constant_pool_count(常量池计数器)
constant_pool[constant_pool_count-1](常量池)
access_flags(类的访问标志)
this_class(当前类名索引值)
super_class(父类名索引值)
interfaces_count(接口计数)
interfaces[interfaces_count](接口数组)
fields_count(成员变量计数)
fields[fields_count](成员变量数组)
methods_count(方法计数)
methods[methods_count](方法数组)
attributes_count(属性计数)
attributes[attributes_count](属性数组)
3.4什么是字节码增强?字节码增强技术相当于是一把打开运行时JVM的钥匙,利用它可以对现有字节码进行修改或者动态生成新的字节码,进而对运行中的程序做修改,实现热部署。也可以跟踪JVM运行中程序的状态,进行性能诊断等。
3.5为什么要进行字节码增强?掌握字节码增强后可以高效地定位并快速修复一些棘手的问题(如线上性能问题、方法出现不可控的出入参需要紧急加日志等问题),也可以在开发中减少冗余代码,大大提高开发效率。
3.6你了解哪些字节码增强技术?ASM技术
Javassist技术
Java Agent技术
3.7什么是热替换以及如何实现?在一个持续运行,并已经加载了所有类的JVM中,还能利用字节码增强技术,对其中的类行为做替换并重新加载,这个过程我们有时通常称之为热替换。
实现:
创建自定义的ClassLoader实例
加载需要热替换的类
创建新的ClassLoader实例,创建类的对象
运行新对象的方法
4.JVM运行内存部分 4.1JVM运行内存是如何划分的?
1.程序计数器
2.Java虚拟机栈
3.堆内存
4.方法区
4.2JVM中的程序计数器用于做什么?程序计数器(Program Counter Register)也称之为PC寄存器,是一块较小的内存空间,用来存储指向下一条指令的地址,也可以看作是当前线程执行的字节码的行号指示器。
4.3JVM虚拟机栈的结构是怎样的?栈中的数据都是以栈帧形式存在,包含
局部变量表
操作数栈
本动态链接
方法返回地址
4.4JVM虚拟机栈中局部变量表的作用是什么?用于存放方法参数和方法内部定义的局部变量信息
4.5JVM虚拟机栈中操作数栈的做用时什么?操作数栈,在方法执行过程中,根据字节码指令,往栈中写入数据或提取数据,即入栈(push)和 出栈(pop)
4.6JVM堆的构成是怎样的?JAVA堆内存在JVM中可分为年轻代和老年代。年轻代又分为Eden和两个Survivor区
4.7Java对象分配内存的过程是怎样的?1.编译器通过逃逸分析(JDK8已默认开启),确定对象是在栈上分配还是在堆上分配。
2.如果是在堆上分配,则首先检测是否可在TLAB(Thread Local Allocation Buffer)上直接分配。
3.如果TLAB上无法直接分配则在Eden加锁区进行分配(线程共享区)。
4.如果Eden区无法存储对象,则执行Yong GC(Minor Collection)。
5.如果Yong GC之后Eden区仍然不足以存储对象,则直接分配在老年代。
4.8JVM年轻代幸存区设置的比较小会有什么问题?伊甸园区被回收时,对象要拷贝到幸存区,假如幸存区比较小,拷贝的对象比较大,对象就会直接存储到老年代,这样会增加老年代GC的频率。而分代回收的思想就会被弱化。
4.9JVM年轻代伊甸园区设置的比例比较小会有什么问题?伊甸园设置的比较小,会增加GC的频率,可能会导致STW的时间边长,影响系统性能。
4.10JVM堆内存为什么要分成年轻代和老年代?为了更好的实现垃圾回收。
4.11如何理解JVM方法区以及它的构成是怎样的?方法区(Methed Area)是一种规范,用于存储已被虚拟机加载的类信息、常 量、静态变量、即时编译后的代码等数据。
构成:
1.类信息包括对每个加载的类型(类class、接口interface、枚举enum、注解annotation)以及属性和方法信息。
2.常量信息可以看做是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型。
逃逸分析一种数据分析算法,基于此算法可以有效减少Java对象在堆内存中的分配。Hotspot虚拟机的编译器能够分析出一个新对象的引用范围,然后决定是否要将这个对象分配到堆上。例如:
1)当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸。
2)当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸。
解决问题:
1)栈上分配:将堆分配转化为栈分配。如果一个对象在方法内创建,要使指向该对象的引用不会发生逃逸,对象可能是栈上分配的候选。
2)同步省略:如果一个对象被发现只有一个线程被访问到,那么对于这个对象的操作可以不考虑同步。
3)分离对象或标量替换:有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。
4.13何为内存溢出以及导致内存溢出的原因?内存中剩余的内存不足以分配给新的内存请求就会内存溢出。内存溢出可能直接导致系统崩溃。
内存泄漏是导致内存溢出的一种原因,但内存溢出不全是由内存泄漏引起的,还可能是:
1)创建的对象太大导致堆内存溢出
2)创建的对象太多导致堆内存溢出
3)方法出现了无限递归调用导致栈内存溢出
4)方法区内存空间不足导致内存溢出。
动态分配的内存空间,在使用完毕后未得到释放,结果导致一直占据该内存单元,直到程序结束。这个现象称之为内存泄漏。因此良好的代码规范,可以有效地避免这些错误。
导致内存泄漏的原因
1)大量使用静态变量(静态变量与程序生命周期一样)
2)IO/连接资源用完没关闭(记得执行close操作)
3)内部类的使用方式存在问题(实力内部类或默认引用外部类对象)
4)缓存(Cache)应用不当(尽量不要使用强引用)
5)ThreadLocal应用不当(用完记得执行remove操作)
4.15JAVA中的四大引用你知道多少?强引用:不会被回收
软引用:内存不够了,被GC
弱引用:GC发生时
虚引用:任何时候
5.JVM垃圾回收部分 5.1何为GC以及为和要GC?
GC(Garbage Collection)称之为垃圾回收,是对内存中的垃圾对象,采用一定的算法进行内存回收的一个动作。
深入理解GC的工作机制,可以帮你写出更好的Java应用,提高开发效率,同时也是进军大规模应用开发的一个前提。
5.2你知道哪些GC算法?引用计数法
可达性分析法
标记-清除法
标记-复制法
标记-整理法
5.3JVM中有哪些垃圾回收器?年轻代和老年代的串行收集器: Serial GC)
年轻代和老年代的并行收集器:(Parallel GC)
年轻代的并行收集器 (Parallel New) + 老年代的并发收集器(CMS-Concurrent Mark and Sweep)。
年轻代和老年代的G1收集器, 负责回收年轻代和老年代。
5.4服务频繁fullgc,younggc次数较少,可能的原因?1:系统承载高并发请求,或者处理数据量过大,导致Young GC很贫乏,而且每次Young GC过后存活对象太多,内存分配不合理,Survivor区过小,导致对象频繁进入老年代,频繁触发Full GC。
2:系统一次性加载过多数据进内存,搞出来很多大对象,导致频繁有大对象进入老年带,必然频繁触发Full GC。
3:系统发生了内存泄漏,莫名其妙创建大量的对象,始终无法回收,一直占用在老年代里,必然频繁触发Full GC。
4:Metaspace(永久代)因为加载类过多触发Full GC。
5:误调用System.gc()触发Full GC。



