JVM学习(十):方法区

news2024/12/26 9:27:26

目录

一、栈、堆和方法区的交互关系

 二、对方法区的理解

2.1 方法区在哪里

2.2 方法区的基本概念 

2.3 Hotspot中方法区的演进

三、方法区的大小

3.1 设置参数 

3.1.1 jdk7及以前

3.1.2 jdk8以后:

3.2 配置参数演示OOM

四、方法区的内部结构

4.1 方法区里面存什么 

4.2 类型信息

4.3 域(Field)信息

4.4 方法(Method)信息 

4.5 运行时常量池 

4.5.1 常量池 

4.5.2 运行时常量池

五、方法区的演进细节 

5.1 Hotspot中方法区的变化

5.2 为什么要移除永久代 

5.3 为什么要调整StringTable的位置 

5.4 证明静态变量的存放位置

六、方法区中的垃圾回收

6.1 方法区垃圾回收概述 

6.2 收集什么 


 

一、栈、堆和方法区的交互关系

        对栈不熟悉的同学可以参考我的文章:JVM学习(八):虚拟机栈(字节码程度深入剖析)_玉面大蛟龙的博客-CSDN博客         

        对堆不熟悉的同学可以参考我的文章:

JVM学习(九):堆_玉面大蛟龙的博客-CSDN博客

        方法区、堆和栈的关系如图: 

         

 二、对方法区的理解

 2.1 方法区在哪里

        《Java虚拟机规范》中明确说明:"尽管所有的方法区在逻辑上是属于堆的一部分,但一些简单的实现可能不会选择去进行垃圾收集或者进行压缩。”但对于HotSpotJVM而言,方法区还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开。

        所以,方法区看作是一块独立于Java堆的内存空间

        我们可以简单地验证一下。随便编写一段代码不会退出的代码:

public class HeapDemo {
    public static void main(String[] args) {
        System.out.println("start...");
        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("end...");
    }

}

        设置堆大小为600M:

-Xms600m -Xmx600m

        打开Java VisualVM,看看堆空间的大小:

         可以看到,新生态和老年代加起来已经达到了600M,并没有加上方法区。

2.2 方法区的基本概念 

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

        方法区在JVM启动的时候被创建,并且它的实际的物理内存空间中和Java堆区一样都可以是不连续的。关闭JVM就会释放这个区域的内存。 

        方法区的大小跟堆空间一样,可以选择固定大小或者可扩展。方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误: java.lang.OutOfMemoryError:PermGen space 或者java.lang.OutOfMemoryError: Metaspace。常见的发生OOM的情况有:加载大量的第三方的jar包;Tomcat部署的工程过多(30-50个);大量动态的生成反射类。

2.3 Hotspot中方法区的演进

        在jdk7及以前,习惯上把方法区称为永久代。jdk8开始,使用元空间取代了永久代。

        本质上,方法区和永久代的等价仅是针对hotspot而言的。《Java虚拟机规范》对如何实现方法区不做统一要求。例如:BEA JRockit / IBM J9中不存在永久代的概念。现在来看,当年使用永久代并不是好的选择,因为这样导致Java程序更容易出现OOM(超过-XX:MaxPermSize上限) 

        而到了JDK-8,终于完全废弃了永久代的概念,改用与JRockit、J9一样在本地内存中实现的元空间(Metaspace)来代替,元空间的本质和永久代类似,都是对JVM规范中方法区的实现。不过元空间与永久代最大的区别在于:元空间不在虚拟机设置的内存中,而是使用本地内存。永久代、元空间二者并不只是名字变了,内部结构也调整了,具体如何调整,在下面第五章中来讲。 

三、方法区的大小

3.1 设置参数 

        方法区的大小不一定是固定的,jvm可以根据应用的需要动态调整。

3.1.1 jdk7及以前

  • 通过-XX:PermSize 来设置永久代初始分配空间。默认值是20.75M
  • 通过-XX:MaxPermsize 来设定永久代最大可分配空间。32位机器默认是64M,64位机器模式是82M

        当JVM加载的类信息容量超过了这个值,会报异常 OutOfMemoryError:PermGenspace 。

3.1.2 jdk8以后:

        元数据区大小可以使用参数-XX:MetaspaceSize-XX:MaxMetaspaceSize指定,替代上述原有的两个参数。参数默认值依赖于平台。windows下,-XX:MetaspaceSize是21M,-XX:MaxMetaspaceSize的值是-1,即没有限制。

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

        -XX:Metaspacesize的作用是设置初始的元空间大小。对于一个64位的服务器端JVM来说,其默认的-XX:MetaspaceSize值为21MB。这就是初始的高水位线,一旦触及这个水位线,Full GC将会被触发并卸载没用的类(即这些类对应的类加载器不再存活),然后这个高水位线将会重置。新的高水位线的值取决于GC后释放了多少元空间。如果释放的空间不足,那么在不超过MaxMetaspaceSize时,适当提高该值。如果释放空间过多,则适当降低该值。如果初始化的高水位线设置过低,上述高水位线调整情况会发生很多次。通过垃圾回收器的日志可以观察到Full GC多次调用。为了避免频繁地GC ,建议将-XX:MetaspaceSize设置为一个相对较高的值。

3.2 配置参数演示OOM

        我们尝试通过配置元空间大小,让元空间出现OOM异常。 

        用反射生成很多的大对象来让元空间溢出。

public class OOMTest extends ClassLoader {
    public static void main(String[] args) {
        int j = 0;
        try {
            OOMTest test = new OOMTest();
            for (int i = 0; i < 100000; i++) {
                //创建ClassWriter对象,用于生成类的二进制字节码
                ClassWriter classWriter = new ClassWriter(0);
                //指明版本号,修饰符,类名,包名,父类,接口
                classWriter.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "Class" + i, null, "java/lang/Object", null);
                //返回byte[]
                byte[] code = classWriter.toByteArray();
                //类的加载
                test.defineClass("Class" + i, code, 0, code.length);//Class对象
                j++;
            }
        } finally {
            System.out.println(j);
        }
    }
}

        设置VM参数:

-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m

四、方法区的内部结构

4.1 方法区里面存什么 

 

        《深入理解Java虚拟机》书中对方法区(Method Area)存储内容描述:它用于存储已被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存等。 这是一种比较经典的说法,事实上随着jdk版本的迭代,方法区存放的内容也会有不同。

4.2 类型信息

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

  1. 这个类型的完整有效名称(全名=包名.类名)
  2. 这个类型直接父类的完整有效名(对于interface或是java.lang.Object,都没有父类)
  3. 这个类型的修饰符(public,abstract,final的某个子集)
  4. 这个类型直接接口的一个有序列表 

4.3 域(Field)信息

  • JVM必须在方法区中保存类型的所有域的相关信息以及域的声明顺序
  • 域的相关信息包括:域名称、域类型、域修饰符(public,private,protected,static,final,volatile,transient的某个子集) 

4.4 方法(Method)信息 

        JVM必须保存所有方法的以下信息,同域信息一样包括声明顺序:

  • 方法名称
  • 方法的返回类型(或void)
  • 方法参数的数量和类型(按顺序)
  • 方法的修饰符(public, private, protected, static, final,synchronized,native, abstract的一个子集)
  • 方法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract和native方法除外)
  • 异常表(abstract和native方法除外)
    • 每个异常处理的开始位置、结束位置、代码处理在程序计数器中的偏移也被捕获的异常类的常量池索引 

4.5 运行时常量池 

        方法区内部包含了运行时常量池。字节码文件内部包含了常量池。要弄清楚方法区,需要理解清楚ClassFile,因为加载类的信息都在方法区。要弄清楚方法区的运行时常量池,需要理解清楚ClassFile中的常量池。

4.5.1 常量池 

        一个有效的字节码文件中除了包含类的版本信息、字段、方法以及接口等描述信息外,还包含一项信息那就是常量池表(Constant Pool Table),包括各种字面量和对类型、域和方法的符号引用。

  • 为什么需要常量池?

        一个java源文件中的类、接口,编译后产生一个字节码文件,而Java中的字节码需要数据支持,通常这种数据很大以至于不能直接存到字节码里,那么换另一种方式,可以存到常量池,这个字节码包含了指向常量池的引用。在动态链接的时候会用到运行时常量池,这个在我的文章中有介绍:JVM学习(八):虚拟机栈(字节码程度深入剖析)_玉面大蛟龙的博客

  • 常量池中有什么? 

        几种在常量池内存储的数据类型包括:数量值、字符串值、类引用、字段引用、方法引用

        例如如下代码:

public class DJLTest {
    public static void main(String[] args) {
        Object obj = new Object();
    }
}

        Object obj = new Object(); 会被编译成如下字节码:

0: new           #2                  // class java/lang/Object
3: dup
4: invokespecial #1                  // Method java/lang/Object."<init>":()V

        其中 #1 和 #2 其实就是指向常量池的:

        #1又继续往常量池的#2 和 #19指…… 

  • 总结 

        常量池可以看做是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等类型。

4.5.2 运行时常量池

        运行时常量池(Runtime Constant Pool)是方法区的一部分。

        常量池表(Constant Pool Table)是class文件的一部分,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。运行时常量池,在加载类和接口到虚拟机后,就会创建对应的运行时常量池

        JVM为每个已加载的类型(类或接口)都维护一个常量池。池中的数据项像数组项一样,是通过索引访问的。运行时常量池中包含多种不同的常量,包括编译期就已经明确的数值字面量,也包括到运行期解析后才能够获得的方法或者字段引用。此时不再是常量池中的符号地址了,这里换为真实地址。因此,运行时常量池相对于class文件常量池的另一重要特征是:具备动态性(例如:String.intern() )。

        运行时常量池类似于传统编程语言中的符号表( symbol table),但是它所包含的数据却比符号表要更加丰富一些。

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

五、方法区的演进细节 

        首先明确:只有HotSpot才有永久代。BEA JRockit、IBM J9等来说,是不存在永久代的概念的。原则上如何实现方法区属于虚拟机实现细节,不受《Java虚拟机规范》管束,并不要求统一。

5.1 Hotspot中方法区的变化

jdk1.6及之前有永久代(permanent generation),静态变量存放在永久代上
jdk1.7有永久代,但已经逐步“去永久代”,字符串常量池、静态变量移除,保存在堆中
jdk1.8及之后无永久代,类型信息、字段、方法、常量保存在本地内存的元空间,但字符串常量池、静态变量仍在堆

5.2 为什么要移除永久代 

        随着Java8的到来,HotSpot VM中再也见不到永久代了。但是这并不意味着类的元数据信息也消失了。这些数据被移到了一个与堆不相连的本地内存区域,这个区域叫做元空间(Metaspace) 。 因此类的元数据分配在本地内存中,元空间的最大可分配空间就是系统可用内存空间。

        关于元空间替换永久代的原因,网上的许多帖子都有谬误,其实官方文档解释得很清楚:JEP 122: Remove the Permanent Generation 

        这里我们提炼一下原因:

        1、为永久代设置空间大小是很难确定的。

        在某些场景下,如果动态加载类过多,容易产生Perm区的OOM。比如某个实际web工程中,因为功能点比较多,在运行过程中,要不断动态加载很多类,经常出现致命错误。

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

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

        2、对永久代进行调优是很困难的。

5.3 为什么要调整StringTable的位置 

        jdk7中将StringTable放到了堆空间中。因为永久代的回收效率很低,在full gc的时候才会触发。而full gc是老年代的空间不足、永久代不足时才会触发。这就导致StringTable回收效率不高。而我们开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足。放到堆里,能及时回收内存。

 5.4 证明静态变量的存放位置

        主要证明jdk1.8之后,静态变量存放在堆中。

        我们写一段代码,构造一个非常大的静态变量:

public class StaticFieldTest {
    private static byte[] arr = new byte[1024 * 1024 * 100];//100MB

    public static void main(String[] args) {
        System.out.println(StaticFieldTest.arr);
    }
}

         选择jdk1.8,配置VM参数:

-Xms200m -Xmx200m -XX:MetaspaceSize=300m -XX:MaxMetaspaceSize=300m -XX:+PrintGCDetails

         看后台打印结果可以很明显地看出,这100M内存放在老年代里,是在堆中:

六、方法区中的垃圾回收

6.1 方法区垃圾回收概述 

        有些人认为方法区如HotSpot虚拟机中的元空间或者永久代)是没有垃圾收集行为的,其
实不然。《Java虚拟机规范》对方法区的约束是非常宽松的,提到过可以不要求虚拟机在方
法区中实现垃圾收集。事实上也确实有未实现或未能完整实现方法区类型卸载的收集益仔仕(如JDK 11时期的ZGC收集器就不支持类卸载)。

        一般来说这个区域的回收效果比较难令人满意,尤其是类型的卸载,条件相当苛刻。但是这部分区域的回收有时又确实是必要的。以前sun公司的Bug列表中,曾出现过的若干个严重的Bug就是由于低版本的Hotspot虚拟机对此区域未完全回收而导致内存泄漏。

6.2 收集什么 

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

        先来说说方法区内常量池之中主要存放的两大类常量:字面量和符号引用。字面量比较接近Java语言层次的常量概念,如文本字符串、被声明为final的常量值等。而符号引用则属于编译原理方面的概念,包括下面三类常量:

  1. 类和接口的全限定名
  2. 字段的名称和描述符
  3. 方法的名称和描述符

        HotSpot虚拟机对常量池的回收策略是很明确的,只要常量池中的常量没有被任何地方引用,就可以被回收。回收废弃常量与回收Java堆中的对象非常类似。

        判定一个常量是否“废弃”还是相对简单,而要判定一个类型是否属于“不再被使用的类”的条件就比较苛刻了。需要同时满足下面三个条件:

  • 该类所有的实例都已经被回收,也就是Java堆中不存在该类及其任何派生子类的实例
  • 加载该类的类加载器已经被回收,这个条件除非是经过精心设计的可替换类加载器的场景,如OSGi、JSP的重加载等,否则通常是很难达成的
  • 该类对应的java.lang.class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

        Java虚拟机被允许对满足上述三个条件的无用类进行回收,这里说的仅仅是“被允许”,而并不是和对象一样,没有引用了就必然会回收。关于是否要对类型进行回收,HotSpot虚拟机提供了-Xnoclassgc参数进行控制,还可以使用-verbose:class以及-XX:+TraceClass-Loading-XX:+TraceClassUnLoading查看类加载和卸载信息。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/507154.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java虚拟机】JVM调优和分析案例综合实战

1.什么是JVM性能优化 jvm性能优化涉及到两个很重要的概念&#xff1a;吞吐量和响应时间。jvm调优主要是针对他们进行调整优化&#xff0c;达到一个理想的目标&#xff0c;根据业务确定目标是吞吐量优先还是响应时间优先。 吞吐量&#xff1a;用户代码执行时间/(用户代码执行时…

C语言学习分享(第六次)------数组

&#x1f493;博主CSDN主页:杭电码农-NEO&#x1f493;   ⏩专栏分类:C语言学习分享⏪   &#x1f69a;代码仓库:NEO的学习日记&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多C语言知识   &#x1f51d;&#x1f51d; 数组详解 1. 前言&#x1f536;2. …

使用 spring 的 IoC 的实现账户的CRUD(2)双层实现

spring实现service和dao的数据的查找 dao层设置接口实现dao层的接口service设置接口通过注入dao层&#xff0c;来实现接口 //dao层的接口&#xff0c;定义了根据id查询的方法 public interface Accountdao {Account findByid(int id); }实现接口&#xff1a;实现了查询的方法 …

【模板】拓扑排序

import java.util.Scanner; import java.util.*;// 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main {public static void main(String[] args) {Scanner in new Scanner(System.in);int point in.nextInt();int side in.nextInt();int[][] arr new i…

MacOS下安装和配置Nginx

一、安装brew /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"按回车后&#xff0c;根据提示操作&#xff1a;输入镜像序号 --> 输入Y&#xff0c;回车等待brew安装完成即可。 在终端输入brew -v后&#xff0c;会提示…

【牛客刷题专栏】0x25:JZ24 反转链表(C语言编程题)

前言 个人推荐在牛客网刷题(点击可以跳转)&#xff0c;它登陆后会保存刷题记录进度&#xff0c;重新登录时写过的题目代码不会丢失。个人刷题练习系列专栏&#xff1a;个人CSDN牛客刷题专栏。 题目来自&#xff1a;牛客/题库 / 在线编程 / 剑指offer&#xff1a; 目录 前言问…

Jedis客户端和SpringDataRedis客户端

目录 3.Redis的Java客户端 3.1.Jedis客户端 3.1.1.快速入门 3.1.2.连接池 3.2.SpringDataRedis客户端 3.2.1.快速入门 3.2.2.自定义序列化 3.2.3.StringRedisTemplate 3.Redis的Java客户端 3.1.Jedis客户端 Jedis的官网地址&#xff1a; GitHub - redis/jedis: Redis…

单片机中时钟分析与快速读懂时序图的方法

目录 一、时钟电路 二、周期 三、时序 我们都知道在学校是通过铃声来控制所有班级的上下课时间&#xff0c;那个单片机是通过什么样的办法进行取指令&#xff0c;执行指令和其它操作的呢&#xff1f;在这里引入了一个时序的概念。 一、时钟电路 单片机时钟电路有三种方式…

LoadRunner的简单使用

目录 1、LoadRunner工具介绍 2、VUG的使用 3、Controller的使用 3.1、场景设计 3.2、场景运行及结果 4、Analysis的使用 1、LoadRunner工具介绍 Virtual User Generator&#xff1a;主要用来生成性能测试脚本Controller&#xff1a;创建测试场景&#xff0c;运行测试脚本、…

民用电力远程监控解决方案

民用电力远程监控解决方案 项目背景 随着我国城市现代化的飞速发展&#xff0c;城市配电系统的不断改造更新&#xff0c;信息化、网络化和智能化的快速发展&#xff0c;要求箱变安全稳定运行&#xff0c;出现故障能够及时排除保证快速供电。 但是&#xff0c;电力行业的监控…

如何挖掘闲置硬件资源的潜力-PrestoDB缓存加速实践小结

用户体验的追求是无限的&#xff0c;而成本是有限的&#xff0c;如何平衡&#xff1f; 用户体验很重要&#xff0c;降本也很重要。做技术的都知道&#xff0c;加机器堆资源可以解决绝大多数的用户觉得慢的问题&#xff0c;但要加钱。没什么用户体验是开发不了的&#xff0c;但…

阿里高P谈内卷,基础牢固才能破局,你的技术栈深度跟广度真的够么?

​ ​ 最近内卷严重&#xff0c;各种跳槽裁员&#xff0c;分享一套学习笔记 / 面试手册&#xff0c;准备跳槽的朋友可以好好刷一刷&#xff0c;还是挺有必要的&#xff0c;它几乎涵盖了所有的软件测试技术栈&#xff0c;非常珍贵&#xff0c;肝完进大厂&#xff01;妥妥的。相信…

PID算法(位置式pid算法和增量式pid算法)

这里写目录标题 PID算法介绍比例环节比例积分环节比例积分微分环节 位置式PID增量式PIDPID参数整定采样周期选择PID参数整定方法![请添加图片描述](https://img-blog.csdnimg.cn/849bf1672243484699b131b487f05a55.png)试凑法临界比例法一般调节法 PID算法介绍 PID 算法是闭环…

使用Process Monitor探测Windows系统高DPI缩放设置的注册表项

目录 1、在高显示比例下部分软件界面显示模糊问题 2、如何设置才能使得软件显示的清晰一些&#xff1f; 3、使用Process Monitor监测上述设置对应的注册表的操作 4、最后 VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&#xff…

分布式 04 nginx 的使用

01.Nginx可以理解成为一个代理运营商在计算机网络中。用户发送的请求在Nginx中处理&#xff0c;而后分配给相关的服务器 02.在Nginx文件中conf文件件&#xff0c;中修改nginx.conf文件 首先先去监听和获取请求&#xff0c;使用关键字server 这个是浏览器url中输入localhost时…

如何在本地部署运行ChatGLM-6B

在本篇技术博客中&#xff0c;将展示如何在本地获取运行代码和模型&#xff0c;并配置环境以及 Web GUI&#xff0c;最后通过 Gradio 的网页版 Demo 进行聊天。 官方介绍 ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM)…

Flutter 自定义裁剪之圆形豁口/缺口

目录 Flutter自定义裁剪Flutter的自定义裁剪类CustomClipper裁剪的实际代码思路分析注意点完整代码总结如图所示,图中的圆形缺口,需要我们自定义裁剪,才能实现。 Flutter自定义裁剪 裁剪,我们想到的是剪刀,实际上,Flutter的裁剪原理,和我们现实物理世界的剪刀是一样的…

木夕的IC日记——Vim使用【一】

Vim使用日记【一】 Vim的运行方式进入Vim第一步&#xff1a;打开文件保存文件并退出Vim三种模式下能做哪些事命令模式编辑模式底行模式Visual Block功能 Vim的运行方式 作为Linux系统中最常用的文本编辑器&#xff0c;Vim体现了Linux“万物皆是文件”的设计哲学。通过Vim&…

flink集群安装部署

1.下载 官网下载&#xff1a;Downloads | Apache Flink 阿里网盘下载&#xff08;包含依赖包&#xff09;&#xff1a;阿里云盘分享 提取码&#xff1a;9bl2 2.解压 tar -zxvf flink-1.12.7-bin-scala_2.11.tgz -C ../opt/module 3.修改配置文件 cd flink-1.12.7/conf/ …

[C++]string的使用

目录 string的使用&#xff1a;&#xff1a; 1.string类介绍 2.string常用接口说明 string相关习题训练&#xff1a;&#xff1a; 1.仅仅反转字母 2.找字符串中第一个只出现一次的字符 3.字符串里面最后一个单词的长度 4.验证一个字符串是否是回文 5.字符串相加 6.翻转字符串…