方法区

文档描述

1、JVM 有一个方法区,在所有 JVM 线程之间共享

2、方法区类似于传统语言的编译代码的存储区,或者类似于操作系统进程中的“text”段,它存储每个类的结构,如:运行时常量池、字段、方法数据、方法、构造函数的代码,包括用于类和实例初始化。以及接口初始化的特殊方法

3、方法区在虚拟机启动时被创建

4、方法区在逻辑上是堆的一部分,但简单的实现可以选择不对其进行垃圾收集或压缩

5、规范没有规定方法区的位置,或用于管理编译代码的策略

6、方法区可以是一个固定大小,也可以根据计算的需要进行扩展,如果不需要更大的方法区,也可以进行收缩

7、方法区的内存不需要连续

8、JVM 的实现可以为程序员或用户提供对方法区初始大小的控制,以及在不同大小的方法区中,对最大和最小方法区大小的控制

9、如果方法区的内存不能被用来满足分配请求,JVM 会抛出 OutOfMemory

 

方法区位置

1、Java 虚拟机规范:尽管所有的方法区在逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩

2、对于 HotSpot JVM 而言,方法区还有一个别名叫做 Non-Heap(非堆),目的是与堆分开

3、方法区看作是一块独立于 Java 堆的内存空间

 

方法区的基本理解

1、方法区(Method Area)与 Java 堆一样,是各个线程共享的内存区域

2、方法区在 JVM 启动时被创建,并且它的实际的物理内存空间,和 Java 堆区都可以是不连续

3、方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展

4、方法区的大小决定系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误:java.lang.OutOfMemoryError: PermGen space 或 java.lang.OutOfMemoryError: Metaspace

(1)加载大量的第三方 jar 包

(2)Tomcat 部署的工程过多(30~50 个)

(3)大量动态的生成反射类

5、关闭 JVM 就会释放这个区域的内存

 

HotSpot 中方法区的演进

1、在 JDK 7 及以前,习惯上把方法区称为永久代

(1)本质上,方法区和永久代并不等价(仅对 hotspot 而言)

(2)Java 虚拟机规范:对如何实现方法区,不做统一要求,例如:BEA JRockit / IBM J9 中不存在永久代的概念

2、在 JDK 8 及以后,完全废弃永久代,改用与 JRockit、J9 一样,在本地内存中实现的元空间(Metaspace)来代替

(1)元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现

(2)元空间与永久代最大区别在于:元空间不在虚拟机设置的内存中,而是使用本地内存

(3)永久代、元空间两者内部结构不同

(4)Java 虚拟机规范:如果方法区无法满足新的内存分配需求时,将抛出 OOM 异常

 

设置方法区内存的大小

1、方法区的大小不必是固定的,JVM 可以根据应用的需要动态调整

2、JDK 7 及以前

(1)-XX:Permsize:设置永久代初始分配空间,默认值 20.75M

(2)-XX:MaxPermsize:设置永久代最大可分配空间,32 位默认是 64M,64 位默认是 82M

(3)当 JVM 加载的类信息容量,超过 -XX:MaxPermsize,报异常OutOfMemoryError:PermGen space

3、JDK 8 及以后

(1)元数据区大小参数:-XX:MetaspaceSize、-XX:MaxMetaspaceSize

(2)默认值依赖于平台,Windows 下,-XX:MetaspaceSize=21M;-XX:MaxMetaspaceSize=-1(没有限制)

(3)与永久代不同,如果不指定大小,默认情况下,虚拟机会耗尽所有的可用系统内存,如果元数据区发生溢出,虚拟机抛出 OutOfMemoryError:Metaspace

(4)-XX:MetaspaceSize:设置初始的元空间大小

(5)对于一个 64 位的服务器端 JVM,-XX:MetaspaceSize 默认值为 21MB(初始高水位线),超出该值,触发 Full GC,卸载没用的类(即这些类对应的类加载器不再存活),高水位线将会重置

(6)新的高水位线的值,取决于 GC 后释放多少元空间:如果释放空间不足,则在不超过 MaxMetaspaceSize 时,适当提高该值;如果释放空间过多,则适当降低该值

(7)如果初始化的高水位线设置过低,高水位线会多次调整情,为避免频繁 GC,建议将 -XX:MetaspaceSize 设置为一个相对较高的值

 

解决 OOM

1、首先通过内存映像分析工具,如:Eclipse Memory Analyzer,分析 dump 出的堆转储快照,确认内存中的对象是否必要,即确认:内存泄漏(Memory Leak)/ 内存溢出(Memory Overflow)

2、若是内存泄漏

(1)通过工具查看泄漏对象到 GC Roots 的引用链

(2)找到泄漏对象通过何种路径与 GC Roots 相关联,并导致垃圾收集器无法自动回收它们

(3)确定泄漏对象的类型信息,以及 GCRoots 引用链的信息,就可以比较准确地定位出泄漏代码的位置

3、如果不存在内存泄漏

(1)即内存中的对象确实都必须存活,则应检查虚拟机的堆参数:-Xmx、-Xms,与机器物理内存对比,参数是否还可以调大

(2)从代码上检查,是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗

 

方法区存储内容

1、深入理解 Java 虚拟机:存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等

 

方法区的内部结构

1、类型信息

(1)对每个加载的类型(类 class、接口 interface、枚举 enum、注解 annotation),JVM 必须在方法区中存储以下类型信息

(2)这个类型的完整有效名称(全类名 = 包名.类名)

(3)这个类型直接父类的完整有效名(对于 interface 或是 java.lang.object,都没有父类)

(4)这个类型的修饰符(public,abstract,final 的某个子集)

(5)这个类型直接接口的一个有序列表

2、字段(Field)信息

(1)JVM 必须在方法区中,保存类型的所有字段的相关信息,以及字段的声明顺序

(2)字段的相关信息包括:字段名称、字段类型、字段修饰符(public,private,protected,static,final,volatile,transient 的某个子集)

3、方法(Method)信息

(1)JVM 必须保存所有方法的以下信息,包括声明顺序

(2)方法名称

(3)方法的返回类型(或 void)

(4)方法参数的数量和类型(按顺序)

(5)方法的修饰符(public,private,protected,static,final,synchronized,native,abstract 的一个子集)

(6)方法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract 和 native 方法除外)

(7)异常表(abstract 和 native 方法除外)

(8)每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引

4、non-final 的类变量

(1)静态变量和类关联,随着类的加载而加载,成为类数据在逻辑上的一部分

(2)类变量被类的所有实例共享,即使没有类实例时,也可以访问它

(3)全局常量(static final):被声明为 final 的类变量的处理方法则不同,每个全局常量在编译时,就会被分配

 

运行时常量池、常量池

1、方法区内部包含运行时常量池(Runtime Constant Pool)

2、字节码文件内部包含常量池(Constant Pool Table)

3、有效字节码文件包含

(1)类的版本信息、字段、方法、接口等描述符信息

(2)常量池表(Constant Pool Table),包括各种字面量和对应类型、字段、方法的符号引用

 

常量池

1、常量池表(Constant Pool Table)是 .class 文件的一部分,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中

2、常量池可理解为一张表,虚拟机指令根据常量表,查找要执行的类名、方法名、参数类型、字面量等类型

3、作用

(1)一个 Java 源文件中的类、接口,编译后产生一个字节码文件,而 Java 中的字节码需要数据支持,通常这种数据会过大,以至于不能直接存到字节码里

(2)以上数据储存到常量池,这个字节码包含指向常量池的引用,在动态链接时,会使用运行时常量池

4、存储的数据类型

(1)数量值

(2)字符串值

(3)类引用

(4)字段引用

(5)方法引用

 

运行时常量池(Runtime Constant Pool)

1、为方法区的一部分

2、在类和接口加载到虚拟机后

(1)创建对应的运行时常量池

(2)存放常量池在编译期,生成的各种字面量与符号引用

3、JVM 为每个已加载的类型(类或接口)都维护一个常量池,通过索引访问池中的数据项

4、包含多种不同的常量

(1)编译期就已经明确的数值字面量

(2)运行期解析后,才能够获得的方法或字段引用

(3)存储的不再是常量池中的符号地址,而是转换为真实地址

(4)类似于传统编程语言中的符号表(SymbolTable),但是它所包含的数据却比符号表要更加丰富一些

5、运行时常量池区别于 .class 文件常量池的重要特征:具备动态性

6、当创建类或接口的运行时常量池时,如果构造运行时常量池所需的内存空间,超过方法区所能提供的最大值,则 JVM 抛出 OutOfMemoryError 异常

 

方法区的演进细节

1、只有 Hotspot 才有永久代;BEA JRockit、IBM J9 等是不存在永久代

2、原则上如何实现方法区属于虚拟机实现细节,不受 Java 虚拟机规范约束,并不要求统一

3、Hotspot 中方法区的变化

(1)JDK 1.6 及之前:有永久代(permanet),静态变量存储在永久代上

(2)JDK 1.7:有永久代,但逐步去永久代,字符串常量池,静态变量移除,保存在堆中

(3)JDK 1.8:无永久代,类型信息,字段,方法,常量保存在本地内存的元空间,但字符串常量池、静态变量仍然在堆中

 

元空间替代永久代

1、原因:JRockit 没有永久代,在 JRockit 和 HotSpot 融合后,不需要配置永久代

2、Java 8 之后,HotSpot VM 中类的元数据信息,移到元空间,与堆不相连的本地内存区域

(1)类的元数据分配在本地内存中,元空间的最大可分配空间为系统可用内存空间

(2)改动原因:难以确定永久代空间大小,难以对永久代调优

(3)在某些场景下,如果动态加载类过多,容易产生 Perm 区的 OOM

"Exception in thread 'dubbo client x.x connector' java.lang.OutOfMemoryError:PermGen space"

3、元空间、永久代的最大区别:元空间并不在虚拟机中,而是使用本地内存,默认情况下,元空间的大小仅受本地内存限制

4、方法区的垃圾收集行为

(1)Java 虚拟机规范:对方法区的约束非常宽松,不要求虚拟机在方法区中实现垃圾收集

(2)实际上,存在未实现或未能完整实现方法区类型卸载的收集器,如:JDK 11 ZGC 收集器不支持类卸载

(3)方法区的回收条件相当苛刻,但回收是必要的

(4)方法区的垃圾收集主要回收两部分内容:常量池中废弃的常量、不再使用的类型

 

字符串常量池(StringTable)调整位置

1、JDK 7 中,将 StringTable 放到堆空间中

2、原因

(1)永久代回收效率很低,在 Full GC 时才会回收

(2)Full GC 是老年代的空间不足、永久代不足时才会触发,导致字符串常量池回收效率不高

(3)开发中会创建大量字符串,回收效率低,导致永久代内存不足,字符串常量池移到堆中,能及时回收内存

 

静态变量存放位置

1、只要是对象实例,必然在 Java 堆中分配

2、Java 虚拟机规范:所有 Class 相关的信息都应该存放在方法区之中,但对方法区的实现未规定

3、JDK 7 及以后版本的 HotSpot 虚拟机,把静态变量与类型在 Java 语言一端的映射 Class 对象存放在一起,存储于 Java 堆之中

 

方法区的垃圾回收

1、回收的两部分内容

(1)常量池中废弃的常量

(2)不再使用的类型

2、方法区内常量池中主要存放的两大类常量

(1)字面量:较接近 Java 语言层次的常量概念,如:文本字符串、被声明为 final 的常量值等

(2)符号引用:属于编译原理方面的概念,包括三类常量:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符

3、HotSpot 虚拟机回收常量池中废弃的常量

(1)条件:只要常量池中的常量没有被任何地方引用,就可以被回收

(2)回收废弃常量与回收 Java 堆中的对象非常类似

4、HotSpot 虚拟机回收不再使用的类型

(1)不再被使用的类,需要同时满足以下三个条件

(2)该类所有的实例都已经被回收,即 Java 堆中不存在该类及其任何派生子类的实例

(3)加载该类的类加载器已经被回收,该条件除非是经过精心设计的可替换类加载器的场景,如:OSGi、JSP 的重加载等,否则通常是很难达成

(4)该类对应的 java.lang.Class 对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

(5)JVM 被允许对满足上述三个条件的无用类进行回收,只是允许,而不是没有引用,就必然会回收

(6)是否对类型进行回收,HotSpot 虚拟机提供 -Xnoclassgc 参数进行控制,可以使用 -verbose:class、-XX:+TraceClassLoading、-XX:+TraceClassUnLoading 查看类加载和卸载信息

5、在大量使用反射、动态代理、CGLib 等字节码框架、动态生成 JSP、OSGi 这类频繁自定义类加载器的场景中,通常都需要 JVM 具备类型卸载的能力,以保证不会对方法区造成过大的内存压力

posted @   半条咸鱼  阅读(170)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示