代码优化是一个涉及面很广的“工程”,但是今天呢,本姑娘主要给大家分享基于逃逸分析,如何给代码做优化。那么逃逸分析是什么呢?我前面的文章也仔细的讲解过了,这里就不过多的赘述了。有不明白逃逸分析的可以先看我的这篇文章:欧尼酱讲JVM(14)——堆

目录

规避逃逸,优化代码

栈上分配

什么是栈上分配

常见的栈上分配的场景

通过对比体验栈上分配的好处

同步省略

什么是同步省略

通过对比体会省略前后的不同

标量替换

什么是标量替换

代码演示是如何替换的

标量替换参数设置

实战演示标量替换前后的性能对比

逃逸分析小结

规避逃逸,优化代码

规避逃逸后,编译器可以对代码做以下优化

栈上分配:将堆分配转化为栈分配。如果一个对象在子程序中被分配,要指向该对象的指针永远不会逃逸,对象可能是栈分配的候选,而不是堆分配

同步省略:如果一个对象被发现只能从一个线程被访问到,那么对于这个对象的操作可以不考虑同步。

分离对象或标量替换:有的对象可能不需要作为一个连续的内存结构存在也可以被访问到,那么对象的部分(或全部)可以不存储在内存,而是存储在CPU寄存器中。

以上都是编译器做的,我们需要做的是尽量让对象不逃逸出方法,那么要如何做呢?请听小编为大家娓娓道来。

栈上分配

什么是栈上分配

JIT编译器在编译期间根据逃逸分析的结果,发现如果一个对象并没有逃逸出方法的话(也就是说这个对象没有被return出去),就可能被优化成栈上分配。分配完成之后,继续在调用栈内执行,最后线程结束,栈空间被回收,局部变量对象也被回收。这样就无须进行垃圾回收了。

通俗点讲,对象没有被return出去,那么在创建对象的时候,对象的实例就不会被分配到堆空间,而是直接分配在栈中。我们知道,几乎所有对象的实例是都被分配到堆上的,这样的话,当这个对象没有被指针指向的时候,而且堆空间不足的时候,就会进行GC,这时候就需要STW,如果对象被频繁的创建在堆中,那么就需要频繁的GC,这种频繁的STW,无疑会降低性能。而栈上分配就是为了减少GC次数,提高性能。

常见的栈上分配的场景

在本仙女的其他博客中也已经说明了,分别是:

给成员变量复制

方法返回值

实例引用传递

通过对比体验栈上分配的好处

如果我只是这样干巴巴的讲,你们也只是这样干巴巴的听,这样的话,我怕噎着你们了,所以本姑娘决定润色一下,来点代码,实践一下。哈哈,本仙女还是很贴心的吧,废话少说,我要三连(点赞+收藏+关注)!

未发生逃逸的情况:

首先需要把参数设置一下,设置为不开启逃逸分析,这样的话对象实例就会全部在堆中创建。参数设置如下:

public class EscapeAnalysis {

public static void main(String[] args) {

long start = System.currentTimeMillis();

for (int i = 0;i< 10000000;i++){

alloc();

}

long end = System.currentTimeMillis();

System.out.println("花费的时间为:" + (end - start) + "ms");

try {

Thread.sleep(1000000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

private static void alloc() {

User user = new User();//未发生逃逸

}

static class User{

}

}

执行时间如下:

可以从JVisualVM上看到,创建的实例数是10万个:

发生逃逸的情况:

首先设置参数,开启逃逸分析:

还是上面那段代码,我们看下执行时间:

哦买噶,漂亮,7ms, 仅仅是7ms,我们看到开启逃逸分析的选手想开了挂一样的快,仅仅7ms就结束了这一次的巅峰对决,果然不出我门所望,开启逃逸分析的选手以7ms的成绩获得了这次比赛的冠军!那么创建的实例数是多少呢,让我门拭目以待:

同步省略

什么是同步省略

要想了解什么是同步省略,就要先了解什么是线程同步:

线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作而是处于等待状态,直到该线程完成操作, 其他线程才能对该内存地址进行操作,这一过程就叫做线程同步。

线程同步的代价是相当高的,同步的后果是降低并发性和性能。

同步省略:在动态编译同步块的时候,JIT编译器可以借助逃逸分析来判断同步块所使用的锁对象是否只能够被一个线程访问,而没有发布到其他线程。如果没有,那么JIT编译器在编译这个同步块的时候就会取消对这部分代码的同步。这样就能大大提高并发性和性能。这个取消同步的过程就叫做同步省略,也叫锁消除。

通过对比体会省略前后的不同

同步省略前:

public class SynchronizedTest {

public void f(){

Object find = new Object();

synchronized (find){

System.out.println(find);

}

}

}

代码中对find这个对象进行了加锁,但是find对象的生命周期只在f()方法中,并不会被其他线程所访问,所以在JIT编译阶段就会被自动优化掉,优化成:

同步省略后:

public class SynchronizedTest {

public void f(){

Object find = new Object();

System.out.println(find);

}

}

标量替换

什么是标量替换

标量(Scalar):是指无法再分解成更小的数据的数据。Java中的原始数据类型就是标量。

聚合量(Aggregate):相对的,那些还可以分解的数据叫做聚合量,Java中的对象就是聚合量,因为它可以分解为其他的聚合量和标量。

标量替换:在JIT阶段,如果经过逃逸分析,发现一个对象不会被外界访问的话,那么经过JIT优化,就会把这个对象拆解成若干个成员变量(拆解后的成员变量必须是对象中包括的成员变量)来代替,这个过程就是标量替换。

代码演示是如何替换的

标量替换前:

public static void main(String[] args) {

find();

}

private static void find(){

Point point = new Point(1,2);

System.out.println("point.x = " + point.x + ";point.y = " + point.y);

}

public class Point {

private int x;

private int y;

}

标量替换后:

private static void find(){

int x = 1;

int y = 2;

System.out.println("point.x = " + x + ";point.y = " + y);

}

对标量替换过程的分析:

可以看到,Point这个聚合量经过逃逸分析后,发现他并没有逃逸,就被替换成两个聚合量了。那么标量替换有什么好处呢?就是可以大大减少堆内存的占用。因为一旦不需要创建对象了,那么就不再需要分配内存了。则1和2就放到了栈针中。大大减少了堆空间的占用,也减少了GC。

标量替换为栈上分配提供了很好的基础。

标量替换参数设置

那么如何让JIT编译器自动为我们的程序进行标量替换呢?

-XX:+EliminateAllocations

设置以上参数打开标量替换,允许将对象打散分配到栈上。

实战演示标量替换前后的性能对比

不开启标量替换:

-Xms100m -Xmx100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations

public class ScalarReplace {

public static class User{

public int id;

public String name;

}

public static void alloc(){

User u = new User();

u.id = 5;

u.name = "清酒欧尼酱";

}

public static void main(String[] args) {

long start = System.currentTimeMillis();

for (int i = 0; i < 10000000; i++){

alloc();

}

long end = System.currentTimeMillis();

System.out.println("花费的时间为:" + (end - start) + "ms");

}

}

我们可以看到发生了很多次GC

开启标量替换:

-Xms100m -Xmx100m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:+EliminateAllocations

运行上面的代码,可以看到:

没有发生GC,并且时间只有4ms。

逃逸分析小结

关于逃逸分析的论文在1999年就已经发表了,但是指导JDK1.6才有实现,并且这项技术到如今也不是十分成熟。期根本原因就是无法保证逃逸分析的性能消耗一定能高于他的消耗,虽然经过逃逸分析可以做标量替换、栈上分配和锁消除。但是逃逸分析自身也是需要进行一系列复杂的分析的,这其实也是一个相对耗时的过程。

一个极端的例子,就是经过逃逸分析之后,发现没有一个对象是不逃逸的,就这个逃逸分析的过程就白白浪费掉了。

虽然 这项技术不是很成熟,但是它也是即时编译优化技术中一个十分重要的手段。

注意到有一些观点,认为通过逃逸分析,JVM会在栈上分配那些不会逃逸的对象,这在理论上是可行的,但是取决于JVM设计者的选择,据我所知,Oracle Hotspot JVM中并未这么做,这一点在逃逸分析相关文档里已经说明,所以可以明确所有的对象实例都是创建在堆上的。

目前很多书籍还是基于JDK1.7以前的版本,JDK已经发生了很大变化,intern字符串的缓存和静态变量曾经都被分配在永久代上,而永久代已经被元数据取代,但是intern字符串缓存和静态变量并不是被转移到元数据区,而是直接在堆上分配,所以这一点同样符合前面一点的结论:对象实例都是分配在堆上的。

java的标量和聚合量_欧尼酱讲JVM(16)——如何基于逃逸分析进行代码优化相关推荐

  1. java的标量和聚合量_第5节:Java基础 - 必知必会(下)

    第5节:Java基础 - 必知必会(下) 本小节是Java基础篇章的第三小节,主要讲述Java中的Exception与Error,JIT编译器以及值传递与引用传递的知识点. 一.Java中的Excep ...

  2. 欧尼酱讲JVM(01)——整体概览(导航)

    从业这么久了,输入了很多东西,趁我还算不老,趁我还有精力,我觉得把我的知识和经验整理一番,输出出来. 那么首先就从JVM开始吧.我将从以下几个方面讲解JVM: 欧尼酱讲JVM(02)--类的加载过程 ...

  3. 欧尼酱讲JVM(17)——方法区详解有图有真相

    目录 位置图解 方法区的理解 方法区在哪里 方法区的基本理解 HotSpot中方法区的理解(演进过程) 方法区的内部结构 图解方法区内部结构 运行时常量池 class文件中常量池的理解 为什么需要常量 ...

  4. 欧尼酱讲JVM(22)——分代收集算法

    目录 分代收集算法 HotSpot中的分代收集 年轻代 老年代 没有一种最好的算法吗?没有,没有最好只有最适合.具体问题具体分析! 上一篇文章<欧尼酱讲JVM(21)--垃圾回收相关算法> ...

  5. 欧尼酱讲JVM(19)——执行引擎

    目录 执行引擎概述 什么是执行引擎 位置图解 执行引擎的工作过程 Java程序的编译和解释运行的理解 Java代码编译和执行的过程 两个问题 什么是执行器,什么是JIT编译器 为什么说Java是半编译 ...

  6. java的标量和聚合量_JVM 角度看代码优化

    从JVM角度看,有这几种优化手段: 栈上分配: 把对上分配对象空间的行为转化成栈上分配,减少YGC,提供性能 同步省略 同步代码块锁消除 标量替换 为栈上分配提供了基础,和栈上分配时搭配做的 这几个优 ...

  7. 欧尼酱讲JVM(14)——堆

    目录 位置图解 堆的简介 堆空间中关于对象创建和GC 堆的细分内存结构 堆空间大小设置和查看 查看设置的参数 OOM异常说明与举例 新生代与老年代相关参数设置 图解对象分配的一般过程 对象分配过程概述 ...

  8. 欧尼酱讲JVM(04)——运行时数据区简介

    运行时数据区概括 Java虚拟机在执行Java程序过程中会把它所管理的内存划分为若干个不同的数据区域.这些区域有各自的用途,以及创建和销毁的时间,有的区域随着虚拟机进程的启动而一直存在,有些区域则是依 ...

  9. 欧尼酱讲JVM(20)——了解垃圾回收

    目录 垃圾回收相关 什么是垃圾 什么是GC 关于垃圾收集的三个经典问题 为什么需要GC 了解早期垃圾回收行为 Java的垃圾回收机制 担忧 应该关心哪些区域的回收 写在最后 垃圾回收相关 什么是垃圾 ...

最新文章

  1. python爬网页源码_python爬虫爬取网页的内容和网页源码不同?
  2. 利用存储过程得到某一指定的表与其它的表之间的外键关系SQL Server2000
  3. IIS设定 CORS 跨域请求(跨域)
  4. python中重要的模块asyncio
  5. 【论文导读】DLP-KDD2019|BST---使用Transformer进行序列推荐
  6. 华为服务器提示错误信息,服务器错误日志
  7. 【Linux】一步一步学Linux——ssh-agent命令(181)
  8. 10.2.0.5启动enterprise manager
  9. 第3步 (请先看第2步再看第3步) 新建完spring+springmvc+mybatis项目 需要推送gitee仓库进行管理 巨详细
  10. IT餐馆—第一回 前言
  11. php面向对象三大特性——继承
  12. vue的登陆验证及返回登录前页面实现
  13. 电脑使用技巧(禁用微软 Windows 10 易升)
  14. [Luogu4173/BZOJ4259] 残缺的字符串
  15. Lcd ST7789S寄存器初始化配置
  16. 22-05-21 西安 javaweb(07) HttpServletRequest和HttpServletResponse、转发与重定向、web应用的路径问题、解决中文乱码问题
  17. 查看连接过的WIFI密码方法汇总
  18. TreeMap、二叉树
  19. CnOpenData中国外观设计专利基本信息数据
  20. 对话情绪识别 API数据接口

热门文章

  1. 轴承_常用硬件的种类以及选用_day16
  2. CW2015电量计驱动分析
  3. 本地使用Maven编译报错: Unknown lifecycle phase “ompile“解决办法
  4. Debian10中使用deb包安装WineHQ(鬼畜依赖解决)
  5. 2021年G3锅炉水处理考试总结及G3锅炉水处理复审考试
  6. AE基础教程(6)——第6章 显示通道,分辨率解析
  7. 后李健熙时代的三星,将迎来怎样变局?
  8. 三星电子 CEO 敲定,三人联席架构继续,李尚勋将任董事会主席
  9. IDEA使Rebar编译Erlang项目
  10. QDateTimeEdit 用法总结