目前已经更新完《Java并发编程》和《Docker教程》,欢迎关注【后端精进之路】,轻松阅读全部文章。

Java并发编程:

Docker教程:

JVM性能优化:

5. 早期编译优化

早起编译优化主要指编译期进行的优化。

java的编译期可能指的以下三种:

  1. 前端编译器:将.java文件变成.class文件,例如Sun的Javac、Eclipse JDT中的增量式编译器(ECJ)
  2. JIT编译器(Just In Time Compiler):将字节码变成机器码,例如HotSpot VM的C1、C2编译器
  3. AOT编译器(Ahead Of Time Compiler):直接把*.java文件编译成本地机器码,例如GNU Compiler for the Java(GCJ)、Excelsior JET

本文中涉及到的编译器都仅限于第一类,第二类编译器跟java语言的关系不大。javac这类编译器对代码的运行效率几乎没有任何优化措施,但javac做了许多针对java语言代码过程的优化措施来改善程序员的编码风格和提高编码效率,java许多的语法特性都是靠编译器的语法糖来实现的。

5.1 javac编译器工作流程

Sun javac编译器的编译过程可以分为3个过程:

  • 解析与填充符号表过程
  • 插入式注解处理器的注解处理过程
  • 分析与字节码生成过程

1. 解析与填充符号表

解析步骤包括了经典程序编译原理中的词法分析与语法分析两个过程。

词法、语法分析:词法分析是将源代码的字符流转变为标记(Token)集合,单个字符是程序编写过程的最小元素,而标记则是编译过程的最小元素,关键字、变量名、字面量、运算符都可以成为标记

语法分析是根据Token序列构造抽象语法树的过程,抽象语法树(Abstract Syntax Tree,AST)是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表着程序代码中的一个语法结构(Construct),例如包、类型、修饰符、运算符、接口、返回值甚至代码注释等都可以是一个语法结构。

填充符号表:符号表(Symbol Table)是由一组符号地址和符号信息构成的表格,可以想象成K-V的形式。符号表中所登记的信息在编译的不同阶段都要用到。在语义分析中,符号表所登记的内容将用于语义检查和产生中间代码。在目标代码生成阶段,当对符号名进行地址分配时,符号表是地址分配的依据

2. 注解处理器

注解处理器是用于提供对注解的支持,可以将其看成一组编译器的插件。

3. 语义分析与字节码生成

语法分析后,编译器获得了程序代码的抽象语法树表示,语法树能表示一个结构正确的源程序的抽象,但无法保证源程序是符合逻辑的。

这部分主要分如下几步,完成语义分析与字节码生成:

  1. 标注检查

标注检查检查的内容包括变量使用前是否已被声明、变量与赋值之间的数据类型是否能够匹配等。在标注检查中,还有一个重要的动作称为常量折叠,这使得a=1+2比起a=3不会增加任何运算量

  1. 数据及控制流分析

数据及控制流分析是对程序上下文逻辑更进一步的验证,可以检查出诸如程序局部变量在使用前是否赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理等

  1. 解语法糖

语法糖(Syntactic Sugar),也称糖衣语法,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但方便使用。java在现代编程语言中属于低糖语言,java中的主要语法糖包括泛型、可变参数、自动装箱/拆箱等,虚拟机运行时不支持这些语法,它们在编译阶段还原回简单的基础语法结构,这个过程称为解语法糖

  1. 字节码生成

字节码生成阶段不仅仅时把前面各个步骤所生成的信息(语法树、符号表)转化成字节码写到磁盘中,编译器还进行了少量的代码添加和转换工作

5.2 Java语法糖

语法糖主要是为了方便程序员的代码开发,这些语法糖并不会提供实质性的功能改进,但是他们能提高效率。

以下介绍了Java中常用的语法糖。

泛型与类型擦除

Java中的参数化类型只在源码中存在,在编译后的字节码中,已经被替换为原来的原生类型了,并且在相应的地方插入了强制转换代码。对于运行期的Java 语言来说,ArrayList和ArrayList就是同一个类。所以说泛型技术实际上就是 Java语言的一颗语法糖,Java语言中的泛型实现方法称为类型擦除,基于这种方法实现的泛型称为伪泛型。

以下两个方法,在编译时,由于类型擦除,变成了一样的原生类型List,因此方法的特征签名变得一致,导致无法编译。

void method(List<Integer> list);
void method (List<String> list);

但是如果两者的返回值不一致,在JDK1.6中则可以编译通过,并不是因为返回值不同,所以重载成功。只是因为加入返回值后,两个方法的字节码特征签名不一样了,所以可以共存。但是在JDK1.7和1.8中,依然无法通过,会报两个方法在类型擦除后具有相同的特征签名。

Java代码中的方法特征签名只包含方法名称、参数顺序和参数类型,而在字节码中的特征签名还包括方法返回值及受查异常表。

方法重载要求方法具备不同的特征签名,返回值并不包含在方法的特征签名中,所以返回值不参与重载选择。但是在Class字节码文件中,只要描述符不是完全一致的两个方法就可以共存。

自动装箱和拆箱

自动装箱和拆箱实现了基本数据类型与对象数据类型之间的隐式转换。

public void autobox() {
Integer one = 1;
if (one == 1) {
System.out.println(one);
}
}

下面对自动装箱和自动拆箱进行详细介绍:

自动装箱就是Java自动将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。因为这里的装箱和拆箱是自动进行的非人为转换,所以就称作为自动装箱和拆箱。原始类型byte,short,char,int,long,float,double和boolean对应的封装类为Byte,Short,Character,Integer,Long,Float,Double,Boolean。

何时发生自动装箱和拆箱,

  1. 赋值:
Integer iObject = 3; //autobxing - primitive to wrapper conversion
int iPrimitive = iObject; //unboxing - object to primitive conversion
  1. 方法调用:当我们在方法调用时,我们可以传入原始数据值或者对象,同样编译器会帮我们进行转换。
public static Integer show(Integer iParam){
System.out.println("autoboxing example - method invocation i: " + iParam);
return iParam;
} //autoboxing and unboxing in method invocation
show(3); //autoboxing
int result = show(3); //unboxing because return type of method is Integer

自动装箱的弊端,

自动装箱有一个问题,那就是在一个循环中进行自动装箱操作的时候,如下面的例子就会创建多余的对象,影响程序的性能。

Integer sum = 0;
for(int i=1000; i<5000; i++){
sum+=i;
}

自动装箱与比较:

下面程序的输出结果是什么?

public class Main {
public static void main(String[] args) { Integer a = 1;
Integer b = 2;
Integer c = 3;
Integer d = 3;
Integer e = 321;
Integer f = 321;
Long g = 3L;
Long h = 2L; System.out.println(c==d);
System.out.println(e==f);
System.out.println(c==(a+b));
System.out.println(c.equals(a+b));
System.out.println(g==(a+b));
System.out.println(g.equals(a+b));
System.out.println(g.equals(a+h));
}
}

在解释具体的结果时,首先必须明白如下两点:

  • 当"=="运算符的两个操作数都是 包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。
  • 对于包装器类型,equals方法并不会进行类型转换。

下面是程序的具体输出结果:

true
false
true
true
true
false
true

注意到对于Integer和Long,Java中,会对-128到127的对象进行缓存,当创建新的对象时,如果符合这个这个范围,并且已有存在的相同值的对象,则返回这个对象,否则创建新的Integer对象。

对于上面的结果:

cd:指向相同的缓存对象,所以返回true;

ef:不存在缓存,是不同的对象,所以返回false;

c(a+b):直接比较的数值,因此为true;

c.equals(a+b):比较的对象,由于存在缓存,所以两个对象一样,返回true;

g(a+b):直接比较的数值,因此为true;

g.equals(a+b):比较对象,由于equals也不会进行类型转换,a+b为Integer,g为Long,因此为false;

g.equals(a+h):和上面不一样,a+h时,a会进行类型转换,转成Long,接着比较两个对象,由于Long存在缓存,所以两个对象一致,返回true。

关于equals和==:

  • .equals(...) will only compare what it is written to compare, no more, no less.
  • If a class does not override the equals method, then it defaults to the equals(Object o) method of the closest parent class that has overridden this method.
  • If no parent classes have provided an override, then it defaults to the method from the ultimate parent class, Object, and so you're left with the Object#equals(Object o) method. Per the Object API this is the same as ==; that is, it returns true if and only if both variables refer to the same object, if their references are one and the same. Thus you will be testing for object equality and not functional equality.
  • Always remember to override hashCode if you override equals so as not to "break the contract". As per the API, the result returned from the hashCode() method for two objects must be the same if their equals methods show that they are equivalent. The converse is not necessarily true.

遍历循环

遍历循环语句是java5的新特征之一,在遍历数组、集合方面,为开发人员提供了极大的方便。

public void circle() {
Integer[] array = { 1, 2, 3, 4, 5 }; for (Integer i : array) { System.out.println(i); }
}

在编译后的版本中,代码还原成了迭代器的实现,这也是为遍历循环需要被遍历的类实现Iterable接口的原因。

变长参数

Arrays.asList(1, 2, 3, 4, 5);

条件编译

条件编译也是java语言的一种语法糖,根据布尔常量值的真假,编译器将会把分支中不成立的代码块消除掉。

public void ifdef() {if (true) {

System.out.println("true");

} else {//此处有警告--DeadCode

System.out.println("false");

}

}

对枚举和字符串的switch支持

public void enumStringSwitch() {

String str = "fans";

switch (str) {

case "fans":

break;case "leiwen":

break;default:

break;

}

}

try-with-resources

在try语句中定义和关闭资源 jdk7提供了try-with-resources,可以自动关闭相关的资源(只要该资源实现了AutoCloseable接口,jdk7为绝大部分资源对象都实现了这个接口)。

staticStringreadFirstLineFromFile(Stringpath)throwsIOException{

try(BufferedReaderbr=newBufferedReader(newFileReader(path))){

returnbr.readLine();
}
}

本文由『后端精进之路』原创,首发于博客 http://teckee.github.io/ , 转载请注明出处

搜索『后端精进之路』关注公众号,立刻获取最新文章和价值2000元的BATJ精品面试课程

JVM性能优化系列-(5) 早期编译优化的更多相关文章

  1. JVM性能优化系列-(6) 晚期编译优化

    6. 晚期编译优化 晚期编译优化主要是在运行时做的一些优化手段. 6.1 JIT编译器 在部分的商用虚拟机中,java程序最初是通过解释器(Interpreter) 进行解释执行的,当虚拟机发现某个方 ...

  2. 小师妹学JVM之:深入理解JIT和编译优化-你看不懂系列

    目录 简介 JIT编译器 Tiered Compilation分层编译 OSR(On-Stack Replacement) Deoptimization 常见的编译优化举例 Inlining内联 Br ...

  3. SQL优化系列(二)- 优化Top SQL

    优化最耗资源的N条SQL语句 如何从SGA或者AWR中找出最消耗资源的SQL, 例如最慢的20条SQL, 然后逐条优化? SQL自动优化工具SQL Tuning Expert Pro for Orac ...

  4. SQL优化系列(一)- 优化SQL

     优化SQL SQL开发人员从源代码中发现一条跑得很慢的SQL, 如何优化? DBA从AWR报告中发现一条跑得很慢的SQL,没有源代码或者不想修改源代码怎么办? SQL自动优化工具SQL Tuning ...

  5. Android性能优化系列之App启动优化

    Android性能优化系列之布局优化 Android性能优化系列之内存优化 Android性能优化系列之apk瘦身 应用的启动速度缓慢是我们在开发过程中常常会遇到的问题,比方启动缓慢导致的黑屏.白屏问 ...

  6. PLSQL_性能优化系列16_Oracle Tuning Analyze优化分析

    2014-12-23 Created By BaoXinjian

  7. JVM执行引擎总结(读《深入理解JVM》) 早期编译优化 DCE for java

    execution engine: 运行时栈current stack frame主要保存了 local variable table, operand stack, dynamic linking, ...

  8. Android性能优化系列之Bitmap图片优化

    https://blog.csdn.net/u012124438/article/details/66087785 在Android开发过程中,Bitmap往往会给开发者带来一些困扰,因为对Bitma ...

  9. 性能优化系列七:SQL优化

    一.SQL在数据库中的执行过程 二.执行计划 1. ACID 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节.事务在执行过程中发生错误,会 ...

随机推荐

  1. Springboot引入本地jar时打包

    在项目的开发过程中有时我们需要引入我们本地的jar包,这些jar包没有存在maven仓库中 ,这时没有办法通过pom文件直接引入,在开发过程中我们可以通过add as library的方式,可以在开发 ...

  2. JMeter——聚合报告

    AggregateReport 是 JMeter 常用的一个 Listener,中文被翻译为“聚合报告”. ​ 对于每个请求,它统计响应信息并提供请求数,平均值,最大,最小值,错误率,大约吞吐量(以请 ...

  3. 用HttpURLConnection来完成HTTP发送报文接收报文!

    public String sendMsg(String url, byte[] PostData) { String content = null; URL urls = null; try { u ...

  4. Navicat Premium 15 永久激活版安装教程

    前言 Navicat 可以说是众多程序猿小伙伴的忠爱了,因为界面简洁且操作简单,让我们爱不释手:最近Navicat Premium 15发布了, 让我们来看看如何安装永久激活版哦(简称白嫖版) Nav ...

  5. Java Web面试题整理(思维导图)

    1,动态网站技术有哪些? 2,一般的Web架构是指BS 还是CS,BS架构是什么咚咚? 3,Web应用程序的流程,即把一个URL串输入地址栏后发生写什么? 4,说一说Servlet生命周期? 5,在W ...

  6. [计算几何+图论]doge

    题意 在平面直角坐标系上,你有一只doge在原点处.doge被绳子拴住了,绳子不会打结,没有弹性(但很柔软),并且长度为L.平面上有一些目标,因此你的doge会按照顺序去捡起它们,但是doge只能走直 ...

  7. [HNOI2008]GT考试(kmp,dp,矩阵乘法)

    [HNOI2008]GT考试(luogu) Description 求有多少个n位的数字串不包含m位的字符串(范围 n <= 1e9 n<=1e9, m <= 20m<=20) ...

  8. tcpdump用法说明

    tcpdump采用命令行方式对接口的数据包进行筛选抓取,其丰富特性表现在灵活的表达式上. 不带任何选项的tcpdump,默认会抓取第一个网络接口,且只有将tcpdump进程终止才会停止抓包. 例如: ...

  9. Docker底层架构之基础架构

    Docker 采用了 C/S架构,包括客户端和服务端. Docker daemon 作为服务端接受来自客户 的请求,并处理这些请求(创建.运行.分发容器). 客户端和服务端既可以运行在一个机器上,也可 ...

  10. jsp路径

    访问静态资源的时候${pageContext.request.Context}没有作用,在浏览器F12调试的时候发现,路径并没有被解释为项目的根路径,而是没有解释出来,还是${pageContext. ...