性能调优专题-基础-3、JVM-方法区和3个池子
方法区也需要进行垃圾回收,大约5%的垃圾回收发生在方法区,但回收率并不高。只有在full GC时才会回收。
1. 不同实现
JDK7用永久代实现方法区
JDK8后用元空间实现方法区,并且将方法区中的字符串常量池和类的静态变量放到堆中,只保留下类的元数据信息(方法元信息、类元信息)
2. 内存设置
2.1. 默认大小
#todo
2.2. 内存溢出
1.加载类的二进制字节码
2.jdk1.8设置元空间大小模拟方法区内存溢出
-XX:MaxMetaspaceSize=8m
3.jdk1.6设置永久代大小模拟方法区内存溢出
-XX:MaxPermSize=8m
框架中的cglib会产生大量的字节码
3. 内部结构
3.1. 类型信息
对每个加载的类型(类class、接口interface、枚举enum、注解annotation),JVM必须在方法区中存储以下类型信息:
- 这个类型的完整有效名称(全名=包名.类名)
- 这个类型直接父类的完整有效名(对于interface或是java.lang.object,都没有父类)
- 这个类型的修饰符(public,abstract,final的某个子集)
- 这个类型直接接口的一个有序列表
3.2. 域(Field)信息
JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序。
域的相关信息包括:域名称、域类型、域修饰符(public,private,protected,static,final,volatile,transient的某个子集)
3.3. 方法(Method)信息
JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序:
- 方法名称
- 方法的返回类型(或void)
- 方法参数的数量和类型(按顺序)
- 方法的修饰符(public,private,protected,static,final,synchronized,native,abstract的一个子集)
- 方法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract和native方法除外)
- 异常表(abstract和native方法除外)
❕ ^jf3el2
- 每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
3.4. non-final的类变量
- 静态变量和类关联在一起,随着类的加载而加载,他们成为类数据在逻辑上的一部分
- 类变量被类的所有实例共享,即使没有类实例时,你也可以访问它
1 |
|
3.5. 补充说明:全局常量(static final)
被声明为final的类变量的处理方法则不同,每个全局常量在编译的时候就会被分配了。
4. 演进细节
4.1. 为什么要替换永久代⭐️🔴
4.1.1. 永久代设置内存大小是很难确定的
4.1.1.1. 方法区内存溢出
❕ ^m58oc6
如果系统定义了太多的类,可能导致方法区溢出,虚拟机同样会抛出内存溢出错误:java.lang.OutofMemoryError:PermGen space 或者java.lang.OutOfMemoryError:Metaspace;
比如下面三种情况:
- 加载大量的第三方的jar包;
- Tomcat部署的工程过多;
- 大量动态的生成反射类;
4.1.2. 永久代调优是很困难的
发散动态分派 性能调优专题-基础-5、JVM-虚拟机栈
4.2. 为什么要调整StringTable位置⭐️🔴
温故知新:
5. 三个池子
5.1. class(静态)常量池
静态常量池也就是Class文件中的常量池,下面用一张图来看看静态常量池在Class文件中的位置:
从上图可以看出,Class文件中包括:
魔数:它的唯一作用是确定这个文件是否可以被JVM接受。很多文件储存标准中都使用魔数来进行身份识别的,其占用这个文件的前四个字节。
版本号:第5和第6个字节是副版本号,第7个和第8 个是主版本号。
常量池计数器:也就是常量池的入口,代表常量池的容量计数器。
**常量池:常量池中主要存放2类常量:字面量和符号引用。
- 字面量:字面量就是指由字母、数字等构成的字符串或者数值常量。字面量只可以右值出现,所谓右值是指等号右边的值,如:
int a=1
这里的a
为左值,1
为右值。在这个例子中1
就是字面量。 - 符号引用:上面的
a、b
就是字段名称,就是一种符号引用,符号引用可以是:- 类和接口的全限定名
com.xx.User
- 字段的名称和描述符
name
- 方法的名称和描述符
set()
- 类和接口的全限定名
静态常量池用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。其中符号引用其实引用的就是常量池里面的字符串,但符号引用不是直接存储字符串,而是存储字符串在常量池里的索引。
当Class文件被加载完成后,java虚拟机会将静态常量池里的内容转移到运行时常量池里,在静态常量池的符号引用有一部分是会被转变为直接引用的,比如说类的静态方法或私有方法,实例构造方法,父类方法,这是因为这些方法不能被重写其他版本,所以能在加载的时候就可以将符号引用转变为直接引用,而其他的一些方法是在这个方法被第一次调用的时候才会将符号引用转变为直接引用的。
5.2. 运行时常量池
运行时常量池是方法区的一部分,是一块内存区域。Class 文件常量池将在类加载后进入方法区的运行时常量池中存放。一个类加载到 JVM 中后对应一个运行时常量池,运行时常量池相对于 Class 文件常量池来说具备动态性,Class 文件常量只是一个静态存储结构,里面的引用都是符号引用。而运行时常量池可以在运行期间将符号引用解析为直接引用。可以说运行时常量池就是用来索引和查找字段和方法名称和描述符的。给定任意一个方法或字段的索引,通过这个索引最终可得到该方法或字段所属的类型信息和名称及描述符信息,这涉及到方法的调用和字段获取。
5.3. 字符串常量池
关键字和接口-3、String和StringTable♨️5.4. 总结
- 1.全局常量池在每个VM中只有一份,存放的是字符串常量的引用值。
- 2.class常量池是在编译的时候每个class都有的,在编译阶段,存放的是常量的符号引用。
- 3.运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个运行时常量池,类在解析之后,将符号引用替换成直接引用,与全局常量池中的引用值保持一致。
6. 参考与感谢
6.1. 尚硅谷宋红康
https://www.bilibili.com/video/BV1PJ411n7xZ?p=263&vd_source=c5b2d0d7bc377c0c35dbc251d95cf204
资料已下载:/Volumes/Seagate Bas/001-ArchitectureRoad/JVM上篇:内存与垃圾回收篇
配套网络笔记: https://www.yuque.com/u21195183/jvm/rq9lt4
6.2. 黑马程序员
/Users/taylor/Nutstore Files/Obsidian_data/pages/002-schdule/001-Arch/001-Subject/007-性能调优专题/001-JVM/黑马程序员JVM完整教程
6.3. 网络笔记
https://www.yuque.com/u21195183/jvm/nbkm46
https://github.com/youthlql/JavaYouth/blob/main/docs/JVM/JVM%E7%B3%BB%E5%88%97-%E7%AC%AC6%E7%AB%A0-%E6%96%B9%E6%B3%95%E5%8C%BA.md
https://blog.csdn.net/sj15814963053/article/details/110371508