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还有许多很多意思的优化,这里只是列举出了几点。当然了,你也不用太在意它,就像文中最后说的,正常写好自己的代码就好了。

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的几点性能优化相关推荐

  1. JAVA之JVM GC 机制与性能优化

    1 背景介绍 与C/C++相比,JAVA并不要求我们去人为编写代码进行内存回收和垃圾清理.JAVA提供了垃圾回收器(garbage collector)来自动检测对象的作用域),可自动把不再被使用的存 ...

  2. JVM性能优化, Part 2 ―― 编译器

    2019独角兽企业重金招聘Python工程师标准>>> ImportNew注:本文是JVM性能优化 – 第2篇 <JVM性能优化, Part 2 ―― 编译器>第一篇 & ...

  3. JVM性能优化, Part 1 - JVM简介

    原文出自:http://www.importnew.com/1774.html 众所周知,Java应用程序是运行在JVM上的,但是你对JVM有所了解么?作为这个系列文章的第一篇,本文将对经典Java虚 ...

  4. 【JVM和性能优化】2.垃圾回收器和内存分配策略

    内存回收 为什么要了解GC(Garbage Collection)和内存分配策略 1.面试需要 2.GC对应用的性能是有影响的 3.写代码有好处 那些需要GC: 共享区的都要被回收比如堆区以及方法区. ...

  5. Java 性能优化的五大技巧

    要对你的 Java 代码进行优化,需要理解 Java 不同要素之间的相互作用,以及它是如何与其运行时的操作系统进行交互的.使用下面这五个技巧和资源,开始学习如何分析和优化你的代码吧. 在我们开始之前, ...

  6. 现在写Android,性能优化太重要了!

    "为什么?为什么?我5000块买的Android手机还会卡???" 经过近十年的发展,Android技术优化日新月异,如今Android 10.0 已经发布,Android系统性能 ...

  7. zabbix学习4: 监控Java原理-zabbix性能优化-低级自动发现-zabbix api

    文章目录 20: zabbix监控java jvm原理 21: zabbix性能优化 22: zabbix低级自动发现 23: zabbix api 20: zabbix监控java jvm原理 to ...

  8. Elasticsearch-32.生产环境常用配置与上线清单 he 集群写性能优化 he 集群读性能优化

    Elasticsearch 生产环境常用配置和上线清单 Development vs.Production Mode 从ES 5开始,支持Development 和Production 两种运行模式 ...

  9. 滴滴架构师被迫离职后,只留下这份731页Java程序性能优化手册

    滴滴资深架构师深度分享Java程序性能优化的宝贵经验,从软件设计.编码和JVM等维度阐述性能优化的方法和技巧. 总览: 篇幅限制,这里就不全部展示出来了.需要获取完整版Java程序性能优化手册的小伙伴 ...

最新文章

  1. “Assign Random Colors” is not working in 3ds Max 2015
  2. GPT-2的大规模部署:AI Dungeon 2 如何支撑百万级用户
  3. android 开启一个定时线程_Android中定时执行任务的3种实现方法
  4. Hyperledger Composer评测
  5. LG P990开机黑屏,但能进入系统的解决办法
  6. java 日期类代码_java 日期时间处理类
  7. mysql 允许用户远程_Mysql 允许用户远程操作
  8. 三国演义告诉我们的60个道理
  9. 2017.4.5 假期的宿舍 思考记录
  10. 【OpenCV】矩阵掩模操作
  11. HDU5620 KK's Steel【菲波拉契数列+水题】
  12. 有长度要求的区间最大值
  13. MyEclipse Hibernate反向工程生成实体类
  14. AMD Intel 机器 Spark 性能测试
  15. 网课题库接口教程(免费)
  16. Matlab画出漂亮的三维散点图
  17. 大数据的核心价值是什么
  18. 第三方支付和聚合支付
  19. Scheme 语言概要(上)
  20. 图片信息用浏览器显示:data:image/png;base64,+图片内容

热门文章

  1. 39策略模式(Strategy Pattern)
  2. 11单件模式(Singleton Pattern)
  3. maven 公共模块依赖_「spring-boot 源码解析」spring-boot 依赖管理
  4. 【数学+编程】对函数进行抽象
  5. SparkStreaming简介 - 与第一个Spark实时计算程序,使用netcat来写数据 - wordcount
  6. 【Python CheckiO 题解】The Warriors
  7. 【HDU - 6185】Covering(矩阵快速幂优化二维dp,高斯消元,轮廓线dp打表)
  8. 【POJ - 2485 】Highways (最小生成树,Prim算法,瓶颈生成树)
  9. 【CodeForces - 1051D】Bicolorings (dp,类似状压dp)
  10. 【POJ - 1502】MPI Maelstrom(Dijkstra单源最短路--求一点到其余个点的最小值的最大值)