Java虚拟机 - 原理篇

一、栈上的数据存储

boolean数据类型保持方式

需求1:编写如下代码,并查看字节码文件中对boolean数据类型处理的指令。

package demo1;

public class Demo01 {
    public static void main(String[] args) {
        boolean a = false;
        if(a){
            System.out.println("a为true");  // √
        }else{
            System.out.println("a为false");
        }

        if(a == true){
            System.out.println("a为true");  // √
        }else{
            System.out.println("a为false");
        }
    }
}

1、常量1先放入局部变量表,相当于给a赋值为true。

2、将1与0比较(判断a是否为false),相当跳转到偏移量17的位置,不相等继续向下运行。这里显然是不相等的。

3、将局部变量表a的值取出来放到操作数栈中,再定义一个常量1,比对两个值是否相等。其实就是判断a == true,如果相等继续向下运行,不相等跳转到偏移量41也就是执行else部分代码。这里显然是相等的。

在Java虚拟机中栈上boolean类型保存方式与int类型相同,所以它的值如果是1代表true,如果是0代表false。但是我们可以通过修改字节码文件,让它的值超过1。

需求2:使用ASM框架修改字节码指令,将iconst1指令修改为iconst2,并测试验证结果。

1、借助于ASM插件:

2、通过插件打开ASM界面:

将代码复制出来,修改一下导出Class文件:

package demo1;

import java.io.File;
import java.util.*;

import org.apache.commons.io.FileUtils;
import org.objectweb.asm.*;

public class Demo01Dump implements Opcodes {

    public static void main(String[] args) throws Exception {
        FileUtils.writeByteArrayToFile(new File("D:\\Demo01.class"),dump());
    }

    public static byte[] dump() throws Exception {

        ClassWriter cw = new ClassWriter(0);
        FieldVisitor fv;
        MethodVisitor mv;
        AnnotationVisitor av0;

        cw.visit(52, ACC_PUBLIC + ACC_SUPER, "demo1/Demo01", null, "java/lang/Object", null);

        cw.visitSource("Demo01.java", null);

        {
            mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(3, l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
            mv.visitInsn(RETURN);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLocalVariable("this", "Ldemo1/Demo01;", null, l0, l1, 0);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }
        {
            mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitLineNumber(5, l0);
            mv.visitInsn(ICONST_2);
            mv.visitVarInsn(ISTORE, 1);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLineNumber(6, l1);
            mv.visitVarInsn(ILOAD, 1);
            Label l2 = new Label();
            mv.visitJumpInsn(IFEQ, l2);
            Label l3 = new Label();
            mv.visitLabel(l3);
            mv.visitLineNumber(7, l3);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("a\u4e3atrue");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            Label l4 = new Label();
            mv.visitJumpInsn(GOTO, l4);
            mv.visitLabel(l2);
            mv.visitLineNumber(9, l2);
            mv.visitFrame(Opcodes.F_APPEND, 1, new Object[]{Opcodes.INTEGER}, 0, null);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("a\u4e3afalse");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv.visitLabel(l4);
            mv.visitLineNumber(12, l4);
            mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
            mv.visitVarInsn(ILOAD, 1);
            mv.visitInsn(ICONST_1);
            Label l5 = new Label();
            mv.visitJumpInsn(IF_ICMPNE, l5);
            Label l6 = new Label();
            mv.visitLabel(l6);
            mv.visitLineNumber(13, l6);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("a\u4e3atrue");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            Label l7 = new Label();
            mv.visitJumpInsn(GOTO, l7);
            mv.visitLabel(l5);
            mv.visitLineNumber(15, l5);
            mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
            mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
            mv.visitLdcInsn("a\u4e3afalse");
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
            mv.visitLabel(l7);
            mv.visitLineNumber(17, l7);
            mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
            mv.visitInsn(RETURN);
            Label l8 = new Label();
            mv.visitLabel(l8);
            mv.visitLocalVariable("args", "[Ljava/lang/String;", null, l0, l8, 0);
            mv.visitLocalVariable("a", "Z", null, l1, l8, 1);
            mv.visitMaxs(2, 2);
            mv.visitEnd();
        }
        cw.visitEnd();

        return cw.toByteArray();
    }
}

注意这句已经修改为iconst_2:

使用jclasslib查看字节码文件:

执行字节码文件:

这里就出现了两个判断语句结果不一致的情况:

第一个判断是将2和0比较,如果不相同就继续运行if下面的分支不会走else分支,显然会走if下面的分支。

第二个判断是将2和1比较,相等走if下面的分支,否则走else。这里由于2和1不相等就会走else分支。

这个案例就可以证明在栈上boolean类型确实是使用了int类型来保存的。

二、对象在堆上是如何存储的?

(1)标记字段

64位不开启指针压缩功能,只是将CMS使用这部分弃用。

(2)元数据的指针 Klass pointer

(3)内存对齐

对象中还有一部分内容就是对齐。内存对齐指的是对象中会空出来几个字节,不做任何数据存储。内存对齐主要目的是为了解决并发情况下CPU缓存失效的问题:

A的数据写入时,由于A和B在同一个缓存行中,所以A和B的缓存数据都会被清空。这样就需要再从内存中读取一次。我们只修改了A对象的数据,引起了B对象的缓存失效。

内存对齐解决了这个问题:内存对齐之后,同一个缓存行中不会出现不同对象的属性。在并发情况下,如果让A对象一个缓存行失效,是不会影响到B对象的缓存行的。

三、方法调用的原理

(1)静态绑定

静态绑定适用于处理静态方法、私有方法、或者使用final修饰的方法,因为这些方法不能被继承之后重写。

(2)动态绑定

四、异常捕获的原理

五、JIT即时编译器

package org.sample;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;
//执行5轮预热,每次持续1秒
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
//执行一次测试
@Fork(value = 1, jvmArgsAppend = {"-Xms1g", "-Xmx1g"})
//显示平均时间,单位纳秒
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
public class MyJITBenchmark {

    public int add (int a,int b){
        return a + b;
    }

    public int jitTest(){
        int sum = 0;
        for (int i = 0; i < 10000000; i++) {
            sum = add(sum,100);
        }
        return sum;
    }


    //禁用JIT
    @Benchmark
    @Fork(value = 1,jvmArgsAppend = {"-Xint"})
    public void testNoJIT(Blackhole blackhole) {
        int i = jitTest();
        blackhole.consume(i);
    }

    //只使用C1 1层
    @Benchmark
    @Fork(value = 1,jvmArgsAppend = {"-XX:TieredStopAtLevel=1"})
    public void testC1(Blackhole blackhole) {
        int i = jitTest();
        blackhole.consume(i);
    }

    //分层编译
    @Benchmark
    public void testMethod(Blackhole blackhole) {
        int i = jitTest();
        blackhole.consume(i);
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(MyJITBenchmark.class.getSimpleName())
                .forks(1)
                .build();

        new Runner(opt).run();
    }
}

(1)方法内联

import java.util.Locale;

public class UpperCase
{
        public String upper;

        public UpperCase()
        {
                int iterations = 10_000_000;

                String source = "Lorem ipsum dolor sit amet, sensibus partiendo eam at.";

                long start = System.currentTimeMillis();
                convertString(source, iterations);
                System.out.println(upper);
                System.out.println(System.currentTimeMillis() - start);

                start = System.currentTimeMillis();
                convertCustom(source, iterations);
                System.out.println(upper);
                System.out.println(System.currentTimeMillis() - start);
        }

        private void convertString(String source, int iterations)
        {
                for (int i = 0; i < iterations; i++)
                {
                        upper = source.toUpperCase(Locale.getDefault());
                }
        }

        private void convertCustom(String source, int iterations)
        {
                for (int i = 0; i < iterations; i++)
                {
                        upper = doUpper(source);
                }
        }

        private String doUpper(String source)
        {
                StringBuilder builder = new StringBuilder();

                int len = source.length();

                for (int i = 0; i < len; i++)
                {
                        char c = source.charAt(i);

                        if (c >= 'a' && c <= 'z')
                        {
                                c -= 32;
                        }

                        builder.append(c);
                }

                return builder.toString();
        }

        public static void main(String[] args)
        {
                new UpperCase();
        }
}

最终结果:自行实现的方法性能要比JDK默认提供的高很多,当然只支持对a-z做大写化。

(2)逃逸分析

测试代码:

package org.sample;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.Random;
import java.util.concurrent.TimeUnit;

//执行5轮预热,每次持续1秒
@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
//执行一次测试
//显示平均时间,单位纳秒
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Measurement(iterations = 3,time = 1,timeUnit = TimeUnit.SECONDS)
@State(Scope.Benchmark)
public class EscapeAnalysisBenchmark2 {

    public int test(){
        int count = 0;
        for (int i = 0; i < 10000000; i++) {
            Point point = new Point();
            point.test();
        }
        return count;
    }

    @Benchmark
    @Fork(value = 1,jvmArgsAppend = {"-Xmx10m"})
    public void testWithJIT(Blackhole blackhole) {
        int i = test();
        blackhole.consume(i);
    }

    @Benchmark
    @Fork(value = 1,jvmArgsAppend = {"-XX:-DoEscapeAnalysis","-Xmx10m"})
    public void testWithoutEA(Blackhole blackhole) {
        int i = test();
        blackhole.consume(i);
    }

    @Benchmark
    @Fork(value = 1,jvmArgsAppend = {"-Xint","-Xmx10m"})
    public void testWithoutJIT(Blackhole blackhole) {
        int i = test();
        blackhole.consume(i);
    }

        public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(EscapeAnalysisBenchmark2.class.getSimpleName())
                .forks(1)
                .build();

        new Runner(opt).run();
    }

}


class Point{
    private int x;
    private int y;

    public void test(){
        x = 1;
        y = 2;
        int z = x++;
    }
}

测试结果:性能最高的是JIT功能全开的情况下;不开启逃逸分析,虽然方法内联还生效,但是性能要差很多;完全不开性能就特别差了。

六、垃圾回收器原理

1. G1垃圾回收器原理

(1)年轻代

(2)混合回收

(3)转移

2. ZGC原理

(1)初始标记阶段

(2)并发处理阶段

(3)转移开始阶段

(4)并发转移阶段

(5)第二次垃圾回收的初始标记阶段

3. ShenandoahGC原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值