方法区

113 阅读5分钟

什么是方法区

jvm内存结构 image-20220615162502611

image-20220615162527587

image-20220615162502611 image

是jvm定义的一种规范

hotspot中1.7的实现方式是永久代,1.8改为了元空间

元空间不在虚拟机设置的内存中,而是使用物理内存

方法区里存了什么

Person persion = new Persion();
方法区   栈         堆

方法区

  • 元数据 - 本地内存的原空间 (类信息,方法)
  • 常量池 - 堆中 (字符串常量池,静态变量)

常量池

  • 每个class文件都有一个class常量池。
  • 字面量包括:1.文本字符串 2.八种基本类型的值 3.被声明为final的常量等;
  • 符号引用包括:1.类和方法的全限定名 2.字段的名称和描述符 3.方法的名称和描述符。
  1. JDK1.8 HotSpot 移除了永久代用元空间(Metaspace)取而代之, 这时候字符串常量池和静态变量还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace)

如何理解方法区

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

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

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

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

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

    • 怎么会导致方法区的OOM呢?

      • 方法区主要是存储类信息,那么当加载大量的jar包:Tomcat部署的工程过多(30-50)个;大量动态的生成反射类都会造成OOM
  • 关闭JVM会释放这个区域的内存

  • 方法区的垃圾回收的目标主要包括:**常量池中废弃的常量和不再使用的类型。

如何设置方法区

JDK7及以前:叫永久代

  • 通过-XX:PermSize来设置永久代初始分配空间。默认是20.75M
  • -XX:MaxPermSize来设定永久代最大可分配空间。32位机器默认是64M,64位机器模式是82M。
  • 当JVM加载的类信息容量超过了这个值,会报异常OutOfMemoryError:PermGen space。

JDK8及以后:叫元空间

  • 元数据区大小可以使用参数-XX:MetaspaceSize-XX:MaxMetaspaceSize指定,替代上述原有的两个参数

  • 默认值依赖于平台。windows下,-XX:MetaspaceSize是21M(近似,实际上20.75多)-XX:MaxMetaspaceSize的值是-1,即没有限制

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

  • -XX:MetaspaceSize
    

    :设置初始的元空间大小。对于一个64位的服务器端JVM来说,其默认的

    -XX:MetaspaceSize
    

    值接近21M。这就是初始的高水位线,一旦触及这个水位线,Full GC将会被处罚并卸载没用的类(即这些类对应的类加载器不再存活),然后这个高水位线将会重置。新的高水位线的值取决于GC后释放了多少元空间。如果释放的空间不足,那么在不超过MaxMetaspaceSize时,适当提高该值。如果释放空间过多,则适当降低该值

    • 如果初始化的高水位线设置过低,上述高水位线调整情况会发生很多次。通过垃圾回收器的日志可以观察到Full GC多次调用。为了避免频繁地GC,建议将-XX:MetaspaceSize设置为一个相对较高的值。

为什么要用元空间代替永久代

用元空间替换永久代的原因:

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

    • 在某些场景下,比如web工程中,需要动态的加载大量的jar包和类,那么就可能会出现OOM问题。
  2. 对于永久代进行调优是很苦难的。

    • 当永久代满,就需要发生GC,而永久代的GC可能会发生较长时间STW,那么就是损失性能的。

字符串常量池

1.为什么有字符串常量池

  1. 实现这种设计的一个很重要的因素是:String类型是不可变的,实例化后,不可变,就不会存在多个同样的字符串实例化后有数据冲突;
  2. 运行时,实例创建的全局字符串常量池中会有一张表,记录着长相持中每个唯一的字符串对象维护一个引用,当垃圾回收时,发现该字符串被引用时,就不会被回收。

2.创建流程

对于jvm底层,String str = new String("123")创建对象流程是什么?

  1. 在常量池中查找是否存在"123"这个字符串;若有,则返回对应的引用实例;若无,则创建对应的实例对象;
  2. 在堆中new一个String类型的"123"字符串对象;
  3. 将对象地址复制给str,然后创建一个应用。

3.几个对象

String str ="ab" + "cd";           // 1个,在常量池
String str = new String("abc");    // 2个,在堆 + 常量池
String str = new String("a" + "b");// 4个,常量池:a,b,ab 堆ab