方法区
栈、堆、方法区的交互关系
从线程共享与否的角度看
从实际情况看
即:
类信息存储在方法区
实例对象信息放在Java堆区
引用放在Java栈区
方法区的理解
《Java虚拟机规范》:尽管在所有的方法区在逻辑上是属于堆的一部分,但是一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。
对于HotSpotJVM而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。
方法区看作是一块独立于Java堆的内存空间
- 方法区(Method Area)与Java堆一样,是各个线程共享的内存区域
- JVM启动的时候被创建,并且实际物理内存是可以不连续的
- 方法区空间大小可以选择固定大小或者可扩展
- 方法区被占满,也是会报错OOM
- Tomcat部署的工程过多,大量动态的生成放射类
- 关闭JVM,就会释放这个区域的内存
Hotspot中方法区的演进
方法区在JDK7之前:永久代
JDK8 :元空间
永久区 与 元空间 区别:
元空间不在虚拟机设置的内存中,而是使用本地内存
设置方法区大小与OOM
JDK7:
-XX:PermSize来设置永久代初始化空间大小,默认值20.75
-XX:MaxPermSize来设置永久代最大可分配空间,32位:64M,64位:82M
JDK8:
-XX:MetaspaceSize 21M
-XX:MaxMetaspaceSize -1,没有限制
CMD查看:
JDK7
jps:查看进程号
jinfo -flag PermSize 正在运行的Java线程号,JDK7
jinfo -flag MaxPermSize 正在运行的java线程号
JDK8:
直接将名字换一换就好了
JDK8-方法区特性
如果不指定大小,虚拟机会耗尽所有的可用系统内存
当方法区装满了,那么就会触发Full GC清除一部分没用的类,然后适当的扩充方法区空间
尽量将MetaspaceSize设置一个较高的值,避免频繁的GC
如何解决OOM
- dump出来堆转储快照进行分析,分析内存映像工具,确定是否出现了内存泄漏、内存溢出
-
内存泄漏:该死的没死
- 查看泄漏对象到GC Roots的引用链。就可以知道哪些对象无法被垃圾收集器自动回收。
- 假如没有内存泄漏,那么就扩大内存好了
内存溢出:没有发生内存泄漏,简而言之就是不够大
增大就完事了
方法区存储内统
具体存储内容:类型信息、常量、静态变量、即时编译器编译后的代码缓存
类型信息
对每个加载的类型(类Class、接口interface,枚举enum、注解annotation),JVM必须在方法区中存储以下类型信息:
- 这个类型的完整有效名称(全名 = 包名.类名)
- 这个类型直接父类的完整有小明(对于interface或是Java.lang.Object,都没有父类)
- 这个类型的修饰符(public,abstract,final的某子集)
- 这个类型直接接口的有序列表
域(Filed)信息
JVM必须在方法区中保存类型的所有域的相关信息以及域的声名顺序
域的相关信息包括:域名称、域类型、域修饰符(public,private,protected,static,final,volatile,transient的某个子集)
方法(Method)信息
JVM必须保存所有方法的以下信息,同域信息一样包含在声名顺序:
- 方法名称
- 方法的返回类型
- 方法参数的类型和数量(按照顺序)
- 方法的修饰符
- 方法的字节码、操作数栈、局部变量表的大小
- 异常表(abstract 和 native方法除外)
- 每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
反编译:(待补充)
javap -v -p class文件名 > test.txt
non-final的类变量(待补充)
静态变量和类关联在一起,随着类的加载而加载,它们成为类数据在逻辑上的一部分。
类变量被类的所有实例共享,即使没有类实例时也可以访问它
运行时常量池 和 常量池
运行时常量池============》方法区
常量池=================》类字节码文件
常量池
包含:
数量值
字符串值
类引用
字段引用
方法引用
为什么用常量池
一个java源文件中的类、接口,编译后产生一个字节码文件。而Java中的字节码需要数据支持,通常这种数据会很大以至于不能直接存到字节码里
但是:
可以将这些数据存到常量池中,这个字节码包括了执行常量池的引用。在动态链接的时候会用到运行时常量池
小结
常量池,可以看作一张表,虚拟机指令根据这张常量表找打要执行的类名,方法名,参数类型,字面量等类型
运行常量池
运行时常量池时方法区的一部分
· 常量池中的(字面量、符号引用),当类加载后存放在方法区的运行时常量池中
加载类和接口到虚拟机后,就会创建对应的运行时常量池
JVM为每个已加载的类型(类或者接口)都维护一个常量池。池中的数据项像数组项一样,是通过索引访问的。
运行时常量池中包含了多种不同的常量,包括编译器就已经明确的数值字面量,也包括到运行期解析后才能获得的方法或者字段引用。此时不再是常量池中的符号地址,这里换位真实地址
运行时常量池:相对于Class文件常量池令一重要特征是:具备动态性
运行时常量池似于传统编程语言中的符号表,但是它包含的数据却比符号表更加丰富一些
当创建类或者接口的运行时常量池时,如果构造运行时常量池所需的内存空间朝超过了方法区所能提供的最大值,则JVM抛出OOM
示例
public class MethodAreaDemo { public static final double count; static { count = 1d; } String str = "555"; public void test1(){ int i = 0; System.out.println(i); } public void test2(int i){ System.out.println(i); } public void test3(int i, String j){ i = 100; System.out.println(j); } public void test4(){ } }
经过反编译之后的文件
Classfile /H:/JVM/chapter3/target/classes/MethodAreaDemo.class Last modified 2020-5-31; size 950 bytes MD5 checksum af276a44df208dd83173a1550e0a4762 Compiled from "MethodAreaDemo.java" public class MethodAreaDemo minor version: 0 major version: 49 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #9.#33 // java/lang/Object."":()V #2 = String #34 // 555 #3 = Fieldref #8.#35 // MethodAreaDemo.str:Ljava/lang/String; #4 = Fieldref #36.#37 // java/lang/System.out:Ljava/io/PrintStream; #5 = Methodref #38.#39 // java/io/PrintStream.println:(I)V #6 = Methodref #38.#40 // java/io/PrintStream.println:(Ljava/lang/String;)V #7 = Fieldref #8.#41 // MethodAreaDemo.count:D #8 = Class #42 // MethodAreaDemo #9 = Class #43 // java/lang/Object #10 = Utf8 count #11 = Utf8 D #12 = Utf8 str #13 = Utf8 Ljava/lang/String; #14 = Utf8#15 = Utf8 ()V #16 = Utf8 Code #17 = Utf8 LineNumberTable #18 = Utf8 LocalVariableTable #19 = Utf8 this #20 = Utf8 LMethodAreaDemo; #21 = Utf8 test1 #22 = Utf8 i #23 = Utf8 I #24 = Utf8 test2 #25 = Utf8 (I)V #26 = Utf8 test3 #27 = Utf8 (ILjava/lang/String;)V #28 = Utf8 j #29 = Utf8 test4 #30 = Utf8 #31 = Utf8 SourceFile #32 = Utf8 MethodAreaDemo.java #33 = NameAndType #14:#15 // " ":()V #34 = Utf8 555 #35 = NameAndType #12:#13 // str:Ljava/lang/String; #36 = Class #44 // java/lang/System #37 = NameAndType #45:#46 // out:Ljava/io/PrintStream; #38 = Class #47 // java/io/PrintStream #39 = NameAndType #48:#25 // println:(I)V #40 = NameAndType #48:#49 // println:(Ljava/lang/String;)V #41 = NameAndType #10:#11 // count:D #42 = Utf8 MethodAreaDemo #43 = Utf8 java/lang/Object #44 = Utf8 java/lang/System #45 = Utf8 out #46 = Utf8 Ljava/io/PrintStream; #47 = Utf8 java/io/PrintStream #48 = Utf8 println #49 = Utf8 (Ljava/lang/String;)V { public static final double count; descriptor: D flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL java.lang.String str; descriptor: Ljava/lang/String; flags: public MethodAreaDemo(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: aload_0 5: ldc #2 // String 555 7: putfield #3 // Field str:Ljava/lang/String; 10: return LineNumberTable: line 1: 0 line 9: 4 LocalVariableTable: Start Length Slot Name Signature 0 11 0 this LMethodAreaDemo; public void test1(); descriptor: ()V flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=1 0: iconst_0 1: istore_1 2: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 5: iload_1 6: invokevirtual #5 // Method java/io/PrintStream.println:(I)V 9: return LineNumberTable: line 12: 0 line 13: 2 line 14: 9 LocalVariableTable: Start Length Slot Name Signature 0 10 0 this LMethodAreaDemo; 2 8 1 i I public void test2(int); descriptor: (I)V flags: ACC_PUBLIC Code: stack=2, locals=2, args_size=2 0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 3: iload_1 4: invokevirtual #5 // Method java/io/PrintStream.println:(I)V 7: return LineNumberTable: line 17: 0 line 18: 7 LocalVariableTable: Start Length Slot Name Signature 0 8 0 this LMethodAreaDemo; 0 8 1 i I public void test3(int, java.lang.String); descriptor: (ILjava/lang/String;)V flags: ACC_PUBLIC Code: stack=2, locals=3, args_size=3 0: bipush 100 2: istore_1 3: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 6: aload_2 7: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 10: return LineNumberTable: line 21: 0 line 22: 3 line 23: 10 LocalVariableTable: Start Length Slot Name Signature 0 11 0 this LMethodAreaDemo; 0 11 1 i I 0 11 2 j Ljava/lang/String; public void test4(); descriptor: ()V flags: ACC_PUBLIC Code: stack=0, locals=1, args_size=1 0: return LineNumberTable: line 27: 0 LocalVariableTable: Start Length Slot Name Signature 0 1 0 this LMethodAreaDemo; static {}; descriptor: ()V flags: ACC_STATIC Code: stack=2, locals=0, args_size=0 0: dconst_1 1: putstatic #7 // Field count:D 4: return LineNumberTable: line 6: 0 line 7: 4 } SourceFile: "MethodAreaDemo.java"