JVM学习笔记——内存结构篇( 五 )

  1. 元空间内存溢出问题
// 元空间存在于jdk1.8之后,实际上这时的元空间已经作用于系统内存了,相当于元空间的大小几乎是不可能出现溢出的// 所以我们需要先设置元空间大小才能观察到溢出问题:-XX:MaxMetaspaceSize=8m// 当方法区为永久代时,溢出就显示错误java.lang.OutOfMemoryError: Metaspacepackage cn.itcast.jvm.t1.metaspace;import jdk.internal.org.objectweb.asm.ClassWriter;import jdk.internal.org.objectweb.asm.Opcodes;/** * 演示元空间内存溢出 java.lang.OutOfMemoryError: Metaspace * -XX:MaxMetaspaceSize=8m */public class Demo1_8 extends ClassLoader { // 可以用来加载类的二进制字节码public static void main(String[] args) {int j = 0;try {Demo1_8 test = new Demo1_8();for (int i = 0; i < 10000; i++, j++) {// ClassWriter 作用是生成类的二进制字节码ClassWriter cw = new ClassWriter(0);// 版本号,public,类名, 包名, 父类,接口cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);// 返回 byte[]byte[] code = cw.toByteArray();// 执行了类的加载test.defineClass("Class" + i, code, 0, code.length); // Class 对象}} finally {System.out.println(j);}}}常量池简介我们再回到方法区来简单介绍一下常量池:
  • 我们在上面的图中可以看到常量池之前是放在方法区中的StringTable,但在jdk1.8之后放在了堆中的StingTable
  • 我们需要注意的是:即使StringTable在堆里面,在堆里存放的数据和在StringTable里存放的数据也不是同一个数据
我们先来简单介绍一下常量池:
  • 常量池实际上是一张表
  • 虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
我们再介绍一下运行时常量池:
  • 运行时常量池,常量池是 *.class 文件中的
  • 当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
常量池详细介绍我们通过一个简单的代码编译来介绍常量池:
// 下面是helloworld的源码package cn.itcast.jvm.t5;// 二进制字节码(类基本信息,常量池,类方法定义,包含了虚拟机指令)public class HelloWorld {public static void main(String[] args) {System.out.println("hello world");}}然后我们运行之后,我们可以在out文件夹下找到其编译后程序:
//// Source code recreated from a .class file by IntelliJ IDEA// (powered by FernFlower decompiler)//package cn.itcast.jvm.t5;public class HelloWorld {public HelloWorld() {}public static void main(String[] args) {System.out.println("hello world");}}我们在该目录下对其进行底层查看:
// 我们通过javap -v 代码名.class来查看其详细信息// 其中包括有:class文件的路径、最后修改时间、文件大小;类的全路径、源(java)文件;常量池;常量定义、值;构造方法等javap -v HelloWorld.class// 我们查看其内部详细信息:// 这部分是class文件路径,最后修改日志,文件大小等信息Classfile /E:/编程内容/JVM/资料-解密JVM/代码/jvm/out/production/jvm/cn/itcast/jvm/t5/HelloWorld.classLast modified 2022-11-2; size 567 bytesMD5 checksum 8efebdac91aa496515fa1c161184e354Compiled from "HelloWorld.java"// 这部分是全路径,源码等public class cn.itcast.jvm.t5.HelloWorldminor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPER// 这部分是常量池:我们可以看到很多东西,注解是IDEA为我们携带的// 首先我们可以看到最前面的#,这个代表这一行的地址,然后如果我们希望看懂这一行的信息,需要根据后面的#查看对应的行号// 我们到对应的行号去寻找,直到最后我们可以看到utf8形式的结果,我们将这些信息组合起来就是该行后面IDEA为我们注释的信息Constant pool:#1 = Methodref#6.#20// java/lang/Object."<init>":()V#2 = Fieldref#21.#22// java/lang/System.out:Ljava/io/PrintStream;#3 = String#23// hello world#4 = Methodref#24.#25// java/io/PrintStream.println:(Ljava/lang/String;)V#5 = Class#26// cn/itcast/jvm/t5/HelloWorld#6 = Class#27// java/lang/Object#7 = Utf8<init>#8 = Utf8()V#9 = Utf8Code#10 = Utf8LineNumberTable#11 = Utf8LocalVariableTable#12 = Utf8this#13 = Utf8Lcn/itcast/jvm/t5/HelloWorld;#14 = Utf8main#15 = Utf8([Ljava/lang/String;)V#16 = Utf8args#17 = Utf8[Ljava/lang/String;#18 = Utf8SourceFile#19 = Utf8HelloWorld.java#20 = NameAndType#7:#8// "<init>":()V#21 = Class#28// java/lang/System#22 = NameAndType#29:#30// out:Ljava/io/PrintStream;#23 = Utf8hello world#24 = Class#31// java/io/PrintStream#25 = NameAndType#32:#33// println:(Ljava/lang/String;)V#26 = Utf8cn/itcast/jvm/t5/HelloWorld#27 = Utf8java/lang/Object#28 = Utf8java/lang/System#29 = Utf8out#30 = Utf8Ljava/io/PrintStream;#31 = Utf8java/io/PrintStream#32 = Utf8println#33 = Utf8(Ljava/lang/String;)V// 这部分是编译后的代码,我们可以看到里面包含了#号,这些#就对应着上面的常量池,他们从常量池中获得相关信息用于代码中{public cn.itcast.jvm.t5.HelloWorld();descriptor: ()Vflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: invokespecial #1// Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 4: 0LocalVariableTable:StartLengthSlotNameSignature050thisLcn/itcast/jvm/t5/HelloWorld;public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=1, args_size=10: getstatic#2// Field java/lang/System.out:Ljava/io/PrintStream;3: ldc#3// String hello world5: invokevirtual #4// Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnLineNumberTable:line 6: 0line 7: 8// 这里是局部变量表LocalVariableTable:StartLengthSlotNameSignature090args[Ljava/lang/String;}// 最后标上上述信息的来源文件SourceFile: "HelloWorld.java"

经验总结扩展阅读