【笔记】JVM找门必备——JIT新特性

本文介绍了JVM的逃逸分析及其在JIT编译器中的应用,包括栈上分配、同步消除和标量替换优化。逃逸分析用于决定对象是否在栈上分配,减少堆内存使用,并可能实现同步消除,提高程序性能。通过对JVM参数设置,可以开启或查看逃逸分析的效果。
摘要由CSDN通过智能技术生成

目录

摘要

逃逸分析

逃逸对象

未逃逸对象

JVM参数设置

栈上分配

同步消除

标量替换优化


摘要

JIT(Just-In-Time)编译器,在JDK6之后,增加了一些新的特性:

  • 栈上分配
  • 标量替换优化
  • 同步消除

换句话说,传统的对象只存在于堆中的做法得到了进一步的提升,为了程序运行的高效,对象可能会放于栈中,对单线程对单一对象的同步锁会失效。得到这一切依赖于逃逸分析(Escape Analysis)的帮助。

逃逸分析 是一种数据分析算法。通过分析对象是否逃逸出方法,来决定是否有必要进行 堆外存储或是同步消除。然而,逃逸分析技术并不是很成熟,其复杂的分析算法,也是个相对耗时的过程。

其中,栈上分配和标量替换优化算是堆外存储技术

逃逸分析

逃逸分析一种数据分析算法,基于此算法可以有效减少 Java 对象在堆内存中的分配。 Hotspot 虚拟机的编译器能够分析出一个新对象的引用范围,然后决定是否要将这个对象 分配到堆上。例如:

  • 当一个对象在方法中被定义后,它被外部方法所引用,则认为是逃逸对象
  • 当一个对象在方法中被定义后,对象只在方法内部使用,则认为是逃逸对象

逃逸对象

如下代码中的 StringBuffer 发生了逃逸,不会在栈上分配。

public StringBuffer append(String s1, String s2) { 
    StringBuffer sb = new StringBuffer(); 
    sb.append(s1); 
    sb.append(s2);
    return sb; 
} 

未逃逸对象

当一个对象在方法内创建,又没有被外界引用,此对象为未逃逸对象。例如:

public void create(int x,int y) { 
    Point p1= new Point(x,y); 
    //...
    p1=null; 
} 

JVM参数设置

在 JDK 1.7 版本之后,HotSpot 中默认就已经开启了逃逸分析。当然也可以通过:

-XX:+DoEscapeAnalysis  显式开启逃逸分析

-XX:+PrintEscapeAnalysis 查看逃逸分析的筛选结果

栈上分配

将堆分配转化为栈分配。如果一个对象在方法内创建,要使指向该对象的引用不会发生逃逸,对象可能是栈上分配的候选。

/**
* 栈上分配测试(-XX:+DoEscapeAnalysis)
* -Xmx128m -Xms128m -XX:+DoEscapeAnalysis -XX:+PrintGC 
*/
public class ObjectStackAllocationTests {
    public static void main(String[] args) throws InterruptedException { 
        long start = System.currentTimeMillis(); 
        for (int i = 0; i < 10000000; i++) { 
            alloc(); 
        } 
        long end = System.currentTimeMillis(); 
        System.out.println("花费的时间为: " + (end - start) + " ms"); // 为了方便查看堆内存中对象个数,线程 
        sleep TimeUnit.MINUTES.sleep(5); 
    }

    private static void alloc() { 
        byte[] data = new byte[10];//未发生逃逸 
    } 
} 

对如上代码运行测试时,分别开启和关闭逃逸分析,检查控制台日志的输出以及花费时 间上的不同。

同步消除

线程同步是靠牺牲性能来保证数据的正确性,程序的并发行和性能都会降低。JVM 的 JIT 编译器可以借助逃逸分析来判断同步块所使用的锁对象是否只能够被一个线程应用,进而,取消对这部分代码上加的锁。这个取消同步的过程也叫 锁消除

public class SynchronizedLockTest { 
    public void lock() { 
        Object obj= new Object(); 
        synchronized(obj) { 
        System.out.println(obj); 
        } 
    } 
} 

标量替换优化

所谓的标量(scalar)一般指的是一个具有原子性的数据,即无法再分解成更小数据的数据。

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

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

package com.java.jvm; 
/** 
* 标量替换测试 (-XX:+EliminateAllocations) 
* -Xmx128m -Xms128m -XX:+DoEscapeAnalysis -XX:+PrintGC -XX:-EliminateAllocations 
*/ 
public class ObjectScalarReplaceTests { 

    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"); 
    } 

    private static void alloc() {
        Point point = new Point(1,2); 
    } 

    static class Point {
        private int x;
        private int y;
        public Point(int x,int y){ 
            this.x=x; 
            this.y=y; 
        } 
    } 
}

对于上面代码,假如开启了标量替换,那么 alloc 方法的内容就会变为如下形式:

private static void alloc() { 
    int x=10; 
    int y=20; 
} 

alloc 方法内部的 Point 对象是一个聚合量,这个聚合量经过逃逸分析后,发现他并 没有逃逸,就被替换成两个标量了。那么标量替换有什么好处呢?就是可以大大减少堆内存 的占用。因为一旦不需要创建对象了,那么就不再需要分配堆内存了。标量替换为栈上分配 提供了很好的基础。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值