JVM的几点性能优化

标签: jvm 性能优化 | 发表时间:2014-03-28 16:47 | 作者:
出处:http://it.deepinmind.com

HotSpot,家喻户晓的JVM,我们的Java和Scala程序就运行在它上面。年复一年,一次又一次的迭代,经过无数工程师的不断优化,现在它的代码执行的速度和效率已经逼近本地编译的代码了。

它的核心是一个JIT(Just-In-Time)编译器。JIT只有一个目的,就是为了提升你代码的执行速度,这也是HotSpot能如此流行和成功的重要因素。

JIT编译器都做了什么?

你的代码在执行的时候,JVM会收集它运行的相关数据。一旦收集到了足够的数据,证明某个方法是热点(默认是1万次调用),JIT就会介入进来,将“运行缓慢的”平台独立的的字节码转化成本地编译的,优化瘦身后的版本。

有些优化是显而易见的:比如简单方法内联,删除无用代码,将库函数调用替换成本地方法等。不过JIT编译的威力远不止此。下面列举了它的一些非常有意思的优化:

分而治之

你是不是经常会这样写代码:

   StringBuilder sb = new StringBuilder("Ingredients: ");
 
for (int i = 0; i < ingredients.length; i++) {
    if (i > 0) {
        sb.append(", ");
    }
    sb.append(ingredients[i]);
}
 
return sb.toString(); 

或者这样:

   boolean nemoFound = false;
 
for (int i = 0; i < fish.length; i++) {
    String curFish = fish[i];
     
    if (!nemoFound) {
        if (curFish.equals("Nemo")) {
            System.out.println("Nemo! There you are!");
            nemoFound = true;
            continue;
        }
    }
     
    if (nemoFound) {
        System.out.println("We already found Nemo!");
    } else {
        System.out.println("We still haven't found Nemo : (");
    }
} 

这两个例子的共同之处是,循环体里先是处理这个事情,过一段时间又处理另外一件。编译器可以识别出这些情况,它可以将循环拆分成不同的分支,或者将几次迭代单独剥离。

我们来说下第一个例子。if(i>0)第一次的时候是false,后面就一直是true。为什么要每次都判断这个呢?编译器会对它进行优化,就好像你是这样写的一样:

   StringBuilder sb = new StringBuilder("Ingredients: "); 
if (ingredients.length > 0) {
    sb.append(ingredients[0]);
    for (int i = 1; i < ingredients.length; i++) {
        sb.append(", ");
        sb.append(ingredients[i]);
    }
}
 
return sb.toString();

这样写的话,多余的if(i > 0)被去掉了,尽管也带来了一些代码重复(两处append),不过性能上得到了提升。

边界条件优化

检查空指针是很常见的一个操作。有时候null是一个有效的值(比如,表明缺少某个值,或者出现错误),有时候检查空指针是为了代码能正常运行。

有些检查是永远不会失败的(在这里null代表失败)。这里有一个典型的场景:

   public static String l33tify(String phrase) {
if (phrase == null) {
throw new IllegalArgumentException("phrase must not be null");
}
return phrase.replace('e', '3');
}

如果你代码写得好的话,没有传null值给l33tify方法,这个判断永远不会失败。

在多次执行这段代码并且一直没有进入到if语句之后,JIT编译器会认为这个检查很多可能是多余的。然后它会重新编译这个方法,把这个检查去掉,最后代码看起来就像是这样的:

   public static String l33tify(String phrase) {
return phrase.replace('e', '3');
}

这能显著的提升性能,而且在很多时候这么优化是没有问题的。

那万一这个乐观的假设实际上是错了呢?

JVM现在执行的已经是本地代码了,空引用可不会引起NullPointerException,而是真正的严重的内存访问冲突,JVM是个低级生物,它会去处理这个段错误,然后恢复执行没有优化过的代码——这个编译器可再也不敢认为它是多余的了:它会重新编译代码,这下空指针的检查又回来了。

虚方法内联

JVM的JIT编译器和其它静态编译器的最大不同就是,JIT编译器有运行时的动态数据,它可以基于这些数据进行决策。

方法内联是编译器一个常见的优化,编译器将方法调用替换成实际调用的代码,以避免一次调用的开销。不过当碰到虚方法调用(动态分发)的话情况就需要点小技巧了。

先看下这段代码 :

   public class Main {
public static void perform(Song s) {
s.sing();
}
}

public interface Song { void sing(); }

public class GangnamStyle implements Song {
@Override
public void sing() {
System.out.println("Oppan gangnam style!");
}
}

public class Baby implements Song {
@Override
public void sing() {
System.out.println("And I was like baby, baby, baby, oh");
}
}

perform方法可能会被调用了无数次,每次都会调用sing方法。方法调用的开销当然是很大的,尤其像这种,因为它需要根据运行时s的类型来动态选择具体执行的代码。在这里,方法内联看真来像是遥不可及的梦想,对吧?

当然不是了。在多次执行perform方法后,编译器会根据它收集的数据发现,95%的调用对象都是GangnamStyle实例。这样的话,JIT编译器会很乐观将虚方法的调用优化掉。也就是说,编译器会直接生成本地代码 ,对应的Java实现大概是这样的:

   public static void perform(Song s) {
if (s fastnativeinstanceof GangnamStyle) {
System.out.println("Oppan gangnam style!");
} else {
s.sing();
}
}

由于这个优化取决于运行时信息,它可以优化掉大部分的sing方法调用,尽管这个方法是多态的。

JIT编译器还有很多很有意思的技巧,这只是介绍了其中的几点,让你能感觉到我们代码在执行的时候,JVM在底层都做了些什么优化。

我能帮助JIT做些什么优化吗

JIT编译器是针对一般人的编译器;它是用来优化正常写出的代码的,它会去分析日常标准写法中的一些模式。不要刻意写代码去帮助JIT编译器进行优化就是对它最好的帮助 ——就正常写你自己的代码就好了。

译注:JIT还有许多很多意思的优化,这里只是列举出了几点。当然了,你也不用太在意它,就像文中最后说的,正常写好自己的代码就好了。

原创文章转载请注明出处: JVM的几点性能优化

英文原文链接

相关 [jvm 性能优化] 推荐:

java 8 JVM性能优化

- - Java - 编程语言 - ITeye博客
转自:http://qindongliang.iteye.com/blog/2199633. jvm java 垃圾回收 . JVM是JAVA世界的核心,了解它有助于我们更好调试,调优和开发程序,最近散仙在看JAVA特种兵一书,看完觉得,作者写的内容还是挺不错,大家感兴趣的,也可以购买本温故而知新下.

JVM的几点性能优化

- - Java译站
HotSpot,家喻户晓的JVM,我们的Java和Scala程序就运行在它上面. 年复一年,一次又一次的迭代,经过无数工程师的不断优化,现在它的代码执行的速度和效率已经逼近本地编译的代码了. 它的核心是一个JIT(Just-In-Time)编译器. JIT只有一个目的,就是为了提升你代码的执行速度,这也是HotSpot能如此流行和成功的重要因素.

JVM性能优化, Part 5:Java的伸缩性

- - 并发编程网 - ifeve.com
JVM性能优化系列文章由Eva Andearsson在javaworld上发表共计5篇文章, ImportNew上有前4篇译文. 本文(第5篇)由 吴杰翻译自: javaworld . 很多程序员在解决JVM性能问题的时候,花开了很多时间去调优应用程序级别的性能瓶颈,当你读完这本系列文章之后你会发现我可能更加系统地看待这类的问题.

Tomcat7调优及JVM性能优化for Linux环境

- - 互联网 - ITeye博客
该优化针对Linux X86_X64环境. Tomcat优化其实就是对server.xml优化(开户线程池,调整http connector参数). 搜索【

JVM 数据存储介绍及性能优化

- - 企业架构 - ITeye博客
转自http://www.ibm.com/developerworks/cn/java/j-lo-JVM-Optimize/index.html. Java 虚拟机内存模型是 Java 程序运行的基础. 为了能使 Java 应用程序正常运行,JVM 虚拟机将其内存数据分为程序计数器、虚拟机栈、本地方法栈、Java 堆和方法区等部分.

MySQL性能优化

- sun - IT程序员面试网
在笔试面试中,尤其是像百度,淘宝这些数据量非常大,而且用LAMP架构的公司,数据库优化方面就显得特别重要了. 此外,除了数据库索引之外,在LAMP结果如此流行的今天,数据库(尤其是MySQL)性能优化也是海量数据处理的一个热点. 下面就结合自己的经验,聊一聊MySQL数据库优化的几个方面. 首先,在数据库设计的时候,要能够充分的利用索引带来的性能提升,至于如何建立索引,建立什么样的索引,在哪些字段上建立索引,上面已经讲的很清楚了,这里不在赘述.

Hebernate 性能优化

- - 企业架构 - ITeye博客
文章分为十三个小块儿对Hibernate性能优化技巧进行总结性分析,分析如下:. 一、在处理大数据量时,会有大量的数据缓冲保存在Session的一级缓存中,这缓存大太时会严重显示性能,所以在使用Hibernate处理大数 据量的,可以使用session. clear()或者session. evict(Object) 在处理过程中,清除全部的缓存或者清除某个对象.

Hbase 性能优化

- - CSDN博客云计算推荐文章
因 官方Book Performance Tuning部分章节没有按配置项进行索引,不能达到快速查阅的效果. 所以我以配置项驱动,重新整理了原文,并补充一些自己的理解,如有错误,欢迎指正. 默认值:3分钟(180000ms). 说明:RegionServer与Zookeeper间的连接超时时间.

JavaScript性能优化

- - ITeye博客
互联网泡沫让投资者长了记性:态度更加谨慎.         如今主流浏览器都在比拼JavaScript引擎的执行速度,但最终都会达到一个理论极限,即无限接近编译后程序执行速度. 这种情况下决定程序速度的另一个重要因素就是代码本身. 在这里我们会分门别类的介绍JavaScript性能优化的技巧,并提供相应的测试用例,供大家在自己使用的浏览器上验证, 同时会对特定的JavaScript背景知识做一定的介绍.

Mysql性能优化

- - 数据库 - ITeye博客
MySQL性能优化.   性能优化是通过某些有效的方法来提高MySQL的运行速度,减少占用的磁盘空间. 性能优化包含很多方面,例如优化查询速度,优化更新速度和优化MySQL服务器等.   数据库管理人员可以使用SHOW STATUS语句来查询MySQL数据库的性能. 语法:SHOW STATUE LIKE ‘value’;其中value参数是常用的几个统计参数.