image.png

    方法区的理解
    Java虚拟机规范中:尽管所有的方法区在逻辑上是属于堆的一部分,但是一些简单的实现可能不会选择去进行垃圾收集器或者进行压缩,但是对于HotSpot而言,方法区还有一个别名(Non-Heap堆),目的就是要和堆分离开;
    所以:方法区可以看做是一块独立于Java堆的内存空间
    1.方法区(Method Area)与Java堆一样,是各个线程共享的内存区域;
    2.方法区在JVM启动的时候即被创建,并且它的实际物理内存空间中和Java堆区一样都可以是不连续的;
    3.方法区的大小,跟堆空间一样,可以选择固定大小或者可扩展
    4.方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致了方法区溢出,虚拟机会抛出内存溢出错误:java.lang.OutOfMemoryError:PermGen space或者java.lang.OutOfMemoryError:Metaspace
    5.关闭JVM就会释放这个区域的内存
    HotSpot中方法区的演进
    在jdk7即之前,习惯性把方法区,称为永久代,jdk8开始,使用元空间取代了永久代
    从本质上来说,方法区和永久代来说并不是等价的关系;因为只有HotSpot有永久代的概念,像BEA JRokit/IBM J9不存在永久代的概念

    JDK8由于Oracle收购了JRockit以后,将HotSpot和JRockit进行了整合,移除了永久代(Permant Generation),引进了元空间(MetaSpace),以下是jdk7和jdk8的jvm内存结构图:
    image.png
    元空间的本质和永久代是类似的:都是对JVM规范中方法区的实现.不过元空间与永久代最大的区别在于:元空间不在虚拟机设置的内存中,而是使用的本地内存.
    根据java虚拟机规范规定:如果方法区无法满足新的内存的分配需求,将会抛出OOM异常!

    设置方法区大小与OOM
    jdk7及以前:通过-XX:PermSize来设置永久代初始分配空间.默认值是20.75M ;-XX:MaxPermSize来设定永久代最大可分配空间 ;当JVM加载的类信息容量超过了这个值,会报异常:OutOfMemoryError:PerGenspace
    jdk8及以后:元数据区大小和最大可分配空间可以使用参数-XX:Metaspace和-XX:MaxMetespaceSize来指定,默认windows下-XX:Metaspace为21M,-XX:MaxMetespaceSize为-1,表示没有任何限制;与永久代不同的是如果不指定大小,默认情况下,虚拟机会耗尽所有的可用系统内存,如果元数据区发生溢出,则会一样抛出异常:OutOfMemory:Metaspace
    image.png

    如何解决OOM异常?
    image.png
    方法区的内部结构
    image.png
    类型信息
    对每个加载的类型(类Class,接口Interface,枚举enum,注解annotation),JVM必须在方法区中存储以下

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

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

    方法信息:
    JVM必须保存所有方法的以下信息,同域信息一样包括声明的顺序:
    1.方法的名称
    2.方法的返回类型(或者void)
    3.方法参数的数量和类型(按照顺序)
    4.方法的修饰符(public,private,protected,static,final,synchronized,native,abstract的一个子集)
    5.方法的字节码(bytecodes,操作数栈,局部变量表即大小(abstract和native方法除外))
    6.异常表(abstract和native方法除外)
    每个异常处理的开始位置,结束位置,代码处理在程序计数器中的偏移地址,被捕获的异常类的常量池索引

    PS:补充两个概念:non-final的类变量和final的类变量
    non-final的类变量:
    1.静态变量和类变量关联在一起,随着类的加载而加载,他们成为了类数据在逻辑上的一部分
    2.类变量(即静态变量)被类的所有实例共享,即使没有类实例时你也可以访问它
    final的类变量
    被声明为final的类变量的处理方法则不同,每个全局变量在编译的时候就已经分配好了

    运行时常量池VS常量池
    在方法区中,内部包括运行时常量池,而字节码文件中包括常量池
    对于一个有效的字节码文件来说,里面除了包含类的版本信息,字段,方法以及接口等描述信息以外,还包含一项信息,那就是常量池(Constant Pool Table),包括各种字面量和对类型.域和方法的符号引用,如下图所示:
    image.png
    那为什么字节码文件中需要常量池?
    一个java源文件中的类,接口,编译后产生一个字节码文件.而Java中的字节码需要数据支持,通常这种数据会很大以至于不能直接存到字节码文件中去,换成另一种方式,可以存到常量池中去,这个字节码包含了指向常量池的引用.在前面栈帧的一个组成部分—动态链接—就会使用到这个常量池;
    那么常量池里面又有什么东西呢?
    1.数量值
    2.字符串值
    3.类引用
    4.字段引用
    5.方法引用
    总结一下:
    常量池可以看做是一张表,虚拟机指令根据这张常量表找到了要执行的类名,方法名,参数类型,字面量等类型;
    接下来,我们再来讲解以下运行时常量池:
    1.运行时常量池(Runtime Constant Pool)是方法区的一部分;
    2.常量池是Class文件的一部分,用来存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中;
    3.运行时常量池,在加载类和接口到虚拟机后,就会创建对应的运行时常量池
    4.JVM为每一个已加载的类型(类和接口)都维护这一个常量池.池中的数据项向数组项一样,是通过索引进行访问的;
    5.运行时常量池中包含了多种不同的常量,包括编译期就已经确定下来的数值字面量,也包括到运行时期解析后才能够得到的方法或者字段引用等,此时不再是常量池中的符号引用了,这里换成了真实的地址.运行时常量池相对于class文件常量池的另一个重要特征:具备动态性
    6.运行时常量池类似传统编程中的符号表(Symbol table),但是它所包含的数据比符号表要更加的丰富
    7.当创建类或者接口的运行时常量池时,如果构造运行时常量池所需的内存空间超过了方法区提供的最大值,则会抛OutOfMemoryError异常.

    方法区的演进细节
    首先,需要明确:只有HotSpot才会有永久代!
    image.png
    永久代为什么要被元空间替换?
    回答:由于永久代存在着很多的弊端:
    1.为永久代设置空间大小是很困难的;某些场景下,如果动态加载的类过多,容易产生Perm区的OOM。而元空间和永久代最大的区别在于:元空间并不在虚拟机中,而是使用的本地的内存,因此,默认情况下,元空间的大小仅仅受到本地内存的限制。
    2.对永久代进行性能的调优是很困难的;如果采用永久代,则内存不足并不可少会触发Full GC,但这样会造成应用程序线程的停顿时间过长,对于性能是有很大的弊端的,而且对于Full GC,我们并不能进行多大程度的性能调优,而采用元空间,直接使用的就是本地的内存,则很大程度上的避免Full GC的发生。
    StringTable为什么要被调整?
    image.png
    静态变量放在哪里?
    静态引用的对象实体始终都存在堆空间中 !

    方法区的垃圾回收
    image.png
    常量池的垃圾回收:
    image.png
    类的垃圾回收:
    image.png