栈上的数据存储
在Java中有8大基本数据类型:
这里的内存占用,指的是堆上或者数组中内存分配的空间大小,栈上的实现更加复杂。
以基础篇的这段代码为例:
Java中的8大数据类型在虚拟机中的实现:
boolean、byte、char、short在栈上是不是存在空间浪费?
是的,Java虚拟机采用的是空间换时间方案,在栈上不存储具体的类型,只根据slot槽进行数据的处理,浪费了一些内存空间但是避免不同数据类型不同处理方式带来的时间开销。
同时,像long型在64位系统中占用2个slot,使用了16字节空间,但实际上在Hotspot虚拟机中,它的高8个字节没有使用,这样就满足了long型使用8个字节的需要。
# boolean数据类型保存方式
需求:
编写如下代码,并查看字节码文件中对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、堆中的数据加载到栈上,由于栈上的空间大于或者等于堆上的空间,所以直接处理但是需要注意下符号位。
boolean、char为无符号,低位复制,高位补0
byte、short为有符号,低位复制,高位非负则补0,负则补1
2、栈中的数据要保存到堆上,byte、char、short由于堆上存储空间较小,需要将高位去掉。boolean比较特殊,只取低位的最后一位保存。
# 案例:验证boolean从栈保存到堆上只取最后一位
将a保存在堆上(使用static),使用ASM框架修改字节码指令,将iconst1指令修改为iconst2和iconst3,并测试验证结果。
package demo1;
public class Demo02 {
static boolean a;
public static void main(String[] args) {
a = true;
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");
}
}
}
完整生成class字节码文件的代码:
package demo1;
import java.io.File;
import java.util.*;
import org.apache.commons.io.FileUtils;
import org.objectweb.asm.*;
public class Demo02Dump implements Opcodes {
public static void main(String[] args) throws Exception {
FileUtils.writeByteArrayToFile(new File("D:\\demo1\\Demo02.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/Demo02", null, "java/lang/Object", null);
cw.visitSource("Demo02.java", null);
{
fv = cw.visitField(ACC_STATIC, "a", "Z", null, null);
fv.visitEnd();
}
{
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/Demo02;", 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(6, l0);
mv.visitInsn(ICONST_3);
mv.visitFieldInsn(PUTSTATIC, "demo1/Demo02", "a", "Z");
Label l1 = new Label();
mv.visitLabel(l1);
mv.visitLineNumber(7, l1);
mv.visitFieldInsn(GETSTATIC, "demo1/Demo02", "a", "Z");
Label l2 = new Label();
mv.visitJumpInsn(IFEQ, l2);
Label l3 = new Label();
mv.visitLabel(l3);
mv.visitLineNumber(8, 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(10, l2);
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(l4);
mv.visitLineNumber(13, l4);
mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
mv.visitFieldInsn(GETSTATIC, "demo1/Demo02", "a", "Z");
mv.visitInsn(ICONST_1);
Label l5 = new Label();
mv.visitJumpInsn(IF_ICMPNE, l5);
Label l6 = new Label();
mv.visitLabel(l6);
mv.visitLineNumber(14, 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(16, 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(18, 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.visitMaxs(2, 1);
mv.visitEnd();
}
cw.visitEnd();
return cw.toByteArray();
}
}
对于iconst2来说:
2的最后两位是10,所以只取最末尾0。
对于iconst3来说:
2的最后两位是11,所以只取最末尾1。