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