一个Java文件的执行全部过程你确定都清楚吗?( 二 )


好处:1、 防止自定义的类篡改核心类库中的代码 。自定义的和类路径 . 类名与核心类库一样的类会委托给启动类加载,启动类加载器会根据包名 . 类名在内存查看是否已经加载,那么面对自定义的类启动类加载器会认为已经加载过了 。如果是给系统类加载器或者自定义类加载器加载的话可能就会产生多个类名相同的类,那么其他类在调用对应基类的话就会报错 。
2、防止同一个类被重复加载 。
4、可见性:子类加载器可以访问父类加载器加载的类型,但是反过来是不允许的 。不然,因为缺少必要的隔离,我们就没办法利用类加载器去实现容器的逻辑 。
解释器和即时编译器(JIT)主要用于将 Class 数据文件编译成对应的本地机器码执行 。
解释器传统的编译工具,主要分为 字节码解释器 和 模版解释器 。
字节码解释器 是在执行时通过纯软件代码模拟字节码的执行,效率低下;模版解释器则是主流使用的解释器,原理是将每一条字节码 和一个模版函数关联,在 Class 字节码转成机器码的过程中会通过对应的模版函数生成对应的机器码,这样短期来看效率还不错,但是一旦 同一个的字节码被多次执行,那么每次都需要通过模版函数生成机器码,效率十分低下 。
即时编译器(JIT)JIT 的原理是将字节码关联的 模版数据直接转成机器码,然后将机器码缓存起来,后面如果再次执行这个字节码时就直接返回缓存中的机器码,省去了二次执行的时间,缺点是第一次的转换消耗比较长,所以以单次执行来看,JIT 的效率是不如 解释器的,但是一旦执行的字节码重复数多,JIT 的作用就体现出来了 。HotSpot 中有两个 JIT 编译器,分别是Client Compiler和Server Compiler,但大多数情况下我们简称为C1 编译器和 C2编译器 。C1进行简单的优化,耗时短 。C2进行耗时长的优化,代码执行效率更高 。实际中C1和C2共同协作执行的 。
实际过程当虚拟机启动时,解释器可以首先发挥作用,而不必等待即时编译器全部编译完成再执行,这样可以省去许多不必要的编译时间 。并且伴随着程序运行时间的推移,即时编译器逐渐发挥作用,根据热点探测功能,将有价值的字节码编译为有价值的本地机器指令,以换取更高的程序执行效率 。
热点代码探测的方式1、规定热点阀值 。
每次方法调用时该方法的调用次数都会+1 ,当调用次数达到阀值,就会触发 JIT编译 。热点阀值可通过 -XX:CompileThreshold= 来设定 。

一个Java文件的执行全部过程你确定都清楚吗?

文章插图
 
如果不做任何设置,方法调用计数器统计的并不是方法被调用的绝对次数,而是一个相对的执行频率,即 一段时间之内方法被调用的次数  。当超过 一定的时间限度 ,如果方法的调用次数仍不足以让它提交给 JIT编译,那这个方法的调用计数器就会 减少一半 ,这个过程称为方法调用计数器热度的 衰减 ,而这段时间就称为此方法统计的 半衰周期  。
可以使用-XX:-UseCounterDecay 来关闭热度衰减,也可以使用-XX:CounterHalfLifeTime设置半衰周期的时间 。
2、回边计数器 。
统计一个方法中循环体代码执行的次数 。在字节码中遇到控制流向后跳转的指令称为 “回边” 。显然,建立回边计数器统计的目的是为了触发OSR 编译( JIT编译) 。
一个Java文件的执行全部过程你确定都清楚吗?

文章插图
 
运行时数据区域程序计数器线程私有,是当前线程执行的字节码指示器,指示字节码的执行顺序 。程序计数器的内存是单独的,不会受到其他变量、对象的影响 。所以它不会发生内存溢出 。也是 JVM唯一 一个没有规定任何 OOM 的区域 。也不存在GC  。
Java虚拟机栈线程私有 。先进后出,是代码执行的核心位置,一个方法在执行前会生成这个方法对应的栈帧,栈帧 包括局部变量表(保存局部变量)、操作数栈(进行局部变量的操作)、动态链接(其他对象、方法的引用)、方法返回值以及一些附加信息 。然后进行压栈操作,开始方法的执行,如果此方法中调用了其他方法,那么会将调用的这个方法对应的栈帧压入栈,等到这个方法执行完之后,如果方法包含返回值,将这个返回值返回给上一个方法,然后这个被调用的栈帧出栈,随后继续执行上一个栈帧 。
一个Java文件的执行全部过程你确定都清楚吗?

文章插图
 


推荐阅读