`
deepinmind
  • 浏览: 451409 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
1dc14e59-7bdf-33ab-841a-02d087aed982
Java函数式编程
浏览量:41620
社区版块
存档分类
最新评论

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



原创文章转载请注明出处:http://it.deepinmind.com

英文原文链接


5
5
分享到:
评论

相关推荐

    面试必问之jvm与性能优化.docx

    ### jvm性能优化相关知识点详解 #### 一、JVM加载Class文件的原理机制 **1.1 类加载概述** Java虚拟机(JVM)在执行Java程序时,并不会一次性加载所有的类,而是采取按需加载的方式。这种方式可以有效减少内存消耗...

    实战JAVA虚拟机 JVM故障诊断与性能优化带源码

    《实战JAVA虚拟机 JVM故障诊断与性能优化》是一本深度探讨Java虚拟机(JVM)的书籍,旨在帮助开发者解决在实际工作中遇到的JVM相关问题,提升系统的性能。这本书提供了丰富的源码实例,让读者能够深入理解JVM的工作...

    实战Java虚拟机——JVM故障诊断与性能优化

    《实战Java虚拟机——JVM故障诊断与性能优化》是一本深入探讨Java开发人员和运维人员必备技能的书籍。本书作者葛一鸣以其丰富的实战经验,详细阐述了JVM(Java Virtual Machine)的工作原理,以及如何有效地进行故障...

    JVM与性能优化知识点整理.pdf

    本篇文章将围绕JVM内存区域划分、执行子系统以及与性能优化相关的知识点进行详细的阐述。 一、JVM内存区域划分 1. **程序计数器**(线程私有):每个线程都有自己的程序计数器,它记录了当前线程正在执行的字节码...

    jvm学习,面试,性能优化等等

    本篇将深入探讨JVM的学习、面试及性能优化相关知识点。 首先,JVM学习涉及多个层面,包括内存管理、类加载机制、垃圾收集、线程管理等。理解JVM内存模型至关重要,它主要分为堆内存(Heap)、方法区(Method Area)...

    JVM和性能优化学习思维笔记.rar_java

    在深入理解JVM与性能优化的过程中,我们需要关注以下几个关键知识点: 1. **JVM架构**:JVM主要由类加载器、运行时数据区、执行引擎、本地方法接口和本地库组成。每个部分都有其特定的功能,如类加载器负责加载类到...

    淘宝JVM优化实践-长仁.pdf

    4. **JVM性能优化**:通过JVM编译优化来加速垃圾回收(GC),例如,使用Intel C/C++ Compiler进行Profile guide optimization,减少JNI调用的开销。实验结果显示,与GCC相比,Intel Compiler在性能上有优势,并有助...

    JVM性能优化相关面试题21道.pdf

    在深入探讨JVM性能优化相关面试题之前,先了解Java虚拟机(JVM)中类加载过程、JVM加载Class文件的原理机制以及Java内存分配的知识点是非常有必要的。 首先,Java类加载过程共有七个步骤,这七个步骤分别是:加载、...

    面试必问之jvm与性能优化.pdf

    ### 面试必问之JVM与性能优化——关键知识点解析 #### 一、JVM加载Class文件的原理机制 在Java开发过程中,深入理解JVM如何加载Class文件至关重要,尤其是在面试时,这一知识点往往是考察的重点之一。下面将详细...

    【Java技术资料】-(机构内训资料)JVM和性能优化学习思维笔记

    【Java技术资料】-(机构内训资料)JVM和性能优化学习思维笔记是一份针对Java开发者深入理解JVM及进行性能优化的重要参考资料。这份资料可能包含了一个详细的知识地图,帮助学习者系统地掌握JVM的工作原理以及如何...

    深入JVM内核—原理、诊断与优化视频教程-2.JVM运行机制

    6. **内存溢出和性能优化**:理解内存泄漏和内存溢出的原因,如堆内存不足、栈溢出等,以及如何通过调整JVM参数进行性能优化,如设置堆大小、开启并发收集、优化新生代和老年代的比例等。 7. **异常处理与线程调度*...

    JVM优化3(Tomcat参数调优,JVM参数调优,jvm字节码,代码优化).pdf

    本篇文件内容主要介绍了JVM优化的第三部分,重点围绕Tomcat参数调优、JVM参数调优、JVM字节码优化以及代码优化等几个方面。下面是针对这些知识点的详细解释: 1. Tomcat参数调优 在Tomcat参数调优部分,首先介绍了...

    jvm性能优化

    以下是对JVM性能优化的关键知识点的详细介绍。 #### 1. 理解JVM内存模型 JVM内存主要分为以下几个部分: - **堆内存**:用于存储对象实例、数组等数据。根据垃圾回收策略的不同,堆内存又分为新生代(Young ...

    Jboss_JVM优化

    Jboss中间件下JVM参数调优配置的知识点主要包括以下几个方面: 1. JVM内存结构优化 JVM内存主要分为堆内存(heap)和非堆内存(nheap)。堆内存主要包括Eden空间、Survivor空间和Tenured空间。Eden空间用于对象的...

    eclipse性能优化 <深度理解jvm>读书笔记

    首先,Eclipse作为一款强大的Java开发工具,其性能优化主要包括以下几个方面: 1. **启动速度优化**:可以通过减少工作空间中的项目数量,禁用不必要的插件,以及使用eclipse -clean启动来提高Eclipse的启动速度。...

    4本高清中文版Java性能优化经典书籍

    在进行Java性能优化时,需要关注以下几个关键点: - **内存管理**:理解内存分配、垃圾收集机制,避免内存泄漏。 - **并发编程**:合理使用线程,避免过度竞争和死锁,利用并发库提升多核处理器利用率。 - **代码...

Global site tag (gtag.js) - Google Analytics