3. 编译期处理
4. 类加载阶段
5. 类加载器
6. 运行期优化
3. 编译期处理
所谓的 语法糖 ,其实就是指 java 编译器把 *.java 源码编译为 *.class 字节码的过程中,自动生成和转换的一些代码,主要是为了减轻程序员的负担,算是 java 编译器给我们的一个额外福利(给糖吃嘛)
注意,以下代码的分析,借助了 javap 工具,idea 的反编译功能,idea 插件 jclasslib 等工具。另外,编译器转换的结果直接就是 class 字节码,只是为了便于阅读,给出了 几乎等价 的 java 源码方式,并不是编译器还会转换出中间的 java 源码,切记。
3.1 默认构造器
3.2 自动拆装箱
3.3 泛型集合取值
ParameterizedType
泛型的参数类型
输出:
3.4 可变参数
注意 如果调用了 foo() 则等价代码为 foo(new String[]{}) ,创建了一个空的数组,而不会传递 null 进去
3.5 foreach 循环
注意 foreach 循环写法,能够配合数组,以及所有实现了 Iterable 接口的集合类一起使用,其中 Iterable
用来获取集合的迭代器( Iterator )
3.6 switch 字符串
3.7 switch 枚举
转换后代码:
3.8 枚举类
3.9 try-with-resources
会被转换为:
为什么要设计一个 addSuppressed(Throwable e) (添加被压制异常)的方法呢?是为了防止异常信息的丢失(想想 try-with-resources 生成的 fianlly 中如果抛出了异常):
3.10 方法重写时的桥接方法
3.11 匿名内部类
匿名内部类接收了一个final变量:
4. 类加载阶段
4.1 加载
- 将类的字节码载入方法区中,内部采用 C++ 的 instanceKlass 描述 java 类,它的重要 field 有:
- _java_mirror 即 java 的类镜像,例如对 String 来说,就是 String.class,作用是把 klass 暴露给 java 使用
- _super 即父类
- _fields 即成员变量
- _methods 即方法
- _constants 即常量池
- _class_loader 即类加载器
- _vtable 虚方法表 -介绍多态介绍过
- _itable 接口方法表
- 如果这个类还有父类没有加载,先加载父类
- 加载和链接可能是交替运行的
Class对象在堆中,每个对象的对象头有16个字节,其中8个字节对应绑定Class内存地址,如果你想访问Class信息,它通过对象头找到堆中的Class对象,进而找到元空间的 instanceKlass 的内存地址,当我们调用一些方法,属性时,它都是从元空间获取到这些Filed,Method 具体信息的。
4.2 链接
验证
准备
- 为 static 变量分配空间,设置默认值 jdk8中static变量存储在堆中
- static 变量在 JDK 7 之前存储于 instanceKlass 末尾,从 JDK 7 开始,存储于 _java_mirror 末尾
- static 变量分配空间和赋值是两个步骤,分配空间在准备阶段完成,赋值在初始化阶段完成
- 如果 static 变量是 final 的基本类型,以及字符串常量,那么编译阶段值就确定了,赋值在准备阶段完成
- 如果 static 变量是 final 的,但属于引用类型,那么赋值也会在初始化阶段完成
解析
//loadClass方法不会导致类的解析和初始化
ClassLoader classloader = Load2.class.getClassLoader();
Class<?> c = classloader.loadClass("cn.jvm.t3.load.C");
new C();
将常量池中的符号引用解析为直接引用:就是将常量池代表的某个符号经过解析阶段可以直接找到堆内存中引用
4.3 初始化
<cinit>()V
方法
初始化即调用 <cinit>()V
,虚拟机会保证这个类的『构造方法』的线程安全
实验:
public class Load3 {
static {
System.out.println("main init");
}
public static void main(String[] args) throws ClassNotFoundException, IOException {
// // 1. 静态常量不会触发初始化
System.out.println(B.b);
// // 2. 类对象.class 不会触发初始化 访问_java_mirror对象
System.out.println(B.class);
// // 3. 创建该类的数组不会触发初始化
System.out.println(new B[0]);
// 4. 不会初始化类 B,但会加载 B、A
ClassLoader cl = Thread.currentThread().getContextClassLoader();
cl.loadClass("cn.jvm.t3.load.B");
// // 5. 不会初始化类 B,但会加载 B、A
ClassLoader c2 = Thread.currentThread().getContextClassLoader();
Class.forName("cn.jvm.t3.load.B", false, c2); //true时会
System.in.read();
// // 1. 首次访问这个类的静态变量或静态方法时
System.out.println(A.a);
// // 2. 子类初始化,如果父类还没初始化,会引发
System.out.println(B.c);
// // 3. 子类访问父类静态变量,只触发父类初始化
System.out.println(B.a);
// // 4. 会初始化类 B,并先初始化类 A
Class.forName("cn.jvm.t3.load.B");
}
}
class A {
static int a = 0;
static {
System.out.println("a init");
}
}
class B extends A {
final static double b = 5.0;
static boolean c = false;
static {
System.out.println("b init");
}
}
练习:
public class Load4 {
public static void main(String[] args) {
System.out.println(E.a);//no
System.out.println(E.b);//no
System.out.println(E.c);//yes
}
}
class E {
public static final int a = 10;
public static final String b = "hello";
public static final Integer c = 20; // Integer.valueOf(20)
static {
System.out.println("init E");
}
}
Last modified 2020-3-15; size 702 bytes
MD5 checksum f0e9582c3e40e2360d0da9962a669f05
Compiled from "Load4.java"
class cn.jvm.t3.load.E
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Methodref #8.#28 // java/lang/Object."<init>":()V
#2 = Methodref #29.#30 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#3 = Fieldref #7.#31 // cn/itcast/jvm/t3/load/E.c:Ljava/lang/Integer;
......
{
public static final int a;
descriptor: I
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: int 10
public static final java.lang.String b;
descriptor: Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
ConstantValue: String hello
public static final java.lang.Integer c;
descriptor: Ljava/lang/Integer;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
cn.itcast.jvm.t3.load.E();
descriptor: ()V
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 11: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcn/itcast/jvm/t3/load/E;
static {}; //初始化
descriptor: ()V
flags: ACC_STATIC
Code:
stack=2, locals=0, args_size=0
0: bipush 20
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: putstatic #3 // Field c:Ljava/lang/Integer;
8: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
11: ldc #5 // String init E
13: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
16: return
LineNumberTable:
line 14: 0
line 16: 8
line 17: 16
}
SourceFile: "Load4.java"
典型应用 - 完成懒惰初始化单例模式
public class Load9 {
public static void main(String[] args) {
Singleton.test(); //不打印 lazy holder init
// Singleton.getInstance(); // 打印 lazy holder init
}
}
class Singleton {
public static void test() {
System.out.println("test");
}
private Singleton() {}
private static class LazyHolder{
private static final Singleton SINGLETON = new Singleton();
static {
System.out.println("lazy holder init");
}
}
public static Singleton getInstance() {
return LazyHolder.SINGLETON;
}
}
5. 类加载器
以 JDK 8 为例:
5.1 启动类加载器
5.2 扩展类加载器
public class G {
static {
// System.out.println("ext G init");
System.out.println("classpath G init");
}
}
/**
* 演示 扩展类加载器
* 在 C:\Program Files\Java\jdk1.8.0_91 下有一个 my.jar
* 里面也有一个 G 的类,观察到底是哪个类被加载了
* jar -cvf my.jar cn/itcast/jvm/t3/load/G.class
*/
public class Load5_2 {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> aClass = Class.forName("cn.itcast.jvm.t3.load.G");
System.out.println(aClass.getClassLoader());
}
}
5.3 双亲委派模式
5.4 线程上下文类加载器
先看 2)发现它最后是使用 Class.forName 完成类的加载和初始化,关联的是应用程序类加载器,因此可以顺利完成类加载
5.5 自定义类加载器
public class Load7 {
public static void main(String[] args) throws Exception {
MyClassLoader classLoader = new MyClassLoader();
Class<?> c1 = classLoader.loadClass("MapImpl1");
Class<?> c2 = classLoader.loadClass("MapImpl1");
System.out.println(c1 == c2);//true
MyClassLoader classLoader2 = new MyClassLoader();
Class<?> c3 = classLoader2.loadClass("MapImpl1");
System.out.println(c1 == c3);//false 类加载对象不是同一个
c1.newInstance();
}
}
class MyClassLoader extends ClassLoader {
@Override // name 就是类名称
protected Class<?> findClass(String name) throws ClassNotFoundException {
String path = "e:\\myclasspath\\" + name + ".class";
try {
ByteArrayOutputStream os = new ByteArrayOutputStream();
Files.copy(Paths.get(path), os);
// 得到字节数组
byte[] bytes = os.toByteArray();
// byte[] -> *.class
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException e) {
e.printStackTrace();
throw new ClassNotFoundException("类文件未找到", e);
}
}
}
6. 运行期优化
6.1 即时编译分层编译
public class JIT1 {
// -XX:+PrintCompilation -XX:-DoEscapeAnalysis
public static void main(String[] args) {
for (int i = 0; i < 200; i++) {
long start = System.nanoTime();
for (int j = 0; j < 1000; j++) {
new Object();
}
long end = System.nanoTime();
System.out.printf("%d\t%d\n",i,(end - start));
}
}
}
0 41714
1 42045
2 39397
...
109 10594
110 8607
111 8277
...
160 20194
161 331
162 331
...
原因是什么呢?
刚才的一种优化手段称之为【逃逸分析】,发现新建的对象是否逃逸。可以使用 -XX:-DoEscapeAnalysis 关闭逃逸分析,再运行刚才的示例观察结果
参考资料:https://docs.oracle.com/en/java/javase/12/vm/java-hotspot-virtual-machine-performance-enhancements.html#GUID-D2E3DC58-D18B-4A6C-8167-4A1DFB4888E4
方法内联
public class JIT2 {
// -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining -XX:CompileCommand=dontinline,*JIT2.square(禁止内联)
// -XX:+PrintCompilation
public static void main(String[] args) {
int x = 0;
for (int i = 0; i < 500; i++) {
long start = System.nanoTime();
for (int j = 0; j < 1000; j++) {
x = square(9);
}
long end = System.nanoTime();
System.out.printf("%d\t%d\t%d\n",i,x,(end - start));
}
}
private static int square(final int i) {
return i * i;
}
}
0 81 44694
1 81 65219
2 81 29464
...
294 81 15891
295 81 0
296 81 0
字段优化
JMH 基准测试请参考: http://openjdk.java.net/projects/code-tools/jmh/
@Warmup(iterations = 2, time = 1)
@Measurement(iterations = 5, time = 1)
@State(Scope.Benchmark)
public class Benchmark1 {
int[] elements = randomInts(1_000);
private static int[] randomInts(int size) {
Random random = ThreadLocalRandom.current();
int[] values = new int[size];
for (int i = 0; i < size; i++) {
values[i] = random.nextInt();
}
return values;
}
@Benchmark
public void test1() {
for (int i = 0; i < elements.length; i++) {//运行
doSum(elements[i]);
}
}
@Benchmark
public void test2() {
int[] local = this.elements;//手动
for (int i = 0; i < local.length; i++) {
doSum(local[i]);
}
}
@Benchmark
public void test3() {
for (int element : elements) {//编译
doSum(element);
}
}
static int sum = 0;
@CompilerControl(CompilerControl.Mode.DONT_INLINE)
static void doSum(int x) {
sum += x;
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(Benchmark1.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
6.2 反射优化
public class Reflect1 {
public static void foo() {
System.out.println("foo...");
}
public static void main(String[] args) throws Exception {
Method foo = Reflect1.class.getMethod("foo");
for (int i = 0; i <= 16; i++) {
System.out.printf("%d\t", i);
foo.invoke(null);
}
System.in.read();
}
}
当执行到15次以上就会在本地生成方法访问器 MethodAccessorImpl,不会调用native方法了;将反射方法调用变成了直接方法调用。