JVM类加载与运行时优化
来源:互联网 发布:258优化网站 编辑:程序博客网 时间:2024/04/30 16:42
1. 类加载生命周期
a. 装载(load) i. 开始时机: 1) new实例化对象时,若类没有加载 2) 读取或设置一个类static字段,若类没有被加载。final除外,因为final字段的值已经在编译期放到了常量池中 3) 调用类的static方法 4) 反射调用类 5) 初始化一个类时,若父类没有被加载,会先加载父类 ii. 不会加载类的情况 1) 通过子类去引用父类的static字段,不会导致子类加载 2) 数组定义引用类,不会导致类加载。 如 Student[] stu = new Student[10], student类不会被加载 3) 读取类的final字段,不会导致类加载。 iii. 流程 1) 通过类的全限定名获取定义此类的二进制流。可以从class文件,网络,或者运行时计算(如动态代理)出这个二进制流 2) 将字节流代表的static存储结构转化为方法区的运行时数据结构,也就是存储到方法区中 3) 内存中生成一个代表此类的class对象 b. 链接 i. 验证 1) 目的:防止加载的class文件危害虚拟机本身安全 2) 流程: a) 文件格式验证,如magic是否为0xCAFEBABE,主次版本号是否在当前VM能处理范围内 b) 元数据验证,主要验证描述信息是否符合Java语言规范 c) 字节码验证,最复杂,通过数据流和控制流分析,确定程序语义是合法的,符合逻辑的 d) 符号引用验证,如通过全限定名能否找到类,字段方法的可访问性等。 ii. 准备 1) 目的:为static变量分配内存,并将它们统一初始化为0. static final除外 iii. 解析 1) 目的:将常量池中的符号引用替换为直接引用 a) 符号引用:字面量,如类名,方法名等 b) 直接引用:类或方法存放在内存中的地址 c. 初始化 i. 初始化static变量为实际的值。通过执行类构造器<clint>方法来完成。这个方法是编译器自动生成在字节码中的 ii. clinit: 1) static变量的赋值 + static{}语句块。 static{}语句块只能访问到定义在它之前的变量。 2) clinit和实例构造器init不同,不需要显式调用父类构造器。JVM保证子类的clinit执行前,父类的clinit肯定被执行过 3) 父类的clinit先执行,故父类中的static{}语句块在子类的static变量赋值前被调用 4) 接口中不能使用static{}语句块,但仍然可以为变量赋值 5) JVM可以保证clinit方法是线程安全的。多个线程同时去初始化一个类,只有一个线程会执行clinit方法,其他都会阻塞等待。
2. 编译期优化(早期优化)
为了保证JRuby,Groovy等语言编译的字节码也能得到性能优化,JVM将性能优化放在了后期的运行时优化,即JIT运行时编译优化中
编译期优化主要为语法糖,用来实现Java的各种新的语法特性,比如泛型,变长参数,自动装箱/拆箱
Java语法糖:与字节码无关,编译后会去掉它们。作用仅仅为方便码农写代码,以及将运行时异常在编译期及早发现(如泛型的使用)
1) 泛型与类型擦除
Java泛型只在编译期存在,编译完成后的字节码中会替换为原生类型。故称Java泛型为伪泛型。C#的泛型在运行期仍然存在。
2) 条件编译
if语句中使用常量。比如if(false) {}, 这个语句块不会被编译到字节码中.这个过程在编译时的控制流分析中完成。
3. 运行时优化(晚期优化)
1). 解释执行和编译执行的对比
a. 解释执行需要JVM解释器,速度慢。编译执行生成了本地机器语言,速度快。 b. 解释执行利用字节码,比机器码紧凑,故需要内存比编译执行小。 c. 解释执行可以直接在字节码上执行,而编译执行需要运行时将字节码先转化为机器码。故编译执行启动时间较长。 d. 编译器可以将一些运行频繁的热点字节码,优化为机器码,从而加快运行速度。解释器也可以将一些优化得比较激进的机器代码,逆优化为字节码,进行解释执行。
2)不同JVM的运行时优化策略
a. Hotspot采用解释器与编译器并存的构架。 i. 第0层,解释执行,不开启性能监控器,可触发第一层编译 ii. 第1层,将字节码编译为机器码,进行简单可靠的优化,可以开启性能监控 iii. 第2层,将字节码编译为机器码,会开启一些编译耗时的优化和一些不可靠的激进优化 b. 早期JVM很多只有解释器 c. JRockit虚拟机只有编译器,没有解释器。因为它是面向服务器应用的。
3)编译对象和触发条件
a. 热点代码 i. 被多次调用的方法 ii. 被多次执行的循环体 b. 热点探测方式 i. 基于采样的热点探测: 1) 周期性检查各个线程的栈顶,发现某个方法经常位于栈顶,则被认为是热点方法 2) 简单,高效,并且可以得到方法调用链。但很难精确确认方法热度,因为线程可能阻塞等 ii. 基于计数器 1) 为每个方法建立计数器,统计它的执行次数 2) 麻烦,但很精确。HotSpot中采用的就是这种方法 c. 流程: i. 检查是否存在被JIT编译过的方法版本 ii. 不存在,则计数器加1 iii. 判断计数是否超过阈值。超过,则提交即时编译的申请 iv. 执行引擎不会同步等待编译完成,而是继续使用解释器执行。 v. JIT编译完成后,方法的调用入口会被自动更新。这样,下一次调用的时候,就是新的入口,也就是JIT编译之后的了。
4)编译过程
a. 字节码 -> HIR,基础化的优化,如方法内联 b. HIR -> LIR, 从HIR中产生低级中间代码表示 c. LIR -> 机器码,使用线性扫描算法,在LIR上分配寄存器,产生机器代码
5)Java即时编译与C/C++编译对比
a. 劣势 i. Java即时编译是在运行时,故会占用用户程序的运行时间。而C/C++是静态编译为机器码的,完全不占用运行时间。 ii. Java运行时不能进行一些比较耗时的优化,故能做的优化也没有C那么多 iii. 多态选择频率远高于C,需要建立虚方法表。也正是多态的存在,使得编译优化难度远高于C。因为多态较难预测代码跳转分支。 iv. Java运行时可以加载新的类,如网络中的二进制流。这使得编译器无法看清程序全貌,全局优化很难进行。 v. Java对象都是在堆上分配(除了class对象在方法区),而C/C++既可以在堆上,又可以在栈上。栈上分配可以减轻垃圾回收压力,且速度远快于堆。 b. 优势 i. 可以进行性能监控,热点探测,分支频率预测,调用频率预测,从而有选择性的优化代码一句话,Java性能上的劣势,是为了换回码农开发效率上的优势。Java用心良苦,码农们不要再吐槽它的性能了!
0 0
- JVM类加载与运行时优化
- JVM运行和类加载过程
- JVM类加载运行内存过程
- JVM运行和类加载全过程
- 类加载与JVM分配
- JVM核心之 JVM运行和类加载全过程
- JVM核心之JVM运行和类加载全过程
- 深入JVM内核—原理与优化之六类加载器
- JVM类与类加载器
- JVM类加载机制与反射
- 图解Tomcat类加载机制与JVM
- JVM --结构与运行
- JVM配置与优化
- JVM类加载器原理与自定义类加载器
- 5. JVM类加载器机制与类加载过程
- JVM类加载器机制与类加载过程
- 图解JVM类加载机制与类加载过程
- 5. JVM类加载器机制与类加载过程
- 项目上传到cocoapods 打包framework上传到cocoapods
- AVC编码中的规格 :High、Baseline、Main什么意思?还有High@L3.0、High@L4.0、High@L5.1等
- fseek函数(转)
- Activity之间的动画切换学习笔记(一)
- UVA10161Ant on a Chessboard
- JVM类加载与运行时优化
- Node.js实现android的apk版本更新服务器
- FPGA基础知识18(在Quartus II下产生无源代码网表设计文件方法 QXP VQM 加密文件)
- 上三角形矩阵
- jQuery的deferred对象详解
- 【JZOJ 4622】亚瑟王之宫
- 手把手教你认识并搭建Nginx
- Windows快捷方式文件格式解析
- 按钮 CSS3悬浮按钮彩球效果