前言:
在面试中,JDK和JRE的区别,字节码的优势,GC垃圾回收器,这些都是会被经常问到的问题,那么你对这些问题有多少了解呢,能答出来多少呢,如果不能快速答出,或者没有明确的答案,那么在看完本篇博客后,相信你会有所收获!
1. JDK、JRE和JVM三者区别和联系
1.1 JDK、JRE和JVM三者区别
1.1.1 JDK是什么?
JDK:全称是Java Development Kit,指的是Java的开发工具
1.1.2 JRE是什么?
JRE:全称是Java Runtime Environment,也就是Java运行时环境
1.1.3 JVM是什么?
JVM:全称是Java Virtual Machine,是指Java虚拟机
1.2 JDK、JRE和JVM三者联系
体现Java的一大特性:支持跨平台,即一次编译,到处运行
2. 什么字节码?采用字节码的好处是什么?
2.1 Java中的编译器和解释器
- Java中引用了虚拟机(JVM),即机器与编译程序之间加入了一层抽象的虚拟的机器,这台虚拟的机器在任何平台上都提供给编译程序一个共同的接口
无论是Windows系统还是Linux系统上,
比如有一个App程序,把程序编译成一个Jar包或者War包
这是由于JVM在中间进行了处理,
向任何平台都提供了一个程序编译的共同接口,
因此,在Windows上的程序也能在Linux上运行
-
编译程序只需要面向Java虚拟机,生成虚拟机能够理解的代码,然后由虚拟机代码转换成特定的机器码执行
-
在Java中,这种共虚拟机理解的代码就叫做字节码 (即拓展名为.class的文件),它不面向任何特定的处理器,只面向虚拟机
-
每一种平台的解释器是不同的,但是实现的虚拟机是相同的
Java源程序经过编译器编译后变成字节码;
字节码由虚拟机解释执行,虚拟机将每一条要执行的字节码送给解释器;
解释器将其翻译成特定机器上的机器码,然后在特定的机器上运行。
这也就是解释了Java的编译与解释并存的特点
Java代码的执行过程:
Java源代码 —> 编译器 ----> JVM可执行的Java字节码(即虚拟指令) —> JVM —> JVM中解释器 —> 机器可执行的二进制机器码 —> 程序运行
2.2 采用字节码的好处是什么?
- Java语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率的问题
传统的解释性语言是编译一句,解释一句
而Java是将编译和解释拆分开:编译是提前做的,在IDE中编写完后就自动编译成.class文件
传统的编译性语言是编译一句,解释一句,解释也会也占用运行时间
而而Java是将编译和解释拆分开,因此执行效率就会相对较高
- 同时又保留了解释性语言的可移植的特点
- 所以Java程序运行时比较高效,而且由于字节码并不专门针对一种特定的机器
- 因此,Java程序无需重新编译便可在不同计算机上运行
3. GC如何判断对象可以被回收?
3.1 引用计数法
1.什么是引用计数法
引用计数法:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时减1,计数为0时可以回收 (但是Java的GC并没有采用引用计数法来判断对象是否可以回收,并且GC并不是Java所独有的,像python和Go语言的GC采用的就是这种方法)
2.为什么Java中GC没有采用计数引用法?
因为引用计数法存在一个很大的缺陷,例如可能会出现A引用了B,B又引用了A,这时候就算它们都不再使用了,但因为相互所以其引用计数器=1,永远永远无法回收
3.为什么Python还采用引用计数法呢?
虽然说引用计数法回存在这种永远无法被回收的缺陷,但其执行效率还是很高的,比如新增一个引用就加1,引用释放就减1,不会消耗太多的性能
3.2 可达性分析
1.什么是可达性分析?
可达性分析法:从GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到GC Roots 没有任何引用链相连时,则证明此对象是不可用的,所以虚拟机就判断是可回收对象 (Java中GC采用的就是这种对象回收方式)
可达性算法中的不可达对象并不是立即死亡的,对象拥有一次自我拯救的机会
2.为何不可达对象可以进行自我拯救?
因为对象被系统宣告死亡至少要经历两次标记过程:
- 第一次是经过可达性分析发现没有域GC Roots相连的引用链
- 第二次是在由虚拟机自动建立的finalize队列中判断是否需要执行finalize()方法
第二次标记具体过程:
-
当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,
若未覆盖,则直接将其回收 -
否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法
-
执行finalize方法完毕后,GC会在此判断该对象是否可达,若不可达,则进行回收;否则,对象“复活” (如果在finalize方法中引用了其他对象,就会判断其可达,然后进行"复活")
注意:
- 每个对象只能触发一次finalize()方法
- 由于finalize()方法运行代价昂贵,不确定性大,无法保证各个对象的调用顺序,不推荐使用它,建议遗忘它
3. GC Roots的对象有哪些?
-
虚拟机栈 (栈帧中的本地变量) 中引用的对象
例如存在一个add的静态方法,其中引用了了User对象
public static User add(int id) { //静态方法add
......
//User对象就是一个GC Roots,如果其他对象被它引用,那么它就就不能被回收
return new User();
}
方法执行完后局部变量都会被回收,但User对象是一个GC Roots,那么它是怎样被GC进行回收的呢?
必须要线程执行到add方法时(即add方法还在活动栈中),执行完后该方法出栈;GC在进行对象回收时,只会遍历活动栈中的内容 (即还在栈中的对象),把它们作为GC Roots对象进行可达性分析;由于add方法执行完后被弹出栈外,就不再进行可达性分析了,自然就就被GC回收了
-
方法区中类静态属性引用的对象
被static修饰符修饰
-
方法区中常量引用的对象
也是被static修饰符修饰
-
本地方法栈中的JNI (即一般说的Native方法) 引用的对象
native方法一般是由JVM自动去调用的
好了,今天有关JDK、JRE和JVM的区别,字节码的好处和GC的可达性分析的学习就到此结束了,欢迎大家学习和讨论!
参考视频链接:
https://www.bilibili.com/video/BV1Eb4y1R7zd (B站UP主程序员Mokey的Java面试100道)