JVM的几点性能优化

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的几点性能优化

英文原文链接



本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/448991.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IDEA配置 及 快捷键

出处&#xff1a; https://www.cnblogs.com/hero123/p/10120552.html 快捷键&#xff1a; 格式化代码 CtrlaltL 后退Ctrlalt <- 格式化代码快捷键&#xff1a;Ctrl Alt L 删除整行&#xff1a;CtrlX 实现类 ctrl alt CtrlN 查找类 CtrlShiftN 查找文件 CTRLSHIFTALTN 查找…

LeetCode Decode Ways

123123转载于:https://www.cnblogs.com/ZHONGZHENHUA/p/10854545.html

SpringBoot 之集成 Spring AOP

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 在开始之前&#xff0c;我们先把需要的jar包添加到工程里。新增Maven依赖如下&#xff1a; <dependency><groupId>org.spri…

9件事把你从消极情绪中解救出来

也许你很难相信&#xff0c;但是情绪可以通过重复形成习惯。消极情绪甚至可以变成某种嵌入你每日生活的东西。 如何将它们赶跑? 你发现你不断地埋怨世界和自己?你可以轻易地生气并且对人变得刻薄?那愤怒又是否成为你对事情本能的回应了?如果你对所述问题中的一个回答了“是…

数据库主键自增插入显示值

版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主同意不得转载。 https://blog.csdn.net/nwsuaf2009012882/article/details/32703597 SQL Server 2008 数据库主键自增插入显示值 前几天在工作的时候遇到在删除数据库中表的数据的时候。删除之后&#xff0c;又一次…

解决: This application has no explicit mapping for /error, so you are seeing this as a fallback.

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 报错如题&#xff0c;出现这个异常说明了跳转页面的url无对应的值. 原因1: Application启动类的位置不对.要将Application类放在最外侧…

Selenium自动化获取WebSocket信息

性能日志 ChromeDriver支持性能日志记录&#xff0c;您可以从中获取域“时间轴”&#xff0c;“网络”和“页面”的事件&#xff0c;以及指定跟踪类别的跟踪数据。启用性能日志 默认情况下不启用性能日志记录。因此&#xff0c;在创建新会话时&#xff0c;您必须启用它。 Desir…

零负债之人的10个习惯

无论你是已下定决心要于今年实现零负债&#xff0c;还是距离这个目标的实现有很长的路要走&#xff0c;能受到启发总是好事。 看看你认识的已经过上“无债一身轻”生活的人──朋友、家人、同事或是你认为可能与其他无负债之人具有类似品质的人。 下文为无负债之人的10个共同…

《App后台开发运维与架构实践》第3章 App后台核心技术

2019独角兽企业重金招聘Python工程师标准>>> 3.1 用户验证方案 3.1.1 使用HTTPS协议 HTTPS协议是“HTTP协议”和“SSL/TLS”的组合。SSL&#xff08;Secure Sockets Layer&#xff09;&#xff0c;即安全套接层&#xff0c;是为了解决因HTTP协议是明文而导致传输内容…

IntelliJ IDEA 配置 JDK

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 提前安装jdk&#xff0c;配置环境变量 一、配置jdk 1、依次点开File -->Project Structure&#xff0c;点击左侧标签页&#xff0c…

xml编辑无提示?这么破!

在学习testng这个单元测试框架时&#xff0c;如果咱们碰到了编辑测试套件xml&#xff0c;不提示的情况&#xff08;有提示方便咱们学习&#xff0c;并且testng的测试套件定义必须按照他的dtd文件约束来&#xff09;&#xff0c;咱们可以按照下面的步骤去解决这个问题。 1.检查t…

“云栖直播”升级为“公开课”

直播平台是面向广大开发者的视频学习平台&#xff0c;帮助广大开发者学习最新技术&#xff0c;了解最新阿里云产品以及最新技术发展趋势&#xff0c;帮助开发者们不断学习与成长。截止到2019年3月&#xff0c;直播共进行800余场&#xff0c;观看人次100万。  社区将对“云栖直…

遭银行账号诈骗最快最有效自救法

银行卡或账户诈骗案件层出不穷&#xff0c;当汇错款时该怎么做&#xff0c;切记以下方法&#xff1a; 一、当汇错款或被骗汇款后&#xff0c;最快最有效的紧急自救法&#xff1a;当你把自己的钱不小心汇到了不该汇的人卡上&#xff0c;或者被骗子忽悠而把钱汇给了骗子&#xf…

SQL 判断非空 NULL :IFNUL( ) 、COALESCE( ) 、ISNULL( ) 、NVL( )

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. mysql 支持&#xff1a; IFNULL&#xff08;&#xff09;、COALESCE&#xff08;&#xff09; 如 IFNULL(UnitsOnOrder, 0) 或者 CO…

navigator.geolocation的应用 - 将定位信息显示在百度地图上

在学习navigator.geolocation的时候&#xff0c;有一个实例是获取坐标后显示在谷歌地图上。众所周知&#xff0c;谷歌地图国内并不能直接访问&#xff0c;得用特殊手段&#xff0c;那我要测试的时候还要开着梯子挺麻烦的&#xff0c;想给别人用也得那个人能访问谷歌地图先。 地…

centos7 mysql数据库安装和配置

2019独角兽企业重金招聘Python工程师标准>>> 一、系统环境 yum update升级以后的系统版本为 [rootyl-web yl]# cat /etc/redhat-release CentOS Linux release 7.1.1503 (Core) 二、mysql安装 一般网上给出的资料都是 #yum install mysql #yum install mysql-serve…

5种聪明工作法

1、每天最多做三件事 请拿出你落落长的待办清单&#xff0c;圈出最重要的一~三件事&#xff0c;然后给自己一天的时间&#xff0c;卯足全力解决它! 你不需要因为还有很多事要做而焦虑&#xff0c;只需要专注今天、当下、以及最重要的问题。 《与成功有约》作者史蒂芬.柯维(Step…

【Quartz】Quartz概述及入门实例

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 Quartz 在开源任务调度框架中的翘首&#xff0c;它提供了强大任务调度机制&#xff0c;难能可贵的是它同时保持了使用的简单性。Quartz 允…

python中del语句

有一种方式可以从列表按照给定的索引而不是值来移除一个元素: 那就是 del 语句。 它不同于会返回一个值的 pop() 方法。 del 语句也可以用来从列表中移除切片或者清空整个python列表&#xff08;我们之前用过的方式是将一个空列表赋值给指定的切片&#xff09;。 例如: >>…

偷时间的孩子

从事临床心理工作已有十三、四年(至一九九五年)&#xff0c;真的有很多话想跟父母们敞开心扉的谈谈。 忙碌的现代社会&#xff0c;让我窥探到了许许多多的杰出角色&#xff0c;他们偷取家庭时间去换取自己的功成名就&#xff0c;他们的心随着公司的企划案四处流浪&#xff0c;孩…