在前面几章中已经学完了运行时数据区(Runtime Data Areas)的大部分内容了,接下来还有最后一点内容,就是方法区,学完了就差不多了。
上一篇文章地址:https://blog.csdn.net/weixin_46635575/article/details/122730272
一、栈,堆,方法区的交互
1、从线程与否的角度分
2、三者之间的关系
二、方法区的基本理解
1、方法区的所在位置
2、Hotspot虚拟机的方法演进
3、设置方法区大小与OOM
(1)来举栗子
假设不设置上面,虚拟机会自动的扩充内存,然后是不会报错什么的,但是如果下面进行设置虚拟机参数的话,那就会报错。
4、方法区的内部结构
- 先来回忆一下执行的一个过程
- 方法区里面的存储的一些数据(大概有这些,但是在不同的版本里面,有些内容是变化的)
(1)类型信息
(2)域(Field)信息(成员变量,也是属性)
(3)方法(Method)信息
方法区里面不仅仅是记录了这些内容,而且它存储了它被谁加载的,之前写的文章中写到了类加载器中,这里就是记录了被谁加载的。
- 对下面这个文件进行反编译
(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."<init>":()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 <init>
#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 // "<init>":()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."<init>":()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方法的栈帧也就没用了,然后就弹出栈了,结束。
7、方法区的演进
(1)方法区的演进
这里有点历史的,就在java6的时候就有点想干掉永久代,但是都是在慢慢的演进,后来甲骨文公司直接收购了JRockit,然后就在JDK8合并的,把这个干掉了。
jdk8就方法区的大小就只是受到本地内存的限制了。
- 为什么要替换呢?
(2)Stringtable的调整
- 为什么要有这样的变化
(3)静态变量
- 可以设置这样的参数,进行查看
先用jdk7来查看,看这个,说明都是放在heap里面的
- jdk8执行的时候,怎么好像也是在方法区呢
为什么都是放到了堆空间了呢?不是说在jdk7及之前是放到了方法区了嘛:下面来分析原因- 但是你要注意看我们是new了一个对象,它肯定是放到堆空间了,
- 看咱们三个变量的位置在jdk8中的放置
这个可以用软件查看,具体现在不讲,你理解下面的三个例子就好啦。
观看三个变量的地址可以知道,三个变量new的都是放在了我们的堆空间了。
8、方法区的垃圾回收行为
9、大厂面试题