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:用于表示引用类型的数组
- 常量池
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)