この図を例にとると、.java から .class へのコンパイル処理が、.class からマシンコードへの解釈処理になります。これらは以下で個別に最適化されます。最適化プロセスにおいて、コンパイル段階の最適化は主にフロントエンドコンパイラの最適化であり、実行段階の最適化は主にジャストインタイムコンパイラの最適化です。
#コンパイラの最適化
##コンパイル プロセス
#上記は javac コンパイル プロセスの図であり、以下は javac コンパイル プロセスのメイン コードです。
#次の手順について詳しく説明します1. シンボル テーブルの解析と入力
語彙分析
2. アノテーション プロセッサ
この部分は、コンパイル中にアノテーションを処理するプラグイン アノテーション プロセッサのプロセスです。構文ツリーを変更できます。変更後、コンパイラは再処理のために上記の最初のステップに戻ります。各ループは Round と呼ばれ、上図のループバック プロセスに相当します。
3. 意味解析とバイトコード生成
構文解析後、生成された構文ツリーは正しい構造を持つソース プログラムを抽象化したものですが、ソースが正しい構造であるという保証はありません。プログラムは論理的です。セマンティック分析のタスクは、構造的に正しいソース プログラムのコンテキスト依存的な性質を調べることです。たとえば、次のコードのエラーは、セマンティック分析段階でのみチェックできます。 boolean a=false;
char b=2;
int c=a+b
注釈チェック
変数が使用前に宣言されているかどうか、変数と変数の間のデータ割り当て、型が一致するかどうかなど。 a=1 2 を a=3 に変える定数折りも存在します。したがって、コード内の a=1 2 および a=3 によって、プログラムの実行中に CPU 命令の操作量が増加することはありません。
データと制御フローの分析
プログラムのローカル変数に使用前に値が割り当てられているかどうか、メソッドの各パスに戻り値があるかどうかを確認します。すべてがチェックされているかどうか、例外が正しく処理されるかどうか、およびその他の問題が発生するかどうか。クラスロード時のデータ・制御フロー解析もあり、目的は基本的に同じですが、検証範囲が異なり、一部の検証項目はコンパイル時またはランタイム時のみ実行できます。
構文シュガーを理解する
構文シュガーとは、コンピューター言語に特定の構文を追加することです。言語の機能には影響しませんが、可読性を向上させることができます。プログラムの。構文シュガーには、ジェネリック、自動アンボックス化などが含まれます。これらの構文は仮想マシン ランタイムではサポートされていないため、コンパイル フェーズ中に基本構文構造に戻ります。このプロセスは、構文シュガーのデコードと呼ばれます。
バイトコード生成
これまでの手順で生成した情報(構文ツリー、シンボルテーブル)をバイトコードに変換してディスクに書き込み、小さなAを追加して変換します。コードの量。たとえば、文字列の追加操作を StringBuffer または StringBuilder の append() 操作に置き換えます。
この時点で、Class ファイルが生成されます。语法糖
语法糖是java中添加某种语法,对语言的功能没有影响,但是可以增加程序的可读性。包括泛型、内部类、枚举类等。
1、泛型与类型擦除
泛型可用于类、接口和方法的创建中,用于对放入集合元素的类型的约束。泛型只在程序源码中存在,在编译阶段有解语法糖的步骤,所以在.Class文件中,已经变为了原来的原生类型了。这个过程叫做类型擦除。
泛型擦除前:
public static void main(String[] args){ Map<String,String> map=new HashMap<>(); map.put("姓名","小明"); map.put("性别","男"); sout(map.get("姓名")); sout(map.get("性别")); }
泛型擦除后:
public static void main(String[] args){ Map map=new HashMap(); map.put("姓名","小明"); map.put("性别","男"); sout((String)map.get("姓名")); sout((String)map.get("性别")); }
所以ArrayList和ArrayList在运行期时是同一个类。
2、自动拆装箱、循环遍历
这些是java中使用最多的语法糖。编译前:
public static void main(String[] args){ List<Integer> list=Arrays.asList(1,2,3,4); int sum=0; for(int i:list){ sum +=i; } System.out.println(sum); }
编译后:
public static void main(String[] args){ List list=Arrays.asList(new Integer[] { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4)}); int sum=0; for(Iterator localIterator=list.iterator();localIterator.hasNext();){ int i=((Integer)localIterator.next()).intValue(); sum +=i; } System.out.println(sum); }
可见,自动拆装箱在编译后被转化为了对应的包装和还原方法,如Integer.valueOf()和Integer.intValue()。
遍历循环则把代码还原为了迭代器的实现。
3、条件编译
根据布尔常量值的真假,编译器会把分支中不成立的代码块消除掉。
public static void main(String[] args){ if(true){ sout("block 1"); }else{ sout("block 2"); } }
编译后,代码变为:
public static void main(String[] args){ sout("block 1"); }
运行期优化
一般情况下,我们将.java编译成.class,.class再解释成机器码。但是也有特殊的情况。有些代码调用比较频繁,比如某个方法或代码块的运行特别频繁,为了提高程序的执行效率,在运行时,虚拟机会把这个代码直接编译成机器码,并进行各种层次的优化。这样的代码称为热点代码。完成这个任务的编译器被称为即时编译器。但是其并不是虚拟机必需的部分。
即时编译器的概述
(1)为什么虚拟机要使用解释器和编译器并存的架构?
虚拟机里包含着解释器和编译器。当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间,立即执行。在程序运行后,随着时间的推移,编译器逐渐发挥作用,把越来越多的代码编译成本地代码之后,可以获取更高的执行效率。
当程序运行环境中内存资源限制较大,可以使用解释执行节约内存,反之可以使用编译来提升效率。
(2)为什么虚拟机要实现两个不同的即时编译器?
虚拟机中内置了两个即时编译器,分别为Client Compiler和Server Compiler,又称为C1和C2。
默认只使用其中的一个,至于选择哪个,取决于虚拟机会根据自身版本和宿主机器的硬件性能自动选择运行模式。用户也可以使用“-client”、“-server”进行指定。
(3)程序何时使用解释器执行?何时使用编译器执行?
虚拟机有一个分层编译策略。
第0层:程序解释执行,解释器不开启性能监控功能,可触发第1层编译
第1层:也称为C1编译,将字节码编译为本地代码,进行简单、可靠的优化,如有必要将加入性能监控的逻辑。
第2层:也称为C2编译,将字节码编译为本地代码,但是会启用一些编译耗时较长的优化,甚至会根据性能监控信息进行一些不可靠的激进优化。
(4)哪些程序代码会被编译为本地代码?如何编译为本地代码?
热点代码包括如下两类,其均把整个方法作为编译对象。
a、被多次调用的方法
b、被多次执行的循环体
热点探测是用来判断一段代码是否为热点代码,其方式有两种:
a、基于采样
b、基于计数器。HotSpot使用的是这种。它为每个方法准备了两类计数器:统计方法被调用次数的方法调用计数器和统计一个方法中循环体代码执行次数的回边计数器。
(5)如何从外部观察及时编译器的编译过程和编译结果?
可以使用 -xx:+PrintCompilation 查看哪些方法被即时编译器编译了。
优化技术有哪些?
虚拟机的即时编译器在生成代码时,采用了如下的代码优化技术。
(1)公共子表达式消除
如果一个表达式E已经计算过了,那如果再次出现E时就不会再对它进行计算。比如:
int d=(a*b)*12+c+(c+b*a)
如果这段代码交给javac编译器,则不会进行任何优化。如果交给即时编译器,会被进行如下步骤的优化:
第一步:消除公共子表达式
int d=E*12+c+(c+E)
第二步:代数化简:
int d=E*13+c*2
(2)数组边界检查消除
数组边界检查是什么?
如果有一个数组foo[],在java语言中访问数组元素foo[i]的时候,系统将会自动进行上下界的范围检查,检查i是否满足0≤i≤foo.length这个条件。
那怎么进行消除呢?
a、把运行期检查提到编译期完成。如foo[3],只要在编译期根据数据流分析来确定foo.length的值,并判断下标“3”没有越界,执行的时候就不用判断了。
b、隐式异常处理。这种思路通常用于空指针检查和算符运算中除数为零的情况。
if(foo!=null){ return foo.value; }else{ throw new NullPointException(); }
被隐式异常处理优化后,变为如下代码:
try{ return foo.value; }catch(segment_fault){ uncommon_trap(); }
除了数组边界检查消除,还有自动装箱消除、安全点消除、消除反射等。
(3)方法内联
把目标方法的代码“复制”到发起调用的方法之中,避免发生真实的方法调用。
public int add(int x1, int x2, int x3, int x4) { return add1(x1, x2) + add1(x3, x4); } public int add1(int x1, int x2) { return x1 + x2; }
运行一段时间后JVM会把add1方法去掉,并把代码翻译成:
public int add(int x1, int x2, int x3, int x4) { return x1 + x2 + x3 + x4; }
(4)逃逸分析
当一个对象在方法中被定义后,它可能被外部方法所引用,比如作为调用参数传递到其它方法中,这称为方法逃逸。同理,如果被外部线程访问到,它就称为线程逃逸。
对变量进行相应分析就叫做逃逸分析。如果能证明别的方法或线程无法通过任何途径访问到这个对象,则可以为这个变量进行一些优化。
优化的手段有栈上分配、同步消除、标量替换等。以同步消除为例,如果逃逸分析能够确定一个变量不会逃逸出线程,即无法被其它线程访问到,那对这个变量实施的同步措施就可以消除掉了。
以上内容便是关于JAVA虚拟机中JVM优化的全部介绍,更多相关问题请访问PHP中文网:JAVA视频教程
以上がJAVA 仮想マシン (JVM) の詳細な紹介 (7) - JVM の最適化の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。