JVM学习笔记之方法区

目录

背景

栈、堆、方法区三者间的交互关系

方法区的理解

设置方法区大小与OOM

方法区的内部结构

方法区使用举例

方法区演进细节

方法区的GC

总结

背景

整理一下关于JVM方法区的学习笔记,基于jdk8,所以方法区的实际实现都是堆中的元空间

栈、堆、方法区三者间的交互关系

三者在运行时数据区的中的分布如下图所示

JVM学习笔记之方法区_第1张图片

从是否线程私有的角度上看的关系如下图所示,其中元空间是数据区的实现

JVM学习笔记之方法区_第2张图片

三者的交互关系如下图所示

JVM学习笔记之方法区_第3张图片

其中的reference就是person变量,对象实例数据就是new Person()创建出来的对象,对象类型数据就是Person类

方法区的理解

线程共享,存储每个类的运行时常量池、属性和方法的数据、方法和构造器的字节码、包括特殊方法、类的初始化和接口的初始化

方法区在JVM启动时就创建了,不必实现GC或压缩

 

jdk8的方法区独立于堆,也没有要求管理字节码的管理策略

和堆一样,大小可以固定也可以自适应,物理地址可以不连续

方法区的大小决定了系统可以保存多少个类,如果定义或加载了太多的类,就会导致OOM

 

关闭JVM就会释放方法区的内存

设置方法区大小与OOM

dk8及以后,设置参数为-XX:MetaspaceSize和-XX:MaxMetaspaceSize,分别设置元空间大小和最大元空间大小

默认值依赖于平台,windows下,前者默认值为21M,后者值为-1,表示无限制

D:\develop\ideaWorkspace\kafka-demo\target\classes\jvm\heap>jps
20208 RemoteMavenServer36
18148 Launcher
4900
15432 Jps
18696 HeapDemo0


D:\develop\ideaWorkspace\kafka-demo\target\classes\jvm\heap>jinfo -flag MetaspaceSize 18696
-XX:MetaspaceSize=21807104


D:\develop\ideaWorkspace\kafka-demo\target\classes\jvm\heap>jinfo -flag MaxMetaspaceSize 18696
-XX:MaxMetaspaceSize=18446744073709486080

设置时,格式为-XX:MetaspaceSize=100M, -XX:MaxMetaspaceSize=100M

 

-XX:MetaspaceSize值是一个水位线,当堆空间大小超过这个值,就会触发一个Full GC。Full GC后,如果释放的内存不大,就会提高-XX:MetaspaceSize值;如果释放了很大内存,就会降低-XX:MetaspaceSize值。一般将此值设置得比较高。

 

方法区的OOM实例,利用以下代码不断创建类,提前设置好元空间大小和最大大小都是100M

package jvm.heap;

import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.Opcodes;

public class HeapDemo0 extends ClassLoader {


    public static void main(String[] args) throws InterruptedException {
        System.out.println("start..");
        int i = 0;
        HeapDemo0 demo = new HeapDemo0();
        while (true) {


            ClassWriter writer = new ClassWriter(0);
            writer.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC,
                    "Class" + i, null, "java/lang/Object", null);
            // 创建类,给定jdk版本号、类访问修饰符、类名、包名、父类签名、实现接口
            byte[] code = writer.toByteArray();
            demo.defineClass("Class" + i, code,  0, code.length);
            // 定义类
            i++;
        }
    }
}

运行后,报错如下

start..
Exception in thread "main" java.lang.OutOfMemoryError: Metaspace
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:756)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:635)
    at jvm.heap.HeapDemo0.main(HeapDemo0.java:20)


Process finished with exit code 1

如何解决这些OOM:

1)、要解决元空间或堆空间的oom,一般先使用内存分析工具,对dump出来的堆转储快照进行分析,重点是确定内存中的对象是否是必要的,也就是要分清楚到底是发生了内存泄漏还是内存溢出

2)、如果内存泄漏,可进一步通过工具查看泄露对象到GC Roots的引用链,掌握对象的类型信息,以及引用链的信息,比较准确定位泄漏代码的位置

3)、如果时内存溢出,那就要调大堆空间和元空间大小,或者缩短对象的生命周期

方法区的内部结构

运行时数据区的内部结构如下图所示

JVM学习笔记之方法区_第4张图片

详细的方法区内部结构如下图所示,可见方法区存储已经被JVM加载的类型信息、常量、静态变量、JIT编译后的代码缓存等

 

JVM学习笔记之方法区_第5张图片

 

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

1)、类的全限定类名(包名.类名)

2)、直接父类的完整有效名(对于接口或java.lang.Object,都没有父类)

3)、访问修饰符(public、abstract、final等)

4)、实现接口的有序列表

 

域信息:属性名、属性类型、修饰符(public、static、volatile、transient等)

 

方法信息:方法名称、返回类型、参数数量和类型(按顺序)、修饰符(public、native等)、字节码、操作数栈、局部变量表及大小(abstract和native方法除外)、异常表(abstract和native方法除外)

 

以以下代码为例

package jvm.heap.methodArea;


import java.io.Serializable;


public class InnerStructureTest extends Object implements Comparable, Serializable {
    private String s = "s";
    public static int num = 0;


    public static void f() {


    }


    public void g(int i) {
        i++;
        int b = 7;
        int c = 9;
        try {
            i = b * c;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public int compareTo(String o) {
        return 0;
    }
}

对其字节码如下

D:\develop\ideaWorkspace\kafka-demo\target\classes\jvm\heap\methodArea>javap -v -p InnerStructureTest.class

Classfile /D:/develop/ideaWorkspace/kafka-demo/target/classes/jvm/heap/methodArea/InnerStructureTest.class
  Last modified 2020-5-15; size 1150 bytes
  MD5 checksum 08e84e5816db3cf034b9439033bec7f8
  Compiled from "InnerStructureTest.java"
public class jvm.heap.methodArea.InnerStructureTest extends java.lang.Object implements java.lang.Comparable, java.io.Serializable
  minor version: 0
  major version: 49
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #10.#41        // java/lang/Object."":()V
   #2 = String             #13            // s
   #3 = Fieldref           #9.#42         // jvm/heap/methodArea/InnerStructureTest.s:Ljava/lang/String;
   #4 = Class              #43            // java/lang/Exception
   #5 = Methodref          #4.#44         // java/lang/Exception.printStackTrace:()V
   #6 = Class              #45            // java/lang/String
   #7 = Methodref          #9.#46         // jvm/heap/methodArea/InnerStructureTest.compareTo:(Ljava/lang/String;)I
   #8 = Fieldref           #9.#47         // jvm/heap/methodArea/InnerStructureTest.num:I
   #9 = Class              #48            // jvm/heap/methodArea/InnerStructureTest
  #10 = Class              #49            // java/lang/Object
  #11 = Class              #50            // java/lang/Comparable
  #12 = Class              #51            // java/io/Serializable
  #13 = Utf8               s
  #14 = Utf8               Ljava/lang/String;
  #15 = Utf8               num
  #16 = Utf8               I
  #17 = Utf8               
  #18 = Utf8               ()V
  #19 = Utf8               Code
  #20 = Utf8               LineNumberTable
  #21 = Utf8               LocalVariableTable
  #22 = Utf8               this
  #23 = Utf8               Ljvm/heap/methodArea/InnerStructureTest;
  #24 = Utf8               f
  #25 = Utf8               g
  #26 = Utf8               (I)V
  #27 = Utf8               e
  #28 = Utf8               Ljava/lang/Exception;
  #29 = Utf8               i
  #30 = Utf8               b
  #31 = Utf8               c
  #32 = Utf8               compareTo
  #33 = Utf8               (Ljava/lang/String;)I
  #34 = Utf8               o
  #35 = Utf8               (Ljava/lang/Object;)I
  #36 = Utf8               
  #37 = Utf8               Signature
  #38 = Utf8               Ljava/lang/Object;Ljava/lang/Comparable;Ljava/io/Serializable;
  #39 = Utf8               SourceFile
  #40 = Utf8               InnerStructureTest.java
  #41 = NameAndType        #17:#18        // "":()V
  #42 = NameAndType        #13:#14        // s:Ljava/lang/String;
  #43 = Utf8               java/lang/Exception
  #44 = NameAndType        #52:#18        // printStackTrace:()V
  #45 = Utf8               java/lang/String
  #46 = NameAndType        #32:#33        // compareTo:(Ljava/lang/String;)I
  #47 = NameAndType        #15:#16        // num:I
  #48 = Utf8               jvm/heap/methodArea/InnerStructureTest
  #49 = Utf8               java/lang/Object
  #50 = Utf8               java/lang/Comparable
  #51 = Utf8               java/io/Serializable
  #52 = Utf8               printStackTrace
{
  private java.lang.String s;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE


  public static int num;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC


  public jvm.heap.methodArea.InnerStructureTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: aload_0
         5: ldc           #2                  // String s
         7: putfield      #3                  // Field s:Ljava/lang/String;
        10: return
      LineNumberTable:
        line 5: 0
        line 6: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  this   Ljvm/heap/methodArea/InnerStructureTest;


  public static void f();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=0, locals=0, args_size=0
         0: return
      LineNumberTable:
        line 11: 0


  public void g(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=5, args_size=2
         0: iinc          1, 1
         3: bipush        7
         5: istore_2
         6: bipush        9
         8: istore_3
         9: iload_2
        10: iload_3
        11: imul
        12: istore_1
        13: goto          23
        16: astore        4
        18: aload         4
        20: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V
        23: return
      Exception table:
         from    to  target type
             9    13    16   Class java/lang/Exception
      LineNumberTable:
        line 14: 0
        line 15: 3
        line 16: 6
        line 18: 9
        line 21: 13
        line 19: 16
        line 20: 18
        line 22: 23
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           18       5     4     e   Ljava/lang/Exception;
            0      24     0  this   Ljvm/heap/methodArea/InnerStructureTest;
            0      24     1     i   I
            6      18     2     b   I
            9      15     3     c   I


  public int compareTo(java.lang.String);
    descriptor: (Ljava/lang/String;)I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: iconst_0
         1: ireturn
      LineNumberTable:
        line 25: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       2     0  this   Ljvm/heap/methodArea/InnerStructureTest;
            0       2     1     o   Ljava/lang/String;


  public int compareTo(java.lang.Object);
    descriptor: (Ljava/lang/Object;)I
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: checkcast     #6                  // class java/lang/String
         5: invokevirtual #7                  // Method compareTo:(Ljava/lang/String;)I
         8: ireturn
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Ljvm/heap/methodArea/InnerStructureTest;


  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: iconst_0
         1: putstatic     #8                  // Field num:I
         4: return
      LineNumberTable:
        line 7: 0
}
Signature: #38                          // Ljava/lang/Object;Ljava/lang/Comparable;Ljava/io/Serializable;
SourceFile: "InnerStructureTest.java"

其中下面内容为类信息

public class jvm.heap.methodArea.InnerStructureTest extends java.lang.Object implements java.lang.Comparable, java.io.Serializable

包括类的全限定类型、直接父类全限定类名、实现的接口的全限定类名以及泛型的全限定类名,对应

public class InnerStructureTest extends Object implements Comparable, Serializable {..}

然后以下为域信息

  private java.lang.String s;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE

  public static int num;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC

描述的是类的字段,包括字段访问修饰符、类型全限定名、字段名;

下面的就是方法信息了,比如默认的构造方法

  public jvm.heap.methodArea.InnerStructureTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: aload_0
         5: ldc           #2                  // String s
         7: putfield      #3                  // Field s:Ljava/lang/String;
        10: return
      LineNumberTable:
        line 5: 0
        line 6: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  this   Ljvm/heap/methodArea/InnerStructureTest;

指明了方法全限定名、方法描述信息、方法修饰符、字节码、行号表、局部变量表

方法描述信息格式为(参数列表)返回类型,V表示void

字节码第一行分别表示操作数栈最大深度、局部变量表大小、参数数量(非静态方法多一个this),下面才是真正的方法字节码

行号表描述的是字节码行号和代码行号的对应关系

局部变量表描述的是局部变量的作用范围、槽号、名字、类型签名等信息

 

静态方法f的方法信息

  public static void f();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=0, locals=0, args_size=0
         0: return
      LineNumberTable:
        line 11: 0

可见参数数量args_size为0

 

带有异常的方法信息

  public void g(int);
    descriptor: (I)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=5, args_size=2
         0: iinc          1, 1
         3: bipush        7
         5: istore_2
         6: bipush        9
         8: istore_3
         9: iload_2
        10: iload_3
        11: imul
        12: istore_1
        13: goto          23
        16: astore        4
        18: aload         4
        20: invokevirtual #5                  // Method java/lang/Exception.printStackTrace:()V
        23: return
      Exception table:
         from    to  target type
             9    13    16   Class java/lang/Exception
      LineNumberTable:
        line 14: 0
        line 15: 3
        line 16: 6
        line 18: 9
        line 21: 13
        line 19: 16
        line 20: 18
        line 22: 23
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           18       5     4     e   Ljava/lang/Exception;
            0      24     0  this   Ljvm/heap/methodArea/InnerStructureTest;
            0      24     1     i   I
            6      18     2     b   I
            9      15     3     c   I

方法字节码里有个异常表,表示从字节码9行到字节码13行有异常,异常类型为java/lang/Exception,捕获异常跳到字节码16行

 

非final类变量

首先静态公有变量或方法可以被对应类型的null对象访问

package jvm.heap.methodArea;

public class InnerStructureTest {

    public static void main(String[] args) {
        Order o = null;
        o.show();
    }
}


class Order {
    public static int count = 1;
    public static final int number = 2;


    public static void show() {
        System.out.println("....");
    }
}

运行结果如下所示

....

Process finished with exit code 0

其中,对Order类的字节码如下所示

D:\develop\ideaWorkspace\kafka-demo\target\classes\jvm\heap\methodArea>javap -v -p Order.class

Classfile /D:/develop/ideaWorkspace/kafka-demo/target/classes/jvm/heap/methodArea/Order.class
  Last modified 2020-5-15; size 622 bytes
  MD5 checksum 8f3559bda36bdc6bc875d21370632a12
  Compiled from "InnerStructureTest.java"
class jvm.heap.methodArea.Order
  minor version: 0
  major version: 49
  flags: ACC_SUPER
Constant pool:
   #1 = Methodref          #7.#24         // java/lang/Object."":()V
   #2 = Fieldref           #25.#26        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #27            // ....
   #4 = Methodref          #28.#29        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Fieldref           #6.#30         // jvm/heap/methodArea/Order.count:I
   #6 = Class              #31            // jvm/heap/methodArea/Order
   #7 = Class              #32            // java/lang/Object
   #8 = Utf8               count
   #9 = Utf8               I
  #10 = Utf8               number
  #11 = Utf8               ConstantValue
  #12 = Integer            2
  #13 = Utf8               
  #14 = Utf8               ()V
  #15 = Utf8               Code
  #16 = Utf8               LineNumberTable
  #17 = Utf8               LocalVariableTable
  #18 = Utf8               this
  #19 = Utf8               Ljvm/heap/methodArea/Order;
  #20 = Utf8               show
  #21 = Utf8               
  #22 = Utf8               SourceFile
  #23 = Utf8               InnerStructureTest.java
  #24 = NameAndType        #13:#14        // "":()V
  #25 = Class              #33            // java/lang/System
  #26 = NameAndType        #34:#35        // out:Ljava/io/PrintStream;
  #27 = Utf8               ....
  #28 = Class              #36            // java/io/PrintStream
  #29 = NameAndType        #37:#38        // println:(Ljava/lang/String;)V
  #30 = NameAndType        #8:#9          // count:I
  #31 = Utf8               jvm/heap/methodArea/Order
  #32 = Utf8               java/lang/Object
  #33 = Utf8               java/lang/System
  #34 = Utf8               out
  #35 = Utf8               Ljava/io/PrintStream;
  #36 = Utf8               java/io/PrintStream
  #37 = Utf8               println
  #38 = Utf8               (Ljava/lang/String;)V
{
  public static int count;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC


  public static final int number;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 2


  jvm.heap.methodArea.Order();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 34: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ljvm/heap/methodArea/Order;


  public static void show();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String ....
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 39: 0
        line 40: 8


  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: iconst_1
         1: putstatic     #5                  // Field count:I
         4: return
      LineNumberTable:
        line 35: 0
}
SourceFile: "InnerStructureTest.java"

对于静态属性count和静态最终属性number,域信息如下所示

  public static int count;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC


  public static final int number;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 2

其中number的值为ConstantValue。对于非final的count,可以根据静态字节码块(也就是)

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: iconst_1
         1: putstatic     #5                  // Field count:I
         4: return
      LineNumberTable:
        line 35: 0

中的如下字节码,为count赋值为1,并设置为静态

         0: iconst_1
         1: putstatic     #5                  // Field count:I

运行时常量池与常量区

运行时常量池所在位置如下图所示

JVM学习笔记之方法区_第6张图片

常量池,就是字节码中的Constant Pool部分

Constant pool:
   #1 = Methodref          #7.#24         // java/lang/Object."":()V
   #2 = Fieldref           #25.#26        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #27            // ....
   #4 = Methodref          #28.#29        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Fieldref           #6.#30         // jvm/heap/methodArea/Order.count:I
   #6 = Class              #31            // jvm/heap/methodArea/Order
   #7 = Class              #32            // java/lang/Object
   #8 = Utf8               count
   #9 = Utf8               I
  #10 = Utf8               number
  #11 = Utf8               ConstantValue
  #12 = Integer            2
  #13 = Utf8               
  #14 = Utf8               ()V
  #15 = Utf8               Code
  #16 = Utf8               LineNumberTable
  #17 = Utf8               LocalVariableTable
  #18 = Utf8               this
  #19 = Utf8               Ljvm/heap/methodArea/Order;
  #20 = Utf8               show
  #21 = Utf8               
  #22 = Utf8               SourceFile
  #23 = Utf8               InnerStructureTest.java
  #24 = NameAndType        #13:#14        // "":()V
  #25 = Class              #33            // java/lang/System
  #26 = NameAndType        #34:#35        // out:Ljava/io/PrintStream;
  #27 = Utf8               ....
  #28 = Class              #36            // java/io/PrintStream
  #29 = NameAndType        #37:#38        // println:(Ljava/lang/String;)V
  #30 = NameAndType        #8:#9          // count:I
  #31 = Utf8               jvm/heap/methodArea/Order
  #32 = Utf8               java/lang/Object
  #33 = Utf8               java/lang/System
  #34 = Utf8               out
  #35 = Utf8               Ljava/io/PrintStream;
  #36 = Utf8               java/io/PrintStream
  #37 = Utf8               println
  #38 = Utf8               (Ljava/lang/String;)V

运行时常量池就是程序运行时的常量池,把符号地址转换为真实地址,其具备动态性,可以动态加载新的常量到常量池中。

 

常量池表包含了各种字面量(2、out、....、println等)、类型、属性和方法的符号引用(#36、#37:#38等)

常量池的存在意义:减少字节码文件大小,而且动态链接时会使用运行时常量池,参见我的文章JVM学习笔记上(概述-本地方法栈)里虚拟机栈中相关内容

方法区使用举例

参加我的文章JVM学习笔记上(概述-本地方法栈)里虚拟机栈的操作数栈部分

方法区演进细节

在jdk7及以前,习惯上把方法区称为永久代;jdk8开始,使用元空间取代了永久代。其他JVM,比如JRockit和J9里,就没有永久代的概念

当年使用永久代,容易导致程序OOM(超过-XX:MaxPermSize)

下面是jdk7和jdk8的堆和方法区的结构图JVM学习笔记之方法区_第7张图片

 

 

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

 

jdk1.6、1.7和1.8中方法区的变化

JVM学习笔记之方法区_第8张图片

jdk6的运行时数据区结构如下图所示

JVM学习笔记之方法区_第9张图片

jdk7的运行时数据区如下图所示,把静态常量和StringTable(字面量表)移到了堆中,这是因为永久代回收效率很低,把字面量表移动到堆中,可以提高它的回收效率,及时回收内存

JVM学习笔记之方法区_第10张图片

jdk8的运行时数据区如下图所示,把永久代从虚拟机内存移出到本地内存中,命名为元空间

JVM学习笔记之方法区_第11张图片

 

把永久代换成元空间的原因:

1)、这是融合JRockit和HotSpot工作的一部分

2)、为永久代设置的空间大小是很难确定的:如果动态加载类过多,就会出现永久代的OOM;而元空间的大小仅受本地内存限制

3)、对永久代进行调优很困难

 

以如下代码为例,说明静态对象属性、对象属性和局部对象属性的对象和引用存放在哪里

public class StaticFieldTest {
    private static String sTag = "tag";
    private String name = "name";


    public void f() {
        String str = "s";
        System.out.println(str);
    }
}

三个字符串对象,都在堆里;静态属性sTag,随着Class对象存放在堆中;普通属性name,随着StaticFieldTest对象放到堆里;局部变量str,存放到f方法栈帧中的局部变量表里

方法区的GC

方法区可以不实现GC,如果要实现GC,那么主要回收不再使用的类型和常量池中废弃的常量

 

判断常量是否不再使用:只要在任何地方都没有引用,就可以被回收

 

判断一个类型是否不再使用,有下面三个条件:

1)、此类所有实例都已经被回收,也就是堆中不存在此类及其子类的实例

2)、加载此类的类加载器已经被回收,此条件一般达不成,除非是精心设计过的可替换类加载器的场景,如OSGi、JSP的重加载等

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

满足上面三个条件后,还要根据参数-Xnoclassgc值决定是否回收废弃的类

 

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

总结

运行时数据区的内存结构如下所示

JVM学习笔记之方法区_第12张图片

下一篇笔记是关于对象实例化的

你可能感兴趣的:(JVM)