基础知识-浅谈JVM(看字节码文件理解++i与i++区别, 用Memory看String str = “11” 是如何产生两个OOP的)

1. 一个类是如何加载的

在这里插入图片描述

.1.1 java文件javac编译后 会编译成字节码指令集的文件,javap -c Test.class可以看到相关字节码指令。
1.2 类加载子系统会将class文件解析成InstanceKlass 元数据会放到方法区内
1.3 JDK8之后 取消掉了老年代 元空间替代了老年代
1.4 可以把方法区理解为接口 老年代 元空间是实现
1.5 方法区 堆是共享内存区域 方法区主要放了class元数据 常量池 静态变量等等,堆内存放对象,本地方法栈放的扩展本地方法,虚拟机栈就是栈帧 一个方法就是一个栈帧 执行即生成 执行完毕即释放

2. 虚拟机栈运行情况 ++i 与 i++

2.1 先看一下 虚拟机栈 内部都有什么
在这里插入图片描述
2.11 局部变量表:主要存储入参数据 局部变量数据
2.12 操作数栈:LIFO 后入先出的栈,方法执行过程中,会有各种字节码指令操作数栈写入或提取数据,也就是入栈 出栈操作。
2.13 动态连接:一个指向常量池该栈帧所属方法的引用,这个引用为了支持方法调用过程中的动态连接。class文件的常量池有大量的符号引用,字节码指令就以常量池的符号引用为参数。符号引用在一阶段使用时转化为直接引用,这个阶段叫静态解析。
2.14 返回地址:方法退出主要有两种,一种字节码指令正常推处,另一种异常情况异常推处。无论是哪一种退出都需要知道上一个方法调用的pc计数器位置。

3. ++i与i++ 理解局部变量表与操作数栈

public static void main(String[] args) {
    
    
	a();
	b();
}

public static void a() {
    
    
	int i = 5;
	int j = i++;
	System.out.println(j);
}

public static void b() {
    
    
	int i = 6;
	int j = ++i;
	System.out.println(j);
}

-- 输出结果如下:
Connected to the target VM, address: '127.0.0.1:62076', transport: 'socket'
5
6
Disconnected from the target VM, address: '127.0.0.1:62076', transport: 'socket'

3.1 byteCode

 // access flags 0x9
 public static a()V
	L0
	 LINENUMBER 11 L0
	 // int类型5 压入栈
	 ICONST_5
	 // index为0 保存到局部变量表
	 ISTORE 0
	L1
	 LINENUMBER 12 L1
	 // index为0 从局部变量表压到栈
	 ILOAD 0
	 // 通过常量增加局部变量
	 IINC 0 1
	 // index为1 保存到局部变量表
	 ISTORE 1
	L2
	 LINENUMBER 13 L2
	 GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
	 ILOAD 1
	 INVOKEVIRTUAL java/io/PrintStream.println (I)V
	L3
	 LINENUMBER 14 L3
	 RETURN
	L4
	 LOCALVARIABLE i I L1 L4 0
	 LOCALVARIABLE j I L2 L4 1
	 MAXSTACK = 2
	 MAXLOCALS = 2
 

 // access flags 0x9
 public static b()V
  L0
   LINENUMBER 17 L0
   // 将int型5 压入栈
   ICONST_5
   // index为0 保存至本地变量
   ISTORE 0
  L1
   LINENUMBER 18 L1
   // 通过常量增加局部变量
   IINC 0 1
   // 本地变量int型 index为0 读取到栈顶
   ILOAD 0
   // 将index为1 保存至本地变量
   ISTORE 1
  L2
   LINENUMBER 19 L2
   GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
   ILOAD 1
   INVOKEVIRTUAL java/io/PrintStream.println (I)V
  L3
   LINENUMBER 20 L3
   RETURN
  L4
   LOCALVARIABLE i I L1 L4 0
   LOCALVARIABLE j I L2 L4 1
   MAXSTACK = 2
   MAXLOCALS = 2

可以看到a方法的字节码文件,L0操作 将5入到栈 然后store到局部变量表;
L1操作,是先ILOAD 0(也就是将5load到栈 j的值),然后IINC 0 1(进行加一操作),最后ISTORE 1 (i赋值为6) 也就是说j先得到i为5的值 随后进行++操作 赋值到i 其实这里i还是为6的

而b方法的字节码文件,L1操作,是IINC 0 1(先进行加一操作 i为6),然后ILOAD 0(i为6的值 load到栈),最后ISTORE 1(赋值j为6)也就是说先进性++操作 然后再赋值j

4. Klass模型

4.1 Klass模型
在这里插入图片描述
4.2 普通的Java类在JVM中对应的是instanceKlass类的实例,它还有以下三个字类
4.21 InstanceMirrorKlass:用于表示java.lang.Class,Java代码中获取到的Class对象,实际上就是这个C++类的实例,存储在堆区,学名镜像类
4.22 InstanceRefKlass:用于表示java/lang/ref/Reference类的子类
4.23 InstanceClassLoaderKlass:用于遍历某个加载器加载的类

4.3 Java中的数组不是静态数据类型,是动态数据类型,即是运行期生成的,Java数组的元信息用ArrayKlass的子类来表示:
4.31 TypeArrayKlass:用于表示基本类型的数组
4.32 ObjArrayKlass:用于表示引用类型的数组

  1. 常量池
    5.1 常量池底层是StringTable 而StringTable底层是HashTable HashTable我们可以从java中看到源码 单位也就是entry 接下来代码去看看 内存到底发生了什么变化 把这个Memory选出来
    在这里插入图片描述
public static void main(String[] args) {
    
    
    a();
    b();

}

public static void a() {
    
    
    String str = "11";
    String str1 = "11";
}

public static void b() {
    
    
    String str = new String("11");
    String str1 = new String("22");
}

1. String str = “11”;这一行代码 执行 我们发现char[] +1,j String +1
会先去常量池找11这个常量 如果没有 那么会创一个String对象 String内部的char数组 装载 然后push到常量池 常量池会创建一个entry装11数据
在这里插入图片描述
2. 第二个 String str1 = “11”;
这一行代码发现 先去常量池找11 找到了 然后str1 指向了str所指向的String对象

在这里插入图片描述
3. String str = new String(“11”);
我们会发现只有String +1. 也就是说 11这个String类型的数据 已经有了,那么只需要new一个也就可以了

在这里插入图片描述
4. String str1 = new String(“22”);
先去常量池找11,没有找到先执行 “22” ,这个意思如同第一个例子,也就是一个String对象 一个char数组,然后 再执行new String()构造方法 构造出一个String对象

在这里插入图片描述
总结:
我们发现 String str = “11”;
如果问 产生了几个String对象,那么就是一个String对象
如果 问一共产生几个对象 那么就是两个oop(InstanceKlass TypeArrayKlass)

猜你喜欢

转载自blog.csdn.net/weixin_45657738/article/details/109474297