面试-JVM
JDK、JRE与JVM之间的关系
-
JDK:Java的SDK开发工具包
-
JRE:运行时环境;包含JavaAPI、JVM在内,提供了Java程序执行的最低的环境要求
-
JVM:为Java字节码的执行提供了保障
类加载子系统—>执行引擎子系统—>运行时数据区—>垃圾回收子系统—>本地接口和本地方法库
面试-类加载子系统
类加载子系统将这些字节码文件先装载进内存
类加载:JVM需要用到某个类;虚拟机会加载它的.class文件;创建对应的Class对象;
类加载过程
-
加载
-
通过一个类的全限定名来获取定义此类的二进制流
-
将字节流所代表的静态存储结构转化为方法区的运行时数据结构
-
内存中生成一个.class对象,作为方法区这个类的各种数据访问接口
-
-
验证
-
文件格式验证
-
是否以魔数0xCAFEBABE开头
-
主、次版本号是否在当前Java虚拟机接受范围之内
-
常量池是否有不支持的常亮、是否有不存在的常量
-
UTF8编码
-
Class文件中信息
-
-
元数据验证
-
是否继承了不允许继承的类
-
非抽象类是否实现了其他类或者接口之中要实现的所有方法
-
字段方法是否与父类产生矛盾
-
-
字节码验证
-
通过数据流分析和控制流分析,确定程序语义是合法的、符合逻辑
-
-
符号引用验证
-
该类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源
-
-
-
准备;解析
-
静态变量分配内存空间
-
类方法解析
-
接口方法解析
-
-
初始化
-
为静态变量赋值
-
执行static代码块
-
执行类构造器方法
-
-
使用和卸载
-
虚拟机自带的类加载器加载的类,在虚拟机的生命周期中始终不会被卸载
-
类加载器顺序
加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类
-
Bootstrap ClassLoader
加载$JAVA_HOME中jre/lib/rt.jar里所有的class
-
Extension ClassLoader
加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包
-
App ClassLoader
加载classpath中指定的jar包及目录中class
-
Custom ClassLoader
应用程序根据自身需要自定义的ClassLoader
-
当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载【ClassLoaderId类加载器 + PackageName包名 + ClassName类名称】,如果已经加载则直接返回原来已经加载的类
-
当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader
-
当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回
双亲委托模型
-
当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载【ClassLoaderId类加载器 + PackageName包名 + ClassName类名称】,如果已经加载则直接返回原来已经加载的类
-
当前classLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载,一直到bootstrp ClassLoader
-
当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回
为何采用双亲委派机制
-
防止类的重复加载
-
保护程序安全、防止核心API被篡改
热部署
-
销毁该自定义ClassLoader
-
更新class类文件
-
创建新的ClassLoader去加载更新后的class类文件
为什么要破坏双亲委托
-
实现可拔插机制【mysql的Driver接口定义在jdk当中的,而其实现由各个数据库的服务商来提供,启动类加载器来委托子类来加载Driver实现】
-
加载流程
-
Bootstrap加载Java核心类(核心类中包含Launcher类)
-
初始化并创建Ext、App类加载器
-
将Ext设置为App的父类加载器 → 同时再将App设置为默认的线程上下文类加载器
-
Bootstrap加载接口的时候;委托子类 App ClassLoader依赖SPI的动态服务发现机制加载META-INF/services路径下的实现类;
-
面试-执行引擎子系统
将字节码指令解释/编译成对应平台上的本地机器指令
执行技术
-
解释执行:用到某处代码时,转换为机器码执行
-
静态编译:程序在启动前,编译成对应平台的机器码
-
即时编译:运行过程中,执行比较频繁的代码转换机械码并存储下来
-
自适应优化:经常调用的方法启动一个后台线程,将其编译为本地代码,并进行仔细优化
-
芯片级直接执行:直接编写机器码的
执行引擎工作过程
javac编译过程
-
词法分析:先读取源代码的字节流数据,然后根据源码语言的语法规则找出源代码中的定义的语言关键字,如if、else、while、for等,然后判断这些关键字的定义是否合法,对于合法的关键字生成用于语法分析的记号序列,同时创建符号表,将所有的标识符记录在符号表【收集属性信息】中,这个过程就被称为词法分析。
-
语法分析:对词法分析后得到的Token流进行语法分析,就是依据源程序的语法规则,检查这些关键词组合在一起是否符合Java语言规范,比如if的后面是不是紧跟着一个布尔型判断表达式、else是否写在if后面等。对于符合规范的,组织上一步产生的记号序列生成语法树。形成一颗符合Java语言规定的抽象语法树。抽象语法树是一个结构化的语法表达形式,它的作用是把语言的主要词法用一个结构化的形式组织在一起,这棵语法树可以被后面按照新的规则再重新组织。
-
语义分析:经过语法分析后就不存在语法错误这些问题了,语义分析主要任务有两个,一个是对上步产生的语法树进行检查,其中包括类型检查、控制流检查、唯一性检查等,第二个则是将一些复杂的语法转换为更简单的语法,相当于把一些文言文、古诗、成语翻译成大白话的意思。比如将foreach转化为for循环、循环标志位替换为break等。
-
字节码生成:将简化后的语法树转换为Class文件的格式,也就是在该阶段会根据简化后的语法树生成字节码。
执行引擎执行过程
-
编译器代码优化
-
机械相关优化
-
寄存器分配优化
-
目标代码生成器
-
本地机械指令
JVM执行引擎子系统
解释器
执行一个方法或某处代码,根据定义的规范,对每条需执行的字节码指令逐行解释
JIT及时编译
执行次数比较频繁的代码直接编译成本地机器码
如何判断热点代码
-
方法调用计数器(Client1500,Server10000;根据方法调用次数)
-
回边计数器(循环体的执行次数)
方法调用计数器统计的执行次数并不是绝对次数;热度衰减:当超过一定的时间,但计数器还是未达到编译阈值无法提交给JIT即时编译器编译时,那此时就会对计数器进行减半**
-
采样探测
周期性的检查每个线程的虚拟机栈栈顶;经常出现在栈顶的方法
-
优点:实现简单
-
缺点:无法实现精准探测
-
-
踪迹探测
将一段频繁执行的代码作为一个编译单元;对该单元进行编译
-
优点:实现复杂
-
缺点:实现精准探测
-
为何保留解释器
-
保证绝对的跨平台性:移除就代表着:每到一个不同的平台,比如从Windows迁移到Linux环境,那么JIT又要重新编译
-
保证Java启动速度:所有的字节码指令全部编译为机器码指令,需要的时间开销是非常巨大的