一、栈上的数据存储
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);}//分层编译@Benchmarkpublic 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功能全开的情况下;不开启逃逸分析,虽然方法内联还生效,但是性能要差很多;完全不开性能就特别差了。