现在的位置: 首页 > 综合 > 正文

03_早期优化、晚期优化、Java内存模型与线程、线程安全与锁优化

2013年05月09日 ⁄ 综合 ⁄ 共 4514字 ⁄ 字号 评论关闭

第十章早期(编译期)优化

 

10.1 概述

  Java 语言的“编译期”是一个“不确定”的操作过程。因为它可能是指一个前端编译器*.java文件转变成*.class文件的过程;也可能是指JVM后端运行期编译器(JIT编译器,Just
In Time Compiler
把字节转变成机器码的过程;还可能是指静态提前编译器(AOT编译器,Ahead
Of Time Compiler
直接把*.java文件编译成本地机器码的过程。

 

10.2 Javac编译器

1. sun Javac的代码来看,编译过程大致可分成三个过程:(1)解析与填充符号表过程(2)插入式注解处理器的注解处理过程(3)分析与字节码生成过程

 

10.2.2
解析与填充符号表(由parseFiles()方法完成)

1. 词法、语法分析

1)词法体分析是将源代码的字符流转变为标记(Token)集合,而标记是编译过程的最小单位,不可再分。

2)语法分析是根据Token序列来构造抽象语法树的过程。

2.填充符号表,完成语法分析和词法分析之后,下一步就是填充符号表的过程,符号表(Symbol Table)是由一组符号地址和符号信息构成的表格。

 

10.2.3
注解处理器

Java语言提供了对注解(Annotations)的支持,这些注解与普通的Java代码一样,是在运行期间发挥作用的。

 

10.2.4
语义分析与字节码生成

  语法分析之后,编译器获得了程序代码的抽象语法树表示,语法树能表示一个结构正确的源程序的抽象,但是无法保证源程序是符合逻辑的。而语义分析的主要任务是对结构上正确的源程序进行上下文有关性质的检查,如进行类型审查。

1. 标注检查

Javac的编译过程中,语义分析过程分为标注检查和数据及控制流分析两步,分别由attribute()flow()方法完成。标注检查步骤检查的内容包括诸如变量使用前是否已被声明等。在标注检查中,有一个重要的动作称为常量折叠(如:int
a = 1+2;
——> int a = 3;

2.数据及控制流分析

这一步是对程序上下文逻辑的更进一步的验证,它可以检查方法的每条路径是否都有返回值,是否所有的受检查异常都被正确处理了等问题。

3.解语法糖

1语法糖(Syntactic Sugar,也称糖也语法,是指在计算机语言中添加的某种语法,这种语法对语言的功能没有影响,但是更方便程序员使用,且增加了程序的可读性,减少程序代码出错的机会。

2Java中最常用的语法糖有泛型、变长参数、自动装箱拆箱等。JVM运行时不支持这些语法,它们在编译时会还原回简单的基础语法结构,这个过程称为解语法糖。在Javac的源码中,解语法糖的过程由desugar()方法触发。

4.字节码生成

  生成字节码是Javac编译过程的最后一个阶段,字节码生成阶段不仅仅是把前面各步骤所生成的信息转化成字节码写到磁盘中,编译器还进行了少量的代码添加和转换工作。

 

10.3 Java语法糖的味道

 几乎各种语言都会提供一些语法糖来方便程序员的编程工作,虽然语法糖不会提供实质性的功能改进,但是或可提高效率,或可提升语法的严谨性,或能减少编码出错的机会。

 

10.3.1
泛型与类型擦除

1. 泛型又分为真实泛型伪泛型Java的泛型是一种伪泛型,取Java的泛型只在程序源码中存在,在编译后的字节码文件中,它们会被替换回原来的原生类型(Raw
Type
,也称裸类型),并且在相应的地方插入了强制类型转换代码。Java中的泛型实现方法称为类型擦除。因此对于运行期的Java语言来说,ArrayList<int>ArrayList<String>就是同一个类。所以泛型擦除成相同的原生类型是无法重载的一部分原因。

2. Java语言中返回值不参与重载选择,但是如果两个方法有相同的名称和特征签名,而返回值不同,那它们也是可以合法地共存于同一个Class文件中,仿佛它们是可依靠返回值重载一样。

10.3.3
条件编译

1. 许多语言都提供了条件编译的途径,如C/C++中使用预处理指示符(#ifdef)来完成条件编译。C/C++的预处理器最初的任务是解决编译时的代码依赖关系。因为编译器一个一个地编译.C/.CPP文件。而各文件之间要互相提供一些符号信息。预处理器就是用来完成这一任务的。而Java语言天然的编译方式决定了它无须使用预处理器。

2. Java语言虽没有预处理器,但也可以借助条件为常量的if语句实现在方法体内部的基本级别的条件编译。Java的条件编译也是一颗语法糖,根据布尔常量值的真假,编译器会在解语法糖的阶段中把分支中不成立的代码块消除掉。

 

 

第十一章晚期(运行期)优化

 

为了提高热点代码的执行效率,在运行时,JVM会把这些代码编译成本地平台相关的机器码,并进行各种层次的优化,完成这个任务的编译器称为即时编译器Just In Time Compiler,即JIT编译器)。

 

11.2.1
解释器与编译器

1. 当程序要迅速启动和执行时,解释器可先发挥作用,省去编译的时间,立即执行。程序运行后,随时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码,以提高执行效率。当运行环境内存限制较大时,解释执行可节省内存;反之,编译执行可以提高速度。

2. 在运行过程中会被JIT编译的“热点代码”有两类:(1)被多次调用的方法(2)被多次执行的循环体

 

11.2.3
编译过程

1. 在默认设置下,无论是方法调用产生的即时编译请求,还是OSR编译请求,JVM在代码编译器未完成之前,仍按照解释方式运行,而编译动作则在后台的编译线程中进行。

2. Server CompilerClient Compiler两个编译器的编译过程是不一样的。

 

11.3 编译优化技术

11.3.1
优化技术分类

1. 编译器策略(延迟编译、分层编译、栈上替换、延迟优化)

2. 一个优化过程实例:

方法内联——>冗余访问消除——>复写传播——>无用代码消除

3. 方法内联的作用:(1)除去方法调用的成本;(2)为其它优化建立良好的基础

4. 逃逸分析:当一个对象在方法里被定义后,它可能被外部方法所引用,如果作为参数传递到其它方法中,这种行为称为方法逃逸。

5. 公共子表达式:如果一个表达式E已经被计算过了,并且先前的计算到现在E中所以变量的值都没有发生变化,那么E的这次出现就成为了公共子表达式。

6. Java语言中对象的内存分配都是在堆上进行的,只有方法的局部变量才能在栈上分配。而C/C++的对象则有多种内存分配方式,既可能在堆上分配,又可能在栈上分配,如果可以在栈上分配线程私有的对象,将减轻内存回收的压力。

7. Javac字节码编译器与虚拟机内的JIT编译器的执行过程合并起来其实就等同于一个传统的编译器所执行的编译过程……

 

 

第十二章 Java内存模型与线程

 

12.2硬件的效率与一致性

1. 缓存一致性(Cache Coherence):

  在多处理器系统中,每个处理器都有自己的高速缓存,而它们又都共享同一主内存。当多个处理器的运算任务都涉及同一块内存区域时,将可能导致各自的缓存数据不一致的情况。所以各处理器访问缓存时必须遵循一些协议。

2. Java内存模型的主要目标是定义程序中各个变量的访问规则,即在JVM中将变量存储到内存和从内存中和取出变量这样的底层细节。

3. JVM的每条线程都有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在各自的工作内存中进行,不能直接读写主内存中的变量,线程间变量的传递要通过主内存来完成。

 

12.3.5原子性、可见性和有序性

1. 原子性(Atomicity):由Java内存模型来直接保证的原子性变量包括readloadassignusestore、和write。此外,synchronized块之间的操作也具备原子性。

2. 可见性(Visibility):指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。

3. 有序性(ordering):如果在本线程内观察,则所有的操作都是有序的;如果在一个线程中观察另一个线程,则所有的操作都是无序的。

4. 普通变量与volatile变量的区别:volatile保证了新值能立即同步到主内存,以及每次使用前立即从主内存同步到内存刷新。因此volatile保证了多线程操作时变量的可见性,而普通变量不能保证这一点。

 

12.3.6
先行发生原则

  操作A先行发生于操作B:即发生操作B之前,操作A产生的影响能被操作B观察到。

1)程序次序规则(2)管程锁定规则(3volatile变量规则(4)线程启动规则

5)线程终止规则(6)线程中断规则(7)对象终结规则(8)传递性

  时间上的先后顺序与先行发生原则之间基本没有太大的关系,所以我们衡量并发安全问题的时候不要受到时间顺序的干扰,一切必须以先行发生原则为准。

 

 

12.4  Java与线程

实现线程主要在三种方式:(1)使用内核线程实现(2)使用用户线程实现(3)使用用户线程加轻量级进程混合实现

12.4.2 Java线程调度

1. 线程调度是指系统为线程分配处理器使用权的过程,主要调度方式有两种:(1)协同式(Cooperation Threads-Scheduling)线程调度(2)抢占式(Preemptive
Threads-Scheduling
)线程调度。

2. 在协同式调度的多线程系统中,线程的执行时间由线程本身来控制,线程把自己的工作执行完了之后,要主动通知系统切换到另一个线程上去。

3. 如果使用抢占式调度的多线程系统,那么每个线程将由系统来分配执行时间,线程的切换由系统来控制。

4. Java的线程会被映射到系统的原生线程上来实现,所以其线程调度最终还是由操作系统说了算。故不能在程序中通过优先级来完全准确地判断一组状态都为Ready的线程将会先执行哪一个。

 

12.4.3 状态转换:Java语言定义了五种进程状态

第十三章线程安全与锁优化

 

以下讨论的线程安全,限定于多个线程之间存在共享数据访问这个前提。

13.2.1 Java语言中的线程安全

  Java中种操作共享数据分为以下五类:不可变、绝对线程安全、相对线程安全、线程兼容和线程对立。

1. 不可变:(Immutable)不可变的对象一定是线程安全的,要想保证对象行为不影响自己的状态,从而使自己成为不可变对象的方法是:将对象中所有带有状态的对象都声明为final

 

13.2.2
线程安全的实现方法

1. 互斥同步(Mutual Exlusion & Synchronization):采取信号量、临界区机制、保证共享数据在同一时刻只被一条线程使用。

2. 非阻塞同步(Non-Blocking Synchronization):基于冲突检测的乐观策略,即先进行操作,如果没有其他线程争用共享数据,则操作成功。

3. 无同步方案:如果不涉及共享数据,则相应代码就是线程安全的,无需同步(可重入代码,线程本地存储)。

 

13.3
锁优化

1. 自旋锁与自适应锁     2.
锁消除

3. 锁粗化                                    4.
轻量级锁

5. 偏向锁

 

抱歉!评论已关闭.