0. 简介
——本文写于观看B站黑马视频JVM部分记录的笔记,权当保存记录作用。
1. 什么是JVM?——Java虚拟机,更准确地说是一套规范,只要实现了这套规范就可以是Java虚拟机,通过Java虚拟机可以实现Java的跨平台运行。
2. JVM,JRE,JDK的关系?
- JVM是Java虚拟机,Java程序都是运行在虚拟机上的;
- JRE是Java运行时环境,提供了Java应用程序执行时所需要的环境,其中包含了JDK;
- JDK是Java开发工具包,是整个Java的核心,其中包含了JRE和JVM;
3. 运行时数据区的内存结构?
-
JVM内存结构:
-
程序计数器的作用?特点?
- 线程计数器用于记录下一条要执行的字节码指令的地址,属于每个线程私有的,因为在多线程的情况下,如果发生了线程间的上下文切换,需要记录该线程执行期间最后的字节码指令执行地址,方便拿回CPU时继续执行程序;
- 如果当前正在执行的是本地方法,则程序计数器的值为undifined;
- 程序计数器在汇编中是使用寄存器来存储相应的值,而在JVM中只是使用了一块很小的内存来存储,同时保证不会出现OOM异常。
-
虚拟机栈的作用?
-
虚拟机栈用来存放栈帧信息,即每次调用方法生成的相关信息,包括:局部变量表,操作数栈,动态链接,方法出口信息,同时,每个线程只能拥有一个活动栈帧,对应着正在执行的方法:
- 局部变量表:即方法中使用到的变量,如实参,方法内部定义的临时变量等,如果变量类型是基本数据类型,存储的是数据的值;如果是类,存储的是指向堆或运行时常量池的引用,对于long和double类型的数据采用两个槽位存放;
- 操作数栈:在方法中的一切操作都基于操作数栈进行,即所有操作都要基于以下顺序:从局部变量表中取值,入栈,相关操作,出栈;
- 动态链接:每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,即全限定名。在方法调用过程中,会将这个符号引用转化为直接引用,即将其绑定到其内存地址上,因为是在运行期间的转化,称为动态链接。如果发生在编译期间则称为静态链接;
-
怎么设置虚拟机栈的大小?——可以通过设置 "-Xss"参数来指定栈的大小
java -Xss2M
-
垃圾回收是否涉及栈内存?——不涉及,方法调用结束自动弹出栈。
-
栈内存分配越大越好嘛?——不是,栈内存越大可以支持更多递归调用,但可能不需要那么多。
-
方法内的局部变量是否线程安全?——对于方法内定义且作用域只限于方法内的临时变量来讲是安全的。
-
什么情况会发生栈内存溢出?
- OOM——新建线程的时候没有足够的内存去创建虚拟机栈,就会抛出OOM异常
- StackOverFlow——请求的栈深度超过了虚拟机允许的最大深度,则会抛出StackOverFlow异常,通常出现在递归调用没有设置合理的边界条件
-
-
本地方法栈的作用?
虚拟机栈为Java方法服务,而本地方法栈为本地方法服务,本地方法通过本地方法接口调用,一般实现是使用c/c++实现的,因为Java接触不到操作系统;
-
堆的定义?
-
用来存放对象和数组的一片空间,也是垃圾回收的主要区域。通过new关键字创建的对象都会被存放在堆中,Java堆还可以细分为新生代(Eden区,Survivor From区,Survivor To区)和老年代,方便更好地进行垃圾回收;
-
怎么设置堆的大小?
java -Xms1M <!--设定堆的初始大小--> java -Xmx2M <!--设定堆运行期间最大可占用大小,即最大堆的大小-->
-
-
方法区的定义?
-
方法区也是各个线程之间共享的,用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译的热点代码等,方法区逻辑上属于堆的一部分(只是逻辑上);
-
事实上静态变量,常量,包括对应的Class类对象,都是存放在堆中,而这里存放的是类的元信息,具体见Class文件结构;其中包括:
- 全限定名
- 父类全限定名
- 访问权限/修饰符
- 常量池
- Filed域(域名,域类型,修饰符)
- 方法(方法名,返回类型,参数,修饰符,异常表)
-
方法区什么时候创建?——在虚拟机启动的时候就创建
-
运行时常量池?
- 运行时常量池是方法区的一部分,Class文件中除了有类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池表(指当前类中的常量),用于存放编译器生成的各种字面量和符号引用(全限定名),这部分内容将在类加载后存放到方法区的运行时常量池中;
- 运行时常量池可以在运行期间被动态地增加元素,最典型的例子是String.intern()方法,这也是和Class文件中的常量池表中所区别开的一点;
- 实际对象还是在堆中,具体地址与运行时常量池的全限定名一一绑定;
-
1.7和1.8的区别?
- JDK1.7之前,使用永久代来实现方法区,这样子垃圾收集器也能管理这一部分内存,但也使得Java程序更容易遇到OOM问题;
- JDK1.8彻底废弃了永久代,使用元空间实现方法区,将这一部分使用直接内存实现;
- JDK1.7之前将字符串常量池StringTable放在方法区中,在1.7之后将其放到了堆中;同时对于String.intern()方法做出了一些修改;
-
怎么使用动态的方式加载类文件?
-
-
运行时常量池?
-
什么是常量池?
- 常量池是Java类Class文件中的一项信息,记录了该类要用到的一些常量,比如:System.out等。
-
常量池和运行时常量池的区别?
- 运行时常量池用于存储类信息,常量,静态变量,即时编译器编译的热点代码等;
- 常量池用于存储本身程序用到的常量的地址或符号引用,当调用相关的方法时,由常量池找到目标常量在运行时常量池的位置,然后调用其中的相关方法或使用值。
-
字符串常量池?(考虑jdk1.7,jdk1.8,intern()方法的区别,考虑new String()创建了几个对象)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wrCxcWD8-1636885606982)(C:\Users\Lin\AppData\Roaming\Typora\typora-user-images\image-20211111084143891.png)]
-
StringTable特性?——懒创建,用到时才创建,即只有符号引用
-
intern方法在JDK不同版本下的区别?
-
StringTable的位置?为什么?——放到堆里面可以及时回收内存
-
StringTable的垃圾回收?——内存紧张时会发生垃圾回收
-
StringTable的性能调优?
——使用intern()将字符串入池,减少重复字符串
——适当地增大StringTable的大小
-XX:StringTableSize=xxxx
-
-
直接内存——分配和释放
- 属于操作系统,常见于NIO操作,如:数据缓冲区
- 分配回收成本较高,但是读写性能高,避免了在Java堆和Native堆中来回复制数据
-
为什么不允许显式地调用GC?
——显式地调用System.gc()会直接触发Full GC,同时对老年代和新生代进行垃圾回收工作,影响工作线程。
4. 垃圾回收机制
-
怎么判断一个对象可以被回收?
-
引用计数法——无法解决循环引用的问题
-
可达性分析法
- 可以作为GCRoots的对象有哪些?
- 虚拟机栈(栈帧中的局部变量表)引用的对象;
- 本地方法栈中JNI引用的对象;
- 方法区中类静态属性引用的对象;
- 方法区中常量引用的对象;
- 所有被同步锁(synchronized)持有的对象;
- 可以作为GCRoots的对象有哪些?
-
四种引用类型
- 强引用——只要有强引用就不会被回收
- 软引用——内存不足会被回收,内存充足则不会被回收
- 弱引用——每次GC不管内存是否充足,只要被扫描到就会被回收
- 虚引用——搭配引用队列使用,一旦发现虚引用被回收,配合执行相关后置方法,即释放虚引用占用空间,一般用来跟踪,典型例子是直接内存的释放;
-
finalize()方法:
- 这个方法是由垃圾收集器在确定这个对象没有被引用时对这个对象调用的,属于Object中定义的方法,意味着释放前需要进行的操作,由GC来调用;
- 即使在可达性分析中判定为不可达的对象,也不一定会死亡;
- 如果要被回收的对象重写了finalize()方法,那么将该对象放到一个F-Queue中,并在稍后由一条由虚拟机自动建立的、低调度优先级的Finalizer线程去执行它们的finalize()方法,在该方法调用过程中如果该对象重新与引用链上的对象建立关系,那么就可以摆脱被回收的命运;
- 如果finalize()方法没有被覆盖,或者finalize()方法执行完后仍旧没有引用,那么此时就必定被回收,一个没有引用的对象被回收最多经历2次GC。
-
引用队列的作用是什么???
——可用于释放引用本身占用的空间,或者实现垃圾回收的跟踪
——虚引用必须搭配引用队列一起使用
-
-
软引用的应用:
-
引用队列的应用:
-
弱引用清理为什么不是一次性清理完所有?
-
垃圾清除算法:
-
标记清除(清除的策略是什么)
——标记出相应的空间可以使用,而不把对应的数据清0,容易产生内存碎片,空间利用率低,但是实现简单;
-
标记整理
——标记之后将存活元素移动到存活区的头部,保证不产生内存碎片,因为涉及到对象位置的移动,所以一般要停止其他线程,耗时较长;
-
标记复制
——使用两块内存空间,一块用于存放当前存活对象,一块置空,在GC过程中,根据可达性分析法筛选出存活对象然后复制到另一块内存空间,从头存放,简化了标记整理,但是空间利用率只有50%;
-
分代收集
——根据对象的存活年龄划分为新生代和老年代,根据不同的区间使用不同的垃圾回收算法,新生代使用标记复制算法,基本分配比例:8:1:1;老年代使用标记清除或标记整理算法,因为老年代基本不怎么清理;
-
5. 相关的JVM调优参数:
-Xms2g <!--初始化堆大小为2g-->
-Xmx2g <!--堆最大内存为2g-->
-XX:NewRatio=4 <!--设置新生代和老年代的内存比例为1:4-->
-XX:SurvivorRatio=8 <!--设置新生代Eden和Survivor比例为8:2-->
-XX:+UseParNewGC <!--使用ParNew+Serial Old垃圾收集器-->
-XX:+UserParallelOldGC <!--使用ParNew+ParNew Old垃圾收集器-->
-XX:+UserConcMarkSweepGC <!--使用CMS+SerialOld垃圾收集器-->
-XX:+PrintGC <!--开启打印GC信息-->
-XX:+PrintGCDetails <!--打印GC详细信息-->
6. 对于大对象的垃圾回收时,是怎么处理的?或者存储时是怎么处理的?
——当遇到一个较大的对象时,即使新生代为空,也无法容纳该对象时,会直接存放到老年代。
7. 非主线程的内存溢出是否会影响主线程?
——不会,当一个线程抛出OOM异常后,它所占据的内存资源会全部被释放掉。
8. 垃圾回收器
-
串行
- Serial,收集新生代垃圾对象,使用标记复制算法,单线程串行;
- Serial Old,收集老年代垃圾对象,使用标记整理算法,单线程串行,CMS的后备方案;
-
吞吐量优先——吞吐量计算:吞吐量=用户代码运行时间/(用户代码运行时间+垃圾收集时间)
-
Parallel Scavenge,收集新生代垃圾对象,使用标记复制算法,多线程并行;
-
Parallel Scavenge收集器使用两个参数控制吞吐量:
XX:MaxGCPauseMillis <!--控制最大的垃圾收集停顿时间--> XX:GCRatio <!--直接设置吞吐量的大小-->
事实上这两个参数是互相矛盾的。
-
Parallel Scavenge收集器可设置-XX:+UseAdptiveSizePolicy参数。当开关打开时不需要手动指定新生代的大小、Eden与Survivor区的比例、晋升老年代的对象年龄等,虚拟机会根据系统的运行状况收集性能监控信息,动态设置这些参数以提供最优的停顿时间和最高的吞吐量,这种调节方式称为GC的自适应调节策略。
-
-
Parallel Old,收集老年代垃圾对象,使用标记整理算法,与Parallel Scanenge搭配使用;
-
-
响应时间优先(最短停顿时间)
- ParNew,收集新生代垃圾对象,使用标记复制算法,多线程并行,Serial收集器的多线程版本,只是相比下多了个多线程而已;
- CMS,并发标记清除,这里的并发指的是实现了垃圾收集线程和用户线程同时进行,一般搭配ParNew使用,同时将Seroal Old作为后备方案,分为四个过程:(只清理老年代)
- 初始标记:暂停所有线程,记录GC Roots对象,速度很快;
- 并发标记:对堆中对象做可达性分析,找出存活对象,耗时较长但不停顿用户线程;
- 重新标记:暂停所有线程,因为并发标记期间用户线程的运行可能导致引用关系发生变化,所以需要进行重新标记;
- 并发清除:因为使用标记清除算法所以不需要移动存活对象,可以和用户线程并行执行。
- 优点:低停顿,并发收集;
- 缺点:空间碎片,可能会触发FullGC;浮动垃圾(并发清理阶段产生),需要预留部分空间;
-
JDK1.8的默认回收器——Parallel Scavenge+Parallel Old;
-
新生代引用老年代的情况下,怎么处理
-
CMS并发失败的原因及处理
- 并发失效——新生代对象晋升到老年代空间时发现空间不够,于是触发FullGC,用户线程暂停,同时使用SerialGC进行垃圾回收;
- 晋升失败——新生代对象晋升到老年代空间时判断空间足够,然后才发现碎片化空间无法完整晋升,整理老年代内存空间,因为移动老年代对象,所以时间损耗比并发失效的情况更加严重;
9. G1回收器——Garbage First,JDK9以后使用,代替了CMS
-
——同时注重吞吐量和低延迟;
——将堆内存划分为多个大小相等的区域,不特别划分新生区,老年代;
——整体上是标记整理算法,两个区域之间是复制算法;
——G1将整个堆分成相同大小的分区(Region),有四种不同的类型分区:Eden,Survivor,Old和Humongous(大对象),分区的大小取值范围为1M到32M,都是2的幂次方。Humongous用于存储大对象,G1认为只要大小超过了一个Region容量一般的对象即可判定为大对象;
-
垃圾回收有几个阶段?
-
Young Collection
——新生代垃圾回收,年轻代空间被逐渐填满后,即当JVM分配对象到Eden区失败时,便会触发一次STW式的年轻代收集。在此过程中,Eden区对象将被复制到Survivor区,Survivor区对象根据年龄以及引用情况判断是否继续存活,复制到Survivor区还是老年区,最后原有的Eden区将被全部回收; -
YoungCollection + Concurrent Mark
——当达到IHOP阈值(老年代占整堆比,默认45%),便会触发并发标记周期(事实上在这个过程中,并发标记是主要工作而Young Collection是偶尔可能会发生的)。这个过程可以细分为:
-
初始标记——仅仅只是标记一下GC Roots能关联到的对象并压入扫描栈中,伴随着Young GC过程中发生,期间会STW;
-
根分区扫描——因为前面发生了一次YoungGC,所以此时的Region里面只有Survivor区和老年区有存活对象。根分区扫描是从Survivor区的对象出发,标记被引用到老年代的对象并压入扫描栈中,该阶段必须在下一次YoungGC前结束,期间并不STW;
-
并发标记——不需要STW,不断从扫描栈中取出引用递归扫描整个堆里的对象,每扫描到一个就会标记并压入扫描栈,直到栈清空;
-
重新标记——触发STW,完成最后的存活对象标记。在完成并发标记后,每个Java线程还会有一些剩下的SATB write barrier记录的引用尚未处理。这个阶段就负责把剩下的引用处理完。
-
清除——负责更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧 Region的全部空间。
-
-
Mixed Collection
——并发周期结束后是混合垃圾回收周期,不仅会进行年轻代的垃圾收集也会进行老年代垃圾回收。此过程回收足够数量的老年代区域后,G1将恢复到执行年轻代回收,重复这个过程;
-
-
FullGC的定义?
- Minor GC,也称为Young GC,清理新生区对象;
- Major GC,也称为Old GC,清理老年区对象;
- Full GC,一次特殊GC行为的描述,这次GC会回收整个堆的内存,包含老年代,新生代,metaspace等。
- 触发Full GC的原因——基本上是老年代空间不足所导致的,不管是晋升失败,还是并发失败,解决方案可以为增大堆的大小,或者提前进行垃圾回收;
-
跨代引用会造成的问题?怎么解决?
-
什么是跨代引用——在年轻代中的对象引用了在老年代的对象,反过来也成立;
-
如果出现跨代引用的情况,在做可达性分析的时候就会比较麻烦,比如在YoungGC的过程中并不需要遍历老年代的对象,但是因为跨代引用的存在,使得YoungGC本身需要遍历整个堆,时间消耗极多;
-
CMS中使用了Card Table的结构,记录了老年代对象到新生代引用,只扫描Card Table即可;G1中使用了RSet,记录的是谁引用了本Region中的内容,为每个Region各自私有。
-
-
重新标记的原因?怎么处理?
——STAB算法
-
什么情况下可以卸载一个类?
——关于类加载器,类,对象之间的关系是:
Java虚拟机本身会始终引用这些类加载器,而这些类加载器则会始终引用它们所加载的类的Class对象。
——因此需要同时满足以下的条件,类才可能被卸载:
- 该类的所有实例已经被回收;
- 加载该类的类加载器已经被回收;
- 该类对应的Class对象没有在任何地方被引用,无法通过反射访问该类的任何内容;
10. 垃圾回收调优:
-
新生代Eden调优
——适当设置内存大小
- 内存太小,频繁触发Minor GC,吞吐量下降;
- 内存太大,老年代空间不足,触发Full GC,损失更严重;
-
新生代幸存区调优——适当设置晋升阈值
-
老年代调优
11. 调优的方向考虑:
- 内存
- 锁竞争
- CPU占用
- IO
12. 类文件结构:
ClassFile {
u4 magic; //Class 文件的标志
u2 minor_version; //Class 的小版本号
u2 major_version; //Class 的大版本号
u2 constant_pool_count; //常量池的数量
cp_info constant_pool[constant_pool_count-1]; //常量池
u2 access_flags; //Class的访问标记
u2 this_class; //当前类
u2 super_class; //父类
u2 interfaces_count; //接口
u2 interfaces[interfaces_count]; //一个类可以实现多个接口
u2 fields_count; //Class文件的字段属性
field_info fields[fields_count]; //一个类会可以有个字段
u2 methods_count; //Class文件的方法数量
method_info methods[methods_count]; //一个类可以有个多个方法
u2 attributes_count; //此类的属性表中的属性数
attribute_info attributes[attributes_count]; //属性表集合,描述某些场景专有的信息
}
-
小版本号和大版本号的区别
- 小版本号基本上不起作用
- 大版本号也称为主版本号,高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件,这里Class文件的版本就是以大版本号为基准的
-
常量池和运行时常量池的区别
- 常量池:该类所用到的常量,如System.out;
- 运行时常量池:Java应用中所有类的常量池的总和;
-
字段和属性表的区别
- 字段是类中的某个属性;
- 属性表是用来描述类中方法,字段,内部类,或者类本身的信息;
-
short类型的数字一般不存储在常量池中——使用槽位放置short类型的数据,如果是超过了short的采访在常量池里
-
操作数栈中存放的内容——基本数据类型放值,类放引用地址
-
是否可以直接操作局部变量表中的数据?——不可以,需要通过操作数栈来对相应的数据进行操作
-
操作数栈的大小?——根据编写的代码的方法调用情况由编译器决定
-
long,float和double的处理方式?——占用两个槽位
-
while循环和for循环虽然在语法表达上不一样但是编译成字节码指令,最终还是一样的
-
解析:
public class Test{ public static void main(String[] args){ int i=0; int x=0; while(i<10){ x=x++; i++; } System.out.println(x); //结果是0 } }
-
使用对象名调用静态方法和使用类名调用静态方法有什么区别嘛?
——翻译成字节码指令后,使用类调用静态方法是直接调用;
——如果是使用对象来调用静态方法的话,那么操作数栈会先将操作对象进行入栈,发现调用的是静态方法,于是再出栈然后调用静态方法,比直接调用多了两个指令,不建议使用。
13. 多态的原理
- JVM的方法调用指令有:
- invokestatic:调用静态方法
- invokespecial:调用实例构造器<init>方法,私有方法和父类方法
- invokevirtual:调用虚方法
- invokeinterface:调用接口方法,运行时确定具体实现
- invokedynamic:运行时动态解析所引用的方法,然后再执行,用于支持动态类型语言
- 虚方法
——非静态,非构造,非私有,非接口,非抽象的可被覆盖的方法
- invokestatic和invokespecial用于静态绑定,invokevirtual和invokeinterface用于动态绑定。动态绑定主要应用于虚方法和接口方法;静态绑定在编译期就已经确定,这是因为静态方法、构造器方法、私有方法和父类方法可以唯一确定。这些方法的符号引用在类加载的解析阶段就会解析成直接引用。因此这些方法也被称为非虚方法,与之相对的便是虚方法。
- 虚方法的方法调用与方法实现的关联(也就是分派)有两种,一种是在编译期确定,被称为静态分派,比如方法的重载;一种是在运行时确定,被称为动态分派,比如方法的覆盖。对象方法基本上都是虚方法。
- 这里需要特别说明的是,final 方法由于不能被覆盖,可以唯一确定,因此 Java 语言规范规定 final 方法属于非虚方法,但仍然使用 invokevirtual 指令调用。静态绑定、动态绑定的概念和虚方法、非虚方法的概念是两个不同的概念。
- 多态的实现步骤:
- 先从操作栈中找到对象的实际类型 class;
- 找到 class 中与被调用方法签名相同的方法,如果有访问权限就返回这个方法的直接引用,如果没有访问权限就报错 java.lang.IllegalAccessError ;
- 如果第 2 步找不到相符的方法,就去搜索 class 的父类,按照继承关系自下而上依次执行第 2 步的操作;
- 如果第 3 步找不到相符的方法,就报错 java.lang.AbstractMethodError ;
14. try-catch的实现原理
——使用一个ExceptionTable监视目标代码快,如果发生了预期内的异常,则跳转至相应的字节码指令行;
——多个try-catch只是往ExceptionTable中多添加几项记录;
15. 怎么保证finally块的代码一定执行
——finally中的代码会被复制三份然后分别在try,catch,catch other的区域中添加,以此保证finally代码的执行;
16. 如果在finally块中写了return 字段,那么在catch other缺少 athrow 指令中,即,把Exception吞掉,无法捕获,因此不建议在finally代码块中使用return关键字;
17. finally代码块对变量的修改是否影响?
- 基本类型
- 对象
18. synchronized关键字——在synchronized中,会自动添加Exception检测,无论何时出现异常都会保证解锁操作可以正常进行;
19. 把*.java源码编译为*.class字节码的过程中,对程序员写的原生代码做优化的处理,也称为语法糖。
- 无参构造——默认调用父类的super()方法
- 自动拆装箱——针对基本数据类型做拆装箱处理
- 泛型集合取值,泛型擦除——集合类中存放的都是Object,取出的时候多做了一步类型转换
- 可变参数的处理——使用数组接收
- foreach的处理
- 自动转换为for循环遍历数组
- 针对于集合类,自动使用迭代器完成遍历
- switch字符串——转化为HashCode,同时做equals判断
- switch枚举——比较枚举对应的int类型
- 枚举类
- try-with-resources——实现自动关闭,但资源必须实现相应的接口
- 重写桥接——重写返回值可以是父类返回值的实现子类
- 匿名内部类——创建了一个额外的类(使用外部变量为什么要定义final的原因)
20. 类加载——指读入class文件,将类的字节码读入方法区,依次在堆中生成class对象,同时执行class的静态代码块和静态变量赋值的过程。(如果没有加载父类要先加载父类)
-
Class与Klass之间的联系——互相映射
-
加载——将Klass对象放到方法区,将class对象放到堆,互相之间知道互相的地址
-
连接
- 验证——验证类(Class字节码文件)是否符合JVM规范,安全性检查
- 准备——为static变量分配空间,设置默认值,这里的默认值是0
- 解析——将常量池中的符号引用解析为直接引用
-
初始化——执行类的<client>构造方法,主要是执行静态代码块以及静态变量的赋值
21. 类加载器:
-
启动类加载器——加载lib目录
-
扩展类加载器——加载ext目录
-
应用程序加载器——加载自定义类
-
自定义加载器——上级是应用程序加载器,需要重写findClass方法
-
双亲委派机制——类的加载需要交给自己的上级,如果上级没办法加载再交由该加载器加载
-
线程上下文加载器:
——线程上下文类加载器(context class loader)是从 JDK 1.2 开始引入的。类 java.lang.Thread中的方法 getContextClassLoader()和 setContextClassLoader(ClassLoader cl)用来获取和设置线程的上下文类加载器。
——如果没有通过 setContextClassLoader(ClassLoader cl)方法进行设置的话,线程将继承其父线程的上下文类加载器。
——使用线程上下文类加载器,可以在执行线程中抛弃双亲委派加载链模式,使用线程上下文里的类加载器加载类。
-
DriverManager和普通类加载是否一样?有什么区别?
——因为DriverManager在rt.jar里面,它的类加载器上启动类加载器。而数据库的driver(com.mysql.jdbc.Driver)是放在classpath里面的,启动类加载器是不能加载的。所以,如果严格按照双亲委派模型,是没办法解决的。而这里的解决办法是:通过调用类的类加载器去加载。而如果调用类的加载器是null,就设置为线程的上下文类加载器。
22. 运行期优化:
- JIT和解释器的区别
- 解释器——将字节码解释为机器码;
- JIT——将一些字节码编译为机器码并存入Code Cache,下次遇到直接执行;
- 逃逸分析
- 方法内联——减少栈帧
- 字段优化
- for循环访问本地变量和局部变量的区别
- 禁用内联和不禁用的区别
- 反射优化
23. JMM——JMM定义了一套在多线程读写共享数据时,对数据的可见性,有序性和原子性的规则和保障;
- moniter,Owner,EntryList,WaitSet
- synchronized和Lock的加锁粒度(for循环写在锁外和锁内的区别)
- volatile保证可见性但不保证原子性
- synchronized也可以实现可见性和原子性,但是synchronized是重量级锁
- 指令重排的经典问题:单例模式
24. CAS:
- 自旋的逻辑
- 与synchronized的比较
25. synchronized优化
- 对象头的内容
- 轻量级锁膨胀成为重量级锁
- 重量级锁自旋重试
- 偏向锁——可重入的锁操作浪费
- 其他优化:
- 减少上锁时间
- 降低锁的粒度
- 锁粗化(方法内联和for循环优化)
- 锁消除——逃逸分析
- 读写分离