在前面几章中已经学完了运行时数据区(Runtime Data Areas)的大部分内容了,接下来还有最后一点内容,就是方法区,学完了就差不多了。
上一篇文章地址:https://blog.csdn.net/weixin_46635575/article/details/122730272
假设不设置上面,虚拟机会自动的扩充内存,然后是不会报错什么的,但是如果下面进行设置虚拟机参数的话,那就会报错。
先来回忆一下执行的一个过程
方法区里面的存储的一些数据(大概有这些,但是在不同的版本里面,有些内容是变化的)
(1)类型信息
方法区里面不仅仅是记录了这些内容,而且它存储了它被谁加载的,之前写的文章中写到了类加载器中,这里就是记录了被谁加载的。
对下面这个文件进行反编译
(4)静态变量和全局常量
静态变量
全局变量即用static修饰的final的常量
上面这张图就可以看出来,在编译的时候是直接赋值的(上面这个number属性),而另外一个count则没有赋值
到这里的有一个静态变量块的复制了一样,这是为什么呢?
其实这个在之前文章中就有写到了内容,对类加载的过程中,就是对静态变量进行处理的时候是不会先赋值的,首先赋值为0,然后再到初始化的过程才会赋值。(因为我们类加载过程分为三个阶段,就Loading加载 ,linking链接 ,初始化三个阶段,在前面两个时期是不会赋值的,只有到最后一个阶段初始化才会对静态变量进行赋值,会自动生成一个clinit的方法,对静态变量进行赋值,【这个clinit是有静态的变量或方法才会生成,不然是不会生成的】)
5、运行时常量池(Runtime Constant pool)重点
要学习运行常量池,那必须得先学习运行时间常量池。
(1)常量池(Constant pool)字节码对应的文件,也是上面这张图里面的内容。 javap -v -p test1.class Classfile /D:/JVMTest/out/production/SecondTest/cn/mldn/Zg/p5/test1.class Last modified 2022-1-22; size 792 bytes MD5 checksum 47e4a1309e9e100140f18f662a2269a1 Compiled from "test1.java" public class cn.mldn.Zg.p5.test1 minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #8.#27 // java/lang/Object."":()V #2 = Fieldref #28.#29 // java/lang/System.out:Ljava/io/PrintStream; #3 = Methodref #30.#31 // java/io/PrintStream.println:(I)V #4 = String #23 // hello #5 = Methodref #30.#32 // java/io/PrintStream.println:(Ljava/lang/Str ing;)V #6 = Methodref #7.#33 // cn/mldn/Zg/p5/test1.hello2:()V #7 = Class #34 // cn/mldn/Zg/p5/test1 #8 = Class #35 // java/lang/Object #9 = Utf8 #10 = Utf8 ()V #11 = Utf8 Code #12 = Utf8 LineNumberTable #13 = Utf8 LocalVariableTable #14 = Utf8 this #15 = Utf8 Lcn/mldn/Zg/p5/test1; #16 = Utf8 main #17 = Utf8 ([Ljava/lang/String;)V #18 = Utf8 args #19 = Utf8 [Ljava/lang/String; #20 = Utf8 i1 #21 = Utf8 I #22 = Utf8 i2 #23 = Utf8 hello #24 = Utf8 hello2 #25 = Utf8 SourceFile #26 = Utf8 test1.java #27 = NameAndType #9:#10 // " ":()V #28 = Class #36 // java/lang/System #29 = NameAndType #37:#38 // out:Ljava/io/PrintStream; #30 = Class #39 // java/io/PrintStream #31 = NameAndType #40:#41 // println:(I)V #32 = NameAndType #40:#42 // println:(Ljava/lang/String;)V #33 = NameAndType #24:#10 // hello2:()V #34 = Utf8 cn/mldn/Zg/p5/test1 #35 = Utf8 java/lang/Object #36 = Utf8 java/lang/System #37 = Utf8 out #38 = Utf8 Ljava/io/PrintStream; #39 = Utf8 java/io/PrintStream #40 = Utf8 println #41 = Utf8 (I)V #42 = Utf8 (Ljava/lang/String;)V { public cn.mldn.Zg.p5.test1(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object." ":()V 4: return LineNumberTable: line 3: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcn/mldn/Zg/p5/test1; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: iconst_1 1: istore_1 2: iinc 1, 1 5: getstatic #2 // Field java/lang/System.out:Ljava/io/Pri ntStream; 8: iload_1 9: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 12: iconst_2 13: istore_2 14: iinc 2, 1 17: getstatic #2 // Field java/lang/System.out:Ljava/io/Pri ntStream; 20: iload_2 21: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 24: return LineNumberTable: line 5: 0 line 6: 2 line 7: 5 line 10: 12 line 11: 14 line 12: 17 line 13: 24 LocalVariableTable: Start Length Slot Name Signature 0 25 0 args [Ljava/lang/String; 2 23 1 i1 I 14 11 2 i2 I public void hello(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/Pri ntStream; 3: ldc #4 // String hello 5: invokevirtual #5 // Method java/io/PrintStream.println:(Lja va/lang/String;)V 8: aload_0 9: invokevirtual #6 // Method hello2:()V 12: return LineNumberTable: line 16: 0 line 17: 8 line 18: 12 LocalVariableTable: Start Length Slot Name Signature 0 13 0 this Lcn/mldn/Zg/p5/test1; public void hello2(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/Pri ntStream; 3: ldc #4 // String hello 5: invokevirtual #5 // Method java/io/PrintStream.println:(Lja va/lang/String;)V 8: return LineNumberTable: line 21: 0 line 22: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 this Lcn/mldn/Zg/p5/test1; } SourceFile: "test1.java"
为什么需要常量池呢?
因为仅仅是写了一个比较小的类,就输出了一句话而已,但是内部的却包括了很多内容,比如我们的String,System等类,如果都放到字节码文件里面,那么就很多了,所以我们就用一些符号把我们需要的类进行符号引用,看清楚这里是引用,那我们就能在运行的时候进行加载解析,来使用,那样字节码文件就比较小了。都是一步步的调用的
我们的每一个方法都是这样直接写引用,然后间接的去引用类本身。【这样也达到了多用的目的,比如我们的String类信息,hello1方法引用,然后hello2引用等】
(2)运行时常量池(Runtime Constant pool)
其实很好理解的,就是运行时候,在内存层面对我们刚才的常量池表的一个维护,它是动态变化的。
到这里来梳理一下过程
首先我们写的程序会在你启动的时候,java虚拟机就会启动了,你写的一个程序就相当于一个进程在执行了。对我们的XXX.java 文件进行编译得到了(XXX.class)的文件。上面一个过程中进行了很多内容的处理,包括了对主类的加载,主类牵扯到所有的类进行加载。对于所有加载的类,java虚拟机会为它维护一张常量池表,以表示当前类的信息,包括方法的符号引用,当前类对其他类的一些引用等。你的程序在执行,你的主类里面,主类里面就有你主类对其他类的一些引用,你的主方法(main方法)在执行,它会去调用其他的方法,就是通过对当前去找到其他类,从其他类里面找到对应的方法,然后进行一系列的处理。 6、方法区使用例子
public class pcTest { public static void main(String[] args) { int x = 100; int y = 10; int k = x / y; int b = 12; System.out.println(k+b); } }
以此类为例,进行讲解
这个lstatic是对其他的引用,因为我们要输出嘛,所有它就是去常量池里面的#System的类(真正在执行的时候,它都会把这些符号引用转化为真正的类)
最后我们的main方法的栈帧也就没用了,然后就d出栈了,结束。
这里有点历史的,就在java6的时候就有点想干掉永久代,但是都是在慢慢的演进,后来甲骨文公司直接收购了JRockit,然后就在JDK8合并的,把这个干掉了。
jdk8就方法区的大小就只是受到本地内存的限制了。
为什么要替换呢?
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)