从jvm代码优化角度谈撸代码

1.前言

以编译程序执行本地代码,比解释执行更快,除虚拟机解释执行字节码额外消耗时间的原因之外,另一个很重要原因就是虚拟机设计团队几乎把对代码的所有优化措施都集中在即时编译器中。一般即时编译器生成的本地代码比javac产生的字节码更优秀。

HotSpot虚拟机在即时编译器中采用的优化技术:https://wiki.openjdk.java.net/display/HotSpot/PerformanceTacticIndex

下面谈一下jit编译优化的常见手段。

2 方法内联

编译器最总要的优化是方法内联。遵循面向对象方式编写的代码,通常会有很多get,set方法。

优化前代码:
static class B{
int value;
final int get(){
return value;
}
}

public void foo(){
y = b.get();
z = b.get();
sum = y + z;
}

内联是最重要的优化措施,原因有二。一,降低调用的开销(如建立栈帧等);二,方法内联为其他优化措施建立良好基础,方法内联膨胀后,可在更大范围采用后续优化手段。

内联后代码:
public void foo(){
y = b.value;
z = b.value;
sum = y + z;
}

第二步优化:冗余访问消除
y和z的表达式一样,所以可以不必去访问对象b,替换为z=y。如果把b.value视为表达式,则该优化也可以理解为公共表达式消除。

冗余存储消除后代码:
public void foo(){
y = b.value;
z = y;
sum = y + z;
}

第三步优化:复写传播
z与y的值一样,不必使用额外的变量,用y代替z

复写传播优化后:
public void foo(){
y = b.value;
y = y;
sum = y + y;
}

第四步优化:无用代码消除

public void foo(){
y = b.value;
sum = y + y;
}

经过优化以后,作用一样,但是省略了很多代码(从字节码,机器码指令上的差距更大),执行效率更高。

由上面的例子,我们可以看到,方法内联是很多优化手段的基础。方法是否内联,取决于方法是否够热(jvm根据内部方法判断,如是否调用频繁),以及大小。只有方法够热,并且字节码小于325字节,或者方法小于35字节时才能内联。

由此我们可以和平时撸代码的一个小经验印证起来,多写小方法,把职责完整的逻辑段抽取出来,成为独立的小方法。小方法好处有几个:
一 逻辑简单,职责单一,与设计原则的单一性对应;
二 抽出去小方法,类似的,职责上接近的,可以当成下一步抽象优化的基础;
三 小方法更容易满足内联条件。

3 逃逸分析

逃逸分析与方法内联相似,也是其他优化手段的基础。
逃逸分析的行为就是分心对象动态作用域,当一个对象不会逃逸到方法或者线程之外,也就是其他方法或者线程无法通过任何路径访问到该对象时,则可进行一些高效优化。

栈上分配:堆中分配的对象,无论是验证,整理或者清理都需耗费时间,如果确定对象不会逃逸,则可在栈上分配内存,对象所在的内存就可以随栈帧出栈而销毁。在一般应用中,不会逃逸的局部对象比例很大,栈上分配可减少垃圾回收压力。

同步消除:线程同步耗时很大,不会逃逸的对象,读写不会有竞争,相应的同步措施可以消除。

标量替换:标量是指无法再分解的数据。java虚拟机中的原始数据类型都可以称为标量。如果可以继续分解,则称为聚合量,如对象。如果把一个java对象拆散,根据程序访问的情况,将其实用到的成员变量恢复原始类型,来访问,叫做标量替换。如果逃逸分析证明一个对象不会被外部访问,并且可以拆解,那么程序真正执行的时候将很可能不创建这个对象,改为直接创建该对象的成员变量来代替。进而直接在栈上分配和读写,为后续优化打基础。

栈上存储的数据,很大概率会被虚拟机分配到物理机的高速寄存器中。

可以看到,逃逸分析也是jit优化代码的重要措施,那撸代码时有啥常识可以印证?
答案:广义的迪米特法则。控制对象之间的信息流量,流向,影响。内部实现和外部接口隔离。严格控制对象的作用域,访问权限等,使对象,方法的影响最小,不但优雅,而且更合编译器口味。(全局变量自然不满足逃逸分析)

4 再谈对象作用域

前面都在聊jit,我们回到编译方式执行程序的场景(代码不够热,解释器执行)。看看下面这段代码。

public static void main (String[] args)(){
{
byte[] a = new byte[64 * 1024 * 1024];
}
System.gc();
}

jvm 加上运行参数"-verbose:gc",查看运行日志

[GC 66846K->65888K(125632K), 0.0009397 secs]
[Full GC 65888K->65746K(125632K), 0.0051574 secs]

内存居然没被回收。改一下代码
public static void main (String[] args)(){
{
byte[] a = new byte[64 * 1024 * 1024];
}
int b = 0;
System.gc();
}

再执行一次,回收了。。。
[GC 66401K->65778K(125632K), 0.0035471 secs]
[Full GC 65778K->218K(125632K), 0.0140596 secs]

原因在于局部变量表。局部变量表是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。在第一段代码中,虽然变量离开了作用域,但是在此之后,没有其他局部变量表的读写,GC Roots一部分的局部变量表仍然保存着对它的关联。正常情况下没影响,但如果遇到一些特殊情况(对象占用内存大,此方法的栈帧长时间不能被回收,方法调用次数达不到jit编译条件),则会采用手动set为null的方式(用来代替那句b = 0,把局部变量表清空)。

然而有一个问题,解释执行和jit编译后执行是完全不同的代码,这里用来救命的 set null,在jit编译后,很大可能会当成无用代码消除,jit编译后,第一段代码可以正常释放内存。更优雅的方式应当还是严格控制对象作用域,减少对set null的依赖。

5 总结
写小方法
遵循广义迪米特法则
严格控制对象作用域

6 参考
深入理解java虚拟机
java性能权威指南

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容