最近裸辞,好好歇歇。整理面试题,欢迎关注。
无论是使用开发工具 IDEA
Eclipse
还是使用容器 Tomcat
jetty
, Java 程序的执行流程如下:
.java
编译成 .class
类型的文件; Class Loader
装载 class 文件; 简单来说,加载指的是把class字节码文件从各个来源通过类加载器装载入内存中。
这里有两个来源:
注:为什么会有自定义类加载器?
一方面是由于java代码很容易被反编译,如果需要对自己的代码加密的话,可以对编译后的代码进行加密,然后再通过实现自己的自定义类加载器进行解密,最后再加载。
另一方面也有可能从非标准的来源加载代码,比如从网络来源,那就需要自己实现一个类加载器,从指定源进行加载。
(上图)这种层次关系称为类加载器的 双亲委派模型 。双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此, 所有的类加载请求最终都应该被传递到顶层的启动类加载器中 ,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。
主要是为了保证加载进来的字节流符合JVM规范。对于字节码的验证,保证程序语义的合理性,比如要保证类型转换的合理性。对于符号引用的验证,比如校验符号引用中通过全限定名是否能够找到对应的类?校验符号引用中的访问性(private,public等)是否可被当前类访问?
主要是为类变量分配内存,赋初始值。特别需要注意,初值,不是代码中具体写的初始化的值,而是Java虚拟机根据不同变量类型的默认初始值。比如8种基本类型的初值,默认为0;引用类型的初值则为null;常量的初值即为代码中设置的值, final static tmp = 123
, 那么该阶段tmp的初值就是123
将常量池内的符号引用替换为直接引用的过程。在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。
这个阶段主要是对类变量初始化,执行类构造器。
在 HotSpot 虚拟机中, 热点探测 是 JIT 的触发标准。
主要是虚拟机会周期性的检查各个线程的栈顶,若某个或某些方法经常出现在栈顶,那这个方法就是“热点方法”。这种判定方式的优点是实现简单;缺点是很难精确一个方法的热度,容易受到线程阻塞或外界因素的影响。
热点探测是基于计数器的热点探测,采用这种方法的虚拟机会为每个方法建立计数器统计方法的执行次数,如果执行次数超过一定的阈值就认为它是“热点方法” 。
虚拟机运行时候,当计数器超过阈值溢出了,就会触发 JIT 编译,JIT 编译器就会将这段代码编译成机器语言并缓存,在该循环时间段内,会直接将执行代码替换,执行缓存的机器语言。
方法调用计数器用于统计方法被调用的次数,默认阈值在 client 模式下是 1500
次,在 server 模式在是 10000
次,可通过 -XX: CompileThreshold
来设定;
回边计数器用于统计的是方法中循环体代码执行的次数。 默认阈值在 client 默认为 13995
,server 默认为 10700
,可通过 -XX: OnStackReplacePercentage=N
来设置;