JVM基础

  |   0 评论   |   0 浏览

image.png

java程序---> java字节码 ---java虚拟机解释成--->机器码

机器码是CPU直接读取运行的指令。

字节码是一种中间状态,需要直译后才能成为机器码.

JVM运行模式:

  1. Client模式启动速度较快,Server模式启动较慢;
  2. 但是启动进入稳定期长期运行之后Server模式的程序运行速度比Client要快很多。
  3. 因为Server模式启动的JVM采用的是重量级的虚拟机,对程序采用了更多的优化;而Client模式
  4. 启动的JVM采用的是轻量级的虚拟机。所以Server启动慢,但稳定后速度比Client远远要快。

执行引擎: 包括 解释器、JIT编译器、垃圾回收器

JIT编译器:属于动态编译方式。 没有JIT之前,每一行Java代码需要去解释然后执行,如果代码执行次数很多(方法被频繁调用n次, client模式下n为1500, server模式下n为10000),这段代码就是热点代码,JIT会把方法编译成机器码。(JIT编译是以整个方法为单位进行编译)。

计数器来记录调用次数:

  • 方法调用计数器: 在方法对象中取存储调用次数
  • 回边计数器: 每一次循环回去,都会记录一次次数

JIT优化:

公共子表达式消除

int d = (o*q) * 27 + p + (p + q *o) ---> int d = E * 27 +p + (p + E) --> int d = E * 28 + p * 2

方法内联

将方法调用直接使用方法体重的代码进行替换,这就是方法内联,减少了方法调用过程中压栈与入栈的开销。

private int addNum(int a1, int a2, int a3,int a4) {
   return sum(a1,a2) + sum(a3,a4);
}
private int sum(int a, int b){
   return a+ b;
}
//方法内联后变为
private int addNum(int a1,int a2, int a3, int a4) {
   return a1 + a2 + a3 + a4;
}
逃逸分析

定义: 当一个对象在方法中被定义后,它可能被外部方法所引用,成为方法逃逸。

public class EscapeAnalysis { 
  //全局变量 
  public static Object object; 
  public void globalVariableEscape(){//全局变量赋值逃逸
    object = new Object(); 
  }
  
  public Object methodEscape(){ //方法返回值逃逸 
    return new Object(); 
  }
  
  public void instancePassEscape(){ //实例引用发生逃逸 
    this.speak(this); 
  }
  
  public void speak(EscapeAnalysis escapeAnalysis){ 
    System.out.println("Escape Hello"); 
  } 
}

再比如:

public static StringBuffer craeteStringBuffer(String s1, String s2) {
  StringBuffer sb = new StringBuffer(); 
  sb.append(s1); sb.append(s2); 
  return sb; 
}
//上述方法会产生方法逃逸,如果不想产生逃逸,将 return sb; 改为 return sb.toString();同时将返回值改为String;

注:

从jdk1.7开始已经默认开启逃逸分析; 可以通过JVM参数制定是否开启逃逸分析

-XX:+DoEscapeAnalysis : 表示开启逃逸分析 

-XX:-DoEscapeAnalysis : 表示关闭逃逸分析
对象的栈上分配内存

一般情况下对象的内存分配是在堆上的。但是随着JIT编译优化的发展,对象内存分配也不一定在堆上(可能一部分在堆上,一部分在栈上)。

public class EscapeAnalysisTest { 
  public static void main(String[] args) { 
    long a1 = System.currentTimeMillis(); 
    for (int i = 0; i < 1000000; i++) { 
      alloc(); 
    }
    // 查看执行时间 
    long a2 = System.currentTimeMillis(); 
    System.out.println("cost " + (a2 - a1) + " ms"); 
    // 为了方便查看堆内存中对象个数,线程sleep 
    try {
      Thread.sleep(100000); 
    } catch (InterruptedException e1) { 
      e1.printStackTrace(); 
    } 
  }
  private static void alloc() { 
    User user = new User(); 
  }
  static class User { } 
}

由于上述代码User对象不会逃逸到alloc外部,经过JIT逃逸分析后,就可以对其内存分配进行优化。

a. 指定JVM参数: 关闭逃逸分析

-Xmx4G -Xms4G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
~ jps 
2809 StackAllocTest 
2810 Jps 
~ jmap -histo 2809 
num #instances  		#bytes        class name ---------------------------------------------- 
1:      524 	 			87282184 			[I 
2: 		1000000 			16000000 			StackAllocTest$User 
3: 			6806 				2093136 			[B 
4: 			8006 				1320872 			[C 
5: 			4188 				100512 				java.lang.String 
6: 			581 				66304 				java.lang.Class

看到关闭逃逸分析后,在堆上分配了$User对象100万个。

b. 开启逃逸分析:

-Xmx4G -Xms4G -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError

会看到堆上面创建的User对象会很少,并且耗时也快很多。

标量替换
//有一个类A 
public class A{ 
  public int a=1; 
  public int b=2 
}
//方法getAB使用类A里面的a,b 
private void getAB(){ 
  A x = new A();
  x.a; 
  x.b; 
}
//JVM在编译的时候会直接编译成 
private void getAB(){ 
  a = 1; 
  b = 2; 
}//这就是标量替换
锁消除

同样基于逃逸分析,当加锁的变量不会发生逃逸,是线程私有的完全没有必要加锁。 在JIT编译时期就

可以将同步锁去掉,以减少加锁与解锁造成的资源开销。

public class TestLockEliminate { 
public static String getString(String s1, String s2) {
  StringBuffer sb = new StringBuffer(); 
  sb.append(s1); 
  sb.append(s2);
  return sb.toString(); 
 }
 public static void main(String[] args) { 
  long tsStart = System.currentTimeMillis();
  for (int i = 0; i < 10000000; i++) { 
  	getString("TestLockEliminate ", "Suffix"); 
  }
  System.out.println("一共耗费:" + (System.currentTimeMillis() - tsStart) + " ms"); 
  } 
 }

上述getString方法中的sb,不可能逃逸出方法,但是StringBuffer的append方法是有synchronized修饰的。

运行时候: (-XX:-EliminateLocks 锁消除必须运行在 -server 模式下)

-XX:+DoEscapeAnalysis -XX:+EliminateLocks   //开启锁消除
-XX:+DoEscapeAnalysis -XX:-EliminateLocks   //关闭锁消除

会发现,开启锁消除,执行时间会少。


标题:JVM基础
作者:码农路上
地址:https://wujingjian.club/articles/2021/04/19/1618828769226.html