
一、JVM 与 JDK之间的联系
JDK是一个大集合,囊括了JRE、JDK工具(javac、javap...),同样包括了最为重要的JVM
二、JDK自带工具
2.1 Javap 工具
Javap工具可以将无法识别(除非借助查看JVM Class字节码文件的协议信息才能读懂),因此通过Javap反编译工具将字节码文件重新反编译成能读懂的文件
2.2 JStack
检测线程情况,判断是否存在死锁
2.3 JConsole
图形化界面查看JVM 运行时数据区的使用情况,垃圾回收的次数与时间
2.4 JVisualvm
同JConsole,功能更加强大
三、JVM为何能够跨平台
比如 Groovy、Java、Scala、kotlin都能依托JVM运行,是因为这些语言会将源代码编译成符合JVM约束的Class文件
四、Java类加载流程
Java的类加载依赖于ClassLoader,ClassLoader会将Class字节码文件加载到内存中,并负责进行下面的加载、链接、初始化过程
- 加载
将Class文件转换为Kclass数据结构,并存放在方法区中,并在堆中生成一个java.lang.class对象作为对方法区Kclass数据对引用;
其中两者互相持有指针。 - 链接
- 验证
主要是验证Class文件的正确性,如Magic、版本等信息 - 准备
为静态变量分配内存,并初始化成默认值
对于static final
修饰的变量,对于基本数据类型或者String类型数据,还是在准备阶段就进行了赋值,而不会进入到初始化阶段才会赋值 - 解析
将类中的符号引用转换为直接引用
- 验证
- 初始化
在初始化的过程中,如果发现类还没有加载与链接,则会先进行加载与链接;
指定class文件中已经构造好clinit
的方法,clinit
方法中包含了:父类的静态变量初始化、父类静态代码块、当前类静态变量初始化、当前类静态代码块;需要注意的是clinit方法只会随着类的加载只执行一次;
触发时机?比如Class.forName
就会导致初始化;这里就有个常规的面试题:为什么加载数据库驱动需要使用此方式?
五、Class.forName 与 ClassLoader的区别
5.1 Class.forName
Class.forName有两种参数形式方法:
- Class.forName(String name)
- Class.forName(String name, boolean initialize, ClassLoader loader )
在第二个方法中,initialize参数就是控制加载的时候是否进行初始化,默认为True.
5.2 ClassLoader.loadClass
方法签名:
- ClassLoader.loadClass(String name)
该方式无法进行 初始化 操作。
5.3 比较
两者都可以保证类加载中的 加载、链接
但Class.forName默认可以进行初始化操作,而ClassLoader方式不会
六、ClassLoader类加载器
6.1 分类
- Bootstrap ClassLoader
负责加载JRE中核心类等等,如:rt.jar - Extension ClassLoader
负责加载JRE中lib/ext
目录中的包,因此,如果我们自定义的一些Class可以放在这个目录中 - Application ClassLoader
应用程序中的所有类都由该加载器加载 - 自定义ClassLoader
6.2 加载流程:双亲委派机制
这是类加载机制中需要遵守的规则
- 检查某个类是否已经被加载
自底向上 - 加载某个类
自顶向下
6.2.1 双亲委派机制的意义
- 避免相同类的重复加载(相同类只会加载一次)
- 保护Java核心类免遭破坏(比如你重新写一个String类,在加载你应用级别代码时,先会看String类是不是已经被其它类加载器加载了)
七、JVM组成部分(简述)
这部分主要由两部分组成,分别是 运行时数据区和执行引擎两部分组成,下面将进行阐述
7.1 运行时数据区
在上述部分中详细介绍了
class
字节码加载到JVM的过程,那么如何组织这些Class文件中不同的结构以及Class中的指令该如何运行,这些就是运行时数据区应该考虑的事情,而方法区中的所有区域生命周期也不同,有的区域生命周期同JVM生命周期,有的区域的生命周期同应用程序中的线程生命周期
运行时数据区的组成部:
- 方法区
- 堆
- 栈
- 本地方法栈
- 程序计数器
7.1.1 方法区
- 线程共享,线程不安全,生命周期同JVM
- 方法区中存放内容如下:
- 已经被类加载器加载的Class信息,表现上为KClass数据结构
- 类静态变量(JDK1.6及之前)
- 运行时常量池
- 即时编译后CPU可直接执行的机器码
- 方法区是一种标准。其实现在不同版本JDK中各有不同
在JDK1.7及之前使用的是永久带的实现方式,在JDK1.8开始使用的是元空间实现方式 - OOM
当方法区内存不足时会抛出OOM
7.1.2 堆
-
线程共享区域
-
主要用于存储各类对象以及数组
-
生命周期同JVM
-
OOM
当堆空间不足时会抛出OOM
7.1.3 栈
- 每个线程所独立存在的区域,因此线程安全
- 是一种栈数据结构
- 当线程需要执行一个方法时,就对应着栈中的栈桢(Frame)结构,当线程需要执行某个方法时,就压入栈,当执行完毕之后再弹出栈
7.1.4 程序计数器
线程安全,记录当前线程正在执行的指令地址,用于假设当前线程失去了CPU执行权,当重新获取CPU执行权时,能够恢复之前的线程
继续完成任务
7.1.5 本地方法栈
线程安全,结构同栈,只是本地方法栈执行的是标注有native
关键字的方法,而这些方法是由C、C++
编写的。
7.2 执行引擎
在执行引擎中包括了三个部分,下面逐一介绍
7.2.1 即时编译器
当开启了开选项之后(默认开启),会动态侦听热点代码,当发现有热点代码之后,通过即时编译器可以将某一段热点代码编译+解释形成CPU可直接执行的机器码指令,并保存在方法区,之后直接可以运行,从而提高热点代码的执行效率
7.2.2 垃圾回收器
这也是Java流行的原因之一,因为Java有垃圾回收器,从而不需要开发人员自己去释放垃圾
7.2.3 解释器
之前面试有个常考的面试题:Java是编译形语言还是解释形语言?其实Java是两者都有兼顾到
Java先利用Javac工具将Java代码编译成Class字节码文件,当类加载器将Class字节码文件加载到JVM之后,真正运行字节码指令时,还是需要解释器去将指令翻译成CPU可识别的指令。
网友评论