详解jvm中的标量替换

概述

通常在java中创建一个对象,大家都认为是在堆中创建。 在jdk6开始有逃逸分析,标量替换等技术,关于在堆中创建对象不再绝对。

关于标量替换,通过以下几点进行概述:

  1. 逃逸分析
  2. 标量替换是什么
  3. 测试标量替换

逃逸分析

逃逸分析是一种分析技术,分析对象的动态作用域,供其他优化措施提供依据。比如分析一个对象不会逃逸到方法之外或线程之外,其它优化措施(栈上分配,标量替换等)根据逃逸程度进行优化。

逃逸分析示例

public class EscapeAnalysis {
  public Person p;
  /**
   * 发生逃逸,对象被返回到方法作用域以外,被方法外部,线程外部都可以访问
   */
  public void escape(){
    p = new Person(26, "TomCoding escape");
  }

  /**
   * 不会逃逸,对象在方法内部
   */
  public String noEscape(){
    Person person = new Person(26, "TomCoding noEscape");
    return person.name;
  }
}

static class Person {
  public int age;
  public String name;

  ... // 省略构造方法
}

标量替换是什么

标量可以理解成一种不可分解的变量,如java内部的基本数据类型、引用类型等。 与之对应的聚合量是可以被拆解的,如对象。
当通过逃逸分析一个对象只会作用于方法内部,虚拟机可以通过使用标量替换来进行优化。
比如上述noEscape()方法中person对象只会在方法内部,通过标量替换技术得到如下伪码:

/**
 * 不会逃逸,对象在方法内部
 */
public String noEscape(){
  int age = 26;
  String name = "TomCoding noEscape";
  return name;
}

测试标量替换

接下来我们通过对noEscape()方法进行测试,主要测试两种场景:

  1. 不使用标量替换
  2. 使用标量替换

以下测试是在jdk8中运行(注jdk8默认是开启逃逸分析,标量替换技术的)

测试代码如下:

void testEliminateAllocationsWithNoEscape() {
  int n = 100000000;
  long start = System.currentTimeMillis();
  EscapeAnalysis escapeAnalysis = new EscapeAnalysis();
  for (int i = 0; i < n; i++) {
    // noEscape()不会发生逃逸
    escapeAnalysis.noEscape();
  }
  System.out.println("耗时:" + (System.currentTimeMillis() - start));
}
  • 不使用标量替换

将jvm参数设置如下:

-Xms5m 最小堆内存5M
-Xmx5m 最大堆内存5M
-XX:+PrintGC  打印gc日志
-XX:-EliminateAllocations 关闭标量替换优化

运行后在我本机的耗时:3006毫秒,gc发生2000多次。

  • 使用标量替换

将jvm参数设置如下:

-Xms5m 最小堆内存5M
-Xmx5m 最大堆内存5M
-XX:+PrintGC  打印gc日志
-XX:+EliminateAllocations 关闭标量替换优化

运行后在我本机的耗时:20毫秒,gc发生6次。

再来看看发生逃逸的对象使用标量替换效果

测试代码如下:

void testEliminateAllocationsWithEscape() {
  int n = 100000000;
  long start = System.currentTimeMillis();
  EscapeAnalysis escapeAnalysis = new EscapeAnalysis();
  for (int i = 0; i < n; i++) {
    // escape()发生逃逸
    escapeAnalysis.escape();
  }
  System.out.println("耗时:" + (System.currentTimeMillis() - start));
}

将jvm参数设置如下:

-Xms5m 最小堆内存5M
-Xmx5m 最大堆内存5M
-XX:+PrintGC  打印gc日志
-XX:+EliminateAllocations 关闭标量替换优化

运行后在我本机的耗时:3705毫秒,gc发生2000多次。

总结

  1. 可以看到通过逃逸分析与标量替换技术有效的减少了gc次数(减少了对象在堆中创建的数量)。
  2. 实际编码过程中避免对象逃逸情况是一种理想的情况。可以形成一种编码意识,尽量去减少对象逃逸。

思考

标量替换只是利用逃逸分析其中的一种优化措施, 还有其它优化措施吗?

以上就是详解jvm中的标量替换的详细内容,更多关于jvm 标量替换的资料请关注我们其它相关文章!

(0)

相关推荐

  • JVM运行时数据区划分原理详解

    Java内存空间 内存是非常重要的系统资源,是硬盘和cpu的中间仓库及桥梁,承载着操作系统和应用程序的实时运行.JVM内存布局规定了JAVA在运行过程中内存申请.分配.管理的策略,保证了JVM的高效稳定运行.不同的jvm对于内存的划分方式和管理机制存在着部分差异(对于Hotspot主要指方法区) (图源阿里)JDK8的元数据区+JIT编译产物 就是JDK8以前的方法区 JavaAPI中的Runtime public class Runtime extends Object Every Java

  • JVM工作原理和工作流程简述

    JAVA之所以跨平台,是因为有JVM这么一个编译和运行机器,它令对于系统的操作对于用户而言是黑盒的,使得开发人员更快速和更注重软件功能的实现.然而,也因为jvm是黑盒,所以内部和底层具有不确定性,如果用状态机来表示jvm,那么jvm就是一种现役复制不确定的状态机,因为它的状态和表现跟系统.底层.硬件等等都有关系,从而状态是不确定,如果在分布式应用中,jvm一直以来兼容性都不是很好,这就是主要原因.尽管如此,就单一的系统而言,弄清楚jvm运行的来龙去脉,对于系统的运行至关重要. 理解jvm的运行原

  • idea中安装VisualVM监控jvm的图文教程

    一.安装VisualVM插件 二.配置VisualVM 三.启动项目 会自动弹出界面 四.完成 总结 到此这篇关于idea中安装VisualVM监控jvm的图文教程的文章就介绍到这了,更多相关idea安装VisualVM内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

  • IDEA设置JVM运行参数的方法步骤

    前言 有时候我们需要在程序运行的时候对程序设置环境变量,恰巧我也遇到了这个问题,所以在此记录一下IDEA是如何设置环境变量的. 作用   -Dproperty=Value 该参数通常用于设置系统级全局变量值,如配置文件路径,保证该属性在程序中任何地方都可访问.当然,也可以通过在程序中使用System.setProperty进行设置. 注意: 1.如果-Dproperty=value的value中包含空格,可以将value使用引号引起来.例如:-Dmyname="hello world"

  • Jvm调优和SpringBoot项目优化的详细教程

    一.Jvm调优. 参考文章 1.先看一下未设置JVM参数的情况,默认情况下,没有设置任何Jvm参数. idea中安装VisualVM监控jvm的图文教程 2.设置Jvm参数. 配置参数: 关于这些设置的JVM参数是什么意思,参考Jvm调优. -XX:MetaspaceSize=128m (元空间默认大小) -XX:MaxMetaspaceSize=128m (元空间最大大小) -Xms1024m (堆最大大小) -Xmx1024m (堆默认大小) -Xmn256m (新生代大小) -Xss256

  • 深入解析JVM之内存结构及字符串常量池(推荐)

    前言 Java作为一种平台无关性的语言,其主要依靠于Java虚拟机--JVM,我们写好的代码会被编译成class文件,再由JVM进行加载.解析.执行,而JVM有统一的规范,所以我们不需要像C++那样需要程序员自己关注平台,大大方便了我们的开发.另外,能够运行在JVM上的并只有Java,只要能够编译生成合乎规范的class文件的语言都是可以跑在JVM上的.而作为一名Java开发,JVM是我们必须要学习了解的基础,也是通向高级及更高层次的必修课:但JVM的体系非常庞大,且术语非常多,所以初学者对此非

  • JVM系列之String.intern的性能解析

    String对象有个特殊的StringTable字符串常量池,为了减少Heap中生成的字符串的数量,推荐尽量直接使用String Table中的字符串常量池中的元素. 那么String.intern的性能怎么样呢?我们一起来看一下. String.intern和G1字符串去重的区别 之前我们提到了,String.intern方法会返回字符串常量池中的字符串对象的引用. 而G1垃圾回收器的字符串去重的功能其实和String.intern有点不一样,G1是让两个字符串的底层指向同一个byte[]数组

  • 浅析JVM垃圾回收的过程

    JVM垃圾回收的算法很多,但是不管是哪种算法,在进行GC时大致的流程都是差不多的,主要有以下3个过程: 1. 枚举根节点 这个过程主要是找到所有的GC Roots对象,这些对象一般发生在JVM虚拟机栈栈帧.常量池中的静态对象.方法区中静态类属性引用.本地方法栈中引用的对象.这个过程会发生STW,所有的线程均运行到安全区域(Safe Region)才开始执行. 通常有两种算法: 引用计数法:每个对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就+1:当引用失效时,计数器值就-1:任何时刻

  • 通俗讲解JVM的类加载机制

    前言 我们很多小伙伴平时都是做JAVA开发的,那么作为一名合格的工程师,你是否有仔细的思考过JVM的运行原理呢. 如果懂得了JVM的运行原理和内存模型,像是一些JVM调优.垃圾回收机制等等的问题我们才能有一个更清晰的概念. 为了走进JVM,深入了解底层,王子打算写一个JVM的专题,留下自己对JVM探索的足迹,同时也希望能帮到小伙伴们更好的理解JVM. 那我们开始吧. JAVA代码的运行流程 首先我们就来聊一聊JAVA代码是怎么运行起来的,这部分比较基础相信大家都知道,就当成是个复习吧. 我们编写

  • 详解jvm中的标量替换

    概述 通常在java中创建一个对象,大家都认为是在堆中创建. 在jdk6开始有逃逸分析,标量替换等技术,关于在堆中创建对象不再绝对. 关于标量替换,通过以下几点进行概述: 逃逸分析 标量替换是什么 测试标量替换 逃逸分析 逃逸分析是一种分析技术,分析对象的动态作用域,供其他优化措施提供依据.比如分析一个对象不会逃逸到方法之外或线程之外,其它优化措施(栈上分配,标量替换等)根据逃逸程度进行优化. 逃逸分析示例 public class EscapeAnalysis { public Person

  • 详解Jvm中时区设置方式

    这篇文章memo一下Jvm中关于时区设定的基础操作. Java的时区设定 这里列出如下三种方式 方式 说明 TimeZone.setDefault方式 通过java的utils下的TimeZone进行动态设定 user.timezone传递方式 运行时通过传递-Duser.timezone=xxx进行设定 TZ环境变量方式 通过export的TZ环境变量进行设定 TimeZone.setDefault方式 Sample代码如下: sh-4.2# cat TestTimeZone.java imp

  • 详解JVM中的GC调优

    那些GC的默认值 其实GC或者说JVM的参数非常非常的多,有控制内存使用的: 有控制JIT的: 有控制分代比例的,也有控制GC并发的: 当然,大部分的参数其实并不需要我们自行去调整,JVM会很好的动态帮我们设置这些变量的值. 如果我们不去设置这些值,那么对GC性能比较有影响的参数和他们的默认值有哪些呢? GC的选择 我们知道JVM中的GC有很多种,不同的GC选择对java程序的性能影响还是比较大的. 在JDK9之后,G1已经是默认的垃圾回收器了. 我们看一下G1的调优参数. G1是基于分代技术的

  • 详解JVM中的本机内存跟踪

    1.概述 有没有想过为什么Java应用程序通过众所周知的-Xms和-Xmx调优标志消耗的内存比指定数量多得多?出于各种原因和可能的优化,JVM可以分配额外的本机内存.这些额外的分配最终会使消耗的内存超出-Xmx限制. 在本教程中,我们将列举JVM中的一些常见内存分配源,以及它们的大小调整标志,然后学习如何使用本机内存跟踪监视它们. 2.原生分配 堆通常是Java应用程序中最大的内存使用者,但还有其他人.除了堆之外,JVM还从本机内存中分配出一个相当大的块来维护类的元数据,应用程序代码,JIT生成

  • 详解JVM 中的StringTable

    是什么 字符串常量池是 JVM中的一个重要结构,用于存储JVM运行时产生的字符串.在JDK7之前在方法区中,存储的是字符串常量.而字符串常量池在 JDK7开始移入堆中,随之而来的是除了存储字符串常量外,还可以存储字符串引用(因为在堆中,引用堆中的字符串常量很方便,所以可以存储引用).这使得很多字符串的操作在 JDK7中和在之前的版本中执行是不同的结果.这也是为什么字符串相关的问题是如此具有迷惑性的原因之一. 底层 String:在 JDK9之前,String底层是使用 char数组来存储字符串数

  • 详解jvm对象的创建和分配

    对象的创建 创建方式 1. new 关键字直接创建. new ObjectName(). 2.通过 Class 反射对象的 newInstance() 方法.ObjectName  obj  =  ObjectName.class.newInstance(). 3.通过 Class 反射对象获取 Constructor 类,再调用其 newInstance() 方法. ObjectName obj = ObjectName.class.getConstructor.newInstance().

  • 详解Java中的悲观锁与乐观锁

    一.悲观锁 悲观锁顾名思义是从悲观的角度去思考问题,解决问题.它总是会假设当前情况是最坏的情况,在每次去拿数据的时候,都会认为数据会被别人改变,因此在每次进行拿数据操作的时候都会加锁,如此一来,如果此时有别人也来拿这个数据的时候就会阻塞知道它拿到锁.在Java中,Synchronized和ReentrantLock等独占锁的实现机制就是基于悲观锁思想.在数据库中也经常用到这种锁机制,如行锁,表锁,读写锁等,都是在操作之前先上锁,保证共享资源只能给一个操作(一个线程)使用. 由于悲观锁的频繁加锁,

  • 详解JVM系列之对象的锁状态和同步

    java对象头 Java的锁状态其实可以分为三种,分别是偏向锁,轻量级锁和重量级锁. 在Java HotSpot VM中,每个对象前面都有一个class指针和一个Mark Word. Mark Word存储了哈希值以及分代年龄和标记位等,通过这些值的变化,JVM可以实现对java对象的不同程度的锁定. 还记得我们之前分享java对象的那张图吗? javaObject对象的对象头大小根据你使用的是32位还是64位的虚拟机的不同,稍有变化.这里我们使用的是64位的虚拟机为例. Object的对象头,

  • 详解Java中String类的各种用法

    目录 一.创建字符串 二.字符.字节与字符串的转换 1.字符与字符串的转换 2.字节与字符串的转换 三.字符串的比较 1.字符串常量池 2.字符串内容比较 四.字符串查找 五.字符串替换 六.字符串拆分 七.字符串截取 八.String类中其它的常用方法 九.StringBuffer 和 StringBuilder 1.StringBuilder与StringBuffer的区别 2.StringBuilder与StringBuffer常用的方法 十.对字符串引用的理解 一.创建字符串 创建字符串

  • 详解Spring中的Environment外部化配置管理

    目录 profiles ProfileService 声明一个配置类 定义测试方法 profiles总结 Properties environment的应用 指定profile属性 @Value注解的使用 SpringEnvironment原理设计 Environment的中文意思是环境,它表示整个spring应用运行时的环境信息,它包含两个关键因素 profiles properties profiles profiles这个概念相信大家都已经理解了,最常见的就是不同环境下,决定当前sprin

随机推荐