06-Method_Area

1. 栈、堆、方法区关系

image-20200708220303243

运行时数据区的Method_Area

image-20200708094507624

ThreadLocal:如何保证多个线程在并发环境下的安全性?典型应用就是数据库连接管理,以及会话管理

1. 对象的访问定位

image-20200708094747667
  • Person:存放在MetaSpace,也可以说MethodArea
  • person:存放在JVM_Stack的局部变量表中方法区的演进细节
  • new Person():存放在Heap中

Java没有选择句柄池

image-20230528090148963

2. 方法区理解

2.5.4. Method Areaopen in new window

2.5.4. Method Area
The Java Virtual Machine has a method area that is shared among all Java Virtual Machine threads. The method area is analogous to the storage area for compiled code of a conventional language or analogous to the "text" segment in an operating system process. It stores per-class structures such as the run-time constant pool, field and method data, and the code for methods and constructors, including the special methods (§2.9) used in class and instance initialization and interface initialization.

The method area is created on virtual machine start-up. Although the method area is logically part of the heap, simple implementations may choose not to either garbage collect or compact it. This specification does not mandate the location of the method area or the policies used to manage compiled code. The method area may be of a fixed size or may be expanded as required by the computation and may be contracted if a larger method area becomes unnecessary. The memory for the method area does not need to be contiguous.

A Java Virtual Machine implementation may provide the programmer or the user control over the initial size of the method area, as well as, in the case of a varying-size method area, control over the maximum and minimum method area size.

The following exceptional condition is associated with the method area:

If memory in the method area cannot be made available to satisfy an allocation request, the Java Virtual Machine throws an OutOfMemoryError.

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

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

Method_Area存放的是Klass,而Heap存放的是实例化的Obj

  • 方法区(Method Area)与Heap一样,是各个线程共享的内存区域
  • Method_Area在JVM启动时被创建,并且它的实际物理内存空间和Heap一样可以是不连续的
  • Method_Area的大小,跟Heap一样,可以选择固定大小或者可扩展
  • Method_Area的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,JVM同样会抛出内存溢出错误:
    • java.lang.OutOfMemoryError: PermGen space
    • java.lang.OutOfMemoryError: Metaspace
      • 加载大量的第三方的jar包
      • Tomcat部署的工程过多(30~50个)
      • 大量动态的生成反射类
  • 关闭JVM就会释放这个区域的内存

1. 方法区演进

  • 永久代、元空间都是对JVM规范中Method_Area的具体实现,最大的区别:元空间不在JVM设置的内存中,而是使用本地内存
    • JDK7及以前,习惯上把Method_Area,称为永久代
    • JDK8开始,完全废弃了永久代概念,改用和JRockit、J9一样在本地内存中实现元空间(Metaspace)来代替(堆外内存)

In JDK 8, classes metadata is now stored in the native heap and this space is called Metaspace.

  • 本质上,方法区和永久代并不等价。仅是对Hotspot而言的,BEA_JRockit、IBM_J9等,不存在永久代概念。《Java虚拟机规范》对如何实现方法区,不做统一要求
    • eg:BEA_JRockit / IBM J9 中不存在永久代的概念
  • 现在来看,当年使用永久代,不是好的idea。导致更容易OOM(超过-XX:MaxPermsize上限)
    • 根据《Java虚拟机规范》的规定,如果方法区无法满足新的内存分配需求时,将抛出OOM异常
image-20200708102919149
JDKpromotion
JDK1.6及以前有永久代。静态变量存储在永久代上
JDK1.7有永久代。逐步 “去永久代”,字符串常量池,静态变量移除,保存在堆中
JDK1.8无永久代。类型信息,字段,方法,常量保存在本地内存的元空间,字符串常量池、静态变量仍然在堆中
image-20200708211541300
image-20230601135837905

JDK8,元空间大小只受物理内存影响

image-20230601135916002

1. 永久代被元空间替代

JEP 122: Remove the Permanent Generationopen in new window

JEP 122: Remove the Permanent Generation

Description

// ...
The proposed implementation will allocate class meta-data in native memory and move interned Strings and class statics to the Java heap.
// ...

JRockit和HotSpot融合后的结果,JRockit没有永久代

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

  1. 因为永久代设置空间大小是很难确定的
    • 在某些场景下,如果动态加载类过多,容易产生Perm区的OOM
      • eg:某个实际Web工程中,因为功能点比较多,在运行过程中,要不断动态加载很多类,经常出现致命错误
      • Exception in thread‘dubbo client x.x connector'java.lang.OutOfMemoryError:PermGen space”

    • 元空间并不在JVM中,而是使用本地内存。因此,默认情况下,元空间的大小仅受本地内存限制
  2. 对永久代进行调优是很困难的
    • 主要是为了降低FullGC

2. MetaSpace

JDK8将原来存放Class元数据的Perm换成了元空间MetaSpace

  • MetaspaceSize:表示触发Metaspace区域GC阈值(在Linux下JDK8默认大小为20.79M)
  • MaxMetaspaceSize:表示Metaspace最大值(Linux下JDK8默认大小为18446744073709547520M,认为是无限大)

JDK8中MetaSpace大小 = Klass MetaSpace + NoKlass MetaSpace

  • Klass MetaSpace大小由CompressedClassSpaceSize指定
  • NoKlass MetaSpace大小则为 2 * InitialBootClassLoaderMetaspaceSize。即默认情况下MetaSpace = 1024M + 4M * 2 = 1032M

元数据区并没有使用JVM内存,而是直接使用OS的本地内存。因此,元空间大小不受JVM内存限制,只和OS内存有关

3. StringTable调整位置

  • JDK7中将StringTable放到了Heap中。因为永久代的回收效率很低,在FullGC时才会触发
  • 开发中会有大量的字符串被创建,回收效率低,导致永久代内存不足。放到堆里,能及时GC

3. 设置方法区大小

1. JDK7及以前

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

jinifo -flag PermSize 8856
# -XX:PermSize=21757952

jinifo -flag MaxPermSize 8856
# -XX:MaxPermSize=85983232

2. JDK8及以后

JDK8中PermSize被废弃

  • 元数据区默认值依赖于平台。windows下,-XX:MetaspaceSize是21M,-XX:MaxMetaspaceSize的值是-1,即没有限制
  • 与永久代不同,如果不指定大小,默认情况下,JVM会耗尽所有的可用系统内存。如果元数据区发生溢出,JVM一样会抛出异常OutOfMemoryError:Metaspace
  • -XX:MetaspaceSize:设置初始的元空间大小。对于一个64位的服务器端JVM来说,默认值为21MB。这就是初始的高水位线,一旦触及这个水位线,FullGC将会被触发并卸载没用的类(即这些类对应的类加载器不再存活),然后这个高水位线将会重置。新的高水位线的值取决于GC后释放了多少元空间。如果释放的空间不足,那么在不超过MaxMetaspaceSize时,适当提高该值。如果释放空间过多,则适当降低该值
    • 如果初始化的高水位线设置过低,上述高水位线调整情况会发生很多次。通过GC日志可以观察到FullGC多次调用。为了避免频繁地GC,建议将MetaspaceSize设置为一个相对较高的值
/**
 * 设置方法区大小及默认值
 * JDK7及以前:
 *      -XX:PermSize=100m -XX:MaxPermSize=100m
 *
 * JDK8及以后:
 *      -XX:MetaspaceSize=100m -XX:MaxMetaspaceSize=100m
 */
public class MethodAreaDemo {
    public static void main(String[] args) {
        System.out.println("start...");
//        try {
//            Thread.sleep(1_000_000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }

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

4. 如何解决OOM

要解决OOM异常或heap space异常,一般手段是首先通过内存映像分析工具(eg:Eclipse Memory Analyzer)对dump出来的《堆转储快照》进行分析,重点是确认内存中的Obj是否是必要的,即先分清楚是出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)

  • 内存泄漏,可进一步通过工具查看泄漏Obj到GC_Roots的引用链。于是就能找到泄漏Obj是通过怎样的路径与GC_Roots相关联并导致GC无法回收。掌握了泄漏Obj的类型信息,以及GC_Roots引用链的信息,就可以比较准确地定位出泄漏代码的位置
  • 内存溢出,内存中的Obj确实都还必须存活着,检查JVM的堆参数(-Xmx-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些Obj生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗
image-20230528183013125
// import com.sun.xml.internal.ws.org.objectweb.asm.ClassWriter;
// import jdk.internal.org.objectweb.asm.Opcodes;

import org.springframework.asm.ClassWriter;
import org.springframework.asm.Opcodes;

/*
 * JDK6/7中
 *      -XX:PermSize=10m -XX:MaxPermSize=10m
 * JDK8中
 *      -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m
 */
public class MethodOOM extends ClassLoader {

    public static void main(String[] args) {
        int j = 0;
        try {
            MethodOOM test = new MethodOOM();
            for (int i = 0; i < 10_000; i++) {
                // 创建ClassWriter对象,用于生成类的二进制字节码
                ClassWriter classWriter = new ClassWriter(0);
                // 指明版本号,修饰符,类名,包名,父类,接口
             	// JDK7用Opcodes.V1_6
                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);
        }
    }
}




















 


 

 

 







3266
Exception in thread "main" java.lang.OutOfMemoryError: Compressed class space
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
	at com.listao.jvm.chapter09.java.MethodOOM.main(MethodOOM.java:30)

5. 方法区内部结构

image-20200708161728320

《深入理解Java虚拟机》中对方法区(Method_Area)存储内容描述如下:

它用于存储已被JVM加载的类型信息、常量、静态变量、即时编译器编译后的代码缓存

  • Klass也会记录其ClassLoader,同时加载到Method_Area
  • ClassLoader也会记录加载了哪些类
image-20200708161856504

1. 类型信息

每个加载类型(类Class、接口Interface、枚举Enum、注解Annotation),JVM必须在方法区中存储以下类型信息:

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

2. 域(Field)信息

JVM必须在Method_Area中保存类型的

  • 所有域的相关信息
    • 域名称、域类型、域修饰符(public, private, protected, static, final, volatile, transient的子集)
  • 域的声明顺序

3. 方法(Method)信息

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

  • 方法名称
  • 方法的返回类型(或void)
  • 方法参数的数量和类型(按顺序)
  • 方法的修饰符(public, private, protected, static, final, synchronized, native, abstract的子集)
  • 方法的字节码(bytecodes)、操作数栈、局部变量表及大小(abstract, native方法除外)
  • 异常表(abstract, native方法除外)
    • 每个异常处理的开始位置、结束位置、代码处理在PC中的偏移地址、被捕获的异常类的常量池索引
# -p 权限比较小的(private)属性、方法
javap -v -p MethodInnerStrucTest.class > test.txt
image-20230530204847469
/**
 * 测试方法区的内部构成
 */
public class MethodInnerStrucTest extends Object implements Comparable<String>, Serializable {
    // 属性
    public int num = 10;
    private static String str = "测试方法的内部结构";

    // 构造器

    // 方法
    public void test1(){
        int count = 20;
        System.out.println("count = " + count);
    }

    public static int test2(int cal){
        int result = 0;
        try {
            int value = 30;
            result = value / cal;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    @Override
    public int compareTo(String o) {
        return 0;
    }
}
Classfile /Users/list/mca_proj/tmp/target/classes/com/listao/jvm/chapter09/java/MethodInnerStrucTest.class
  Last modified May 30, 2023; size 1652 bytes
  MD5 checksum 3ea406c18fff69b07ced8d520b58e28d
  Compiled from "MethodInnerStrucTest.java"

// 1. 类型信息
public class com.listao.jvm.chapter09.java.MethodInnerStrucTest extends java.lang.Object implements java.lang.Comparable<java.lang.String>, java.io.Serializable
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #18.#52        // java/lang/Object."<init>":()V
   #2 = Fieldref           #17.#53        // com/listao/jvm/chapter09/java/MethodInnerStrucTest.num:I
   #3 = Fieldref           #54.#55        // java/lang/System.out:Ljava/io/PrintStream;
   #4 = Class              #56            // java/lang/StringBuilder
   #5 = Methodref          #4.#52         // java/lang/StringBuilder."<init>":()V
   #6 = String             #57            // count =
   #7 = Methodref          #4.#58         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #8 = Methodref          #4.#59         // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
   #9 = Methodref          #4.#60         // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #10 = Methodref          #61.#62        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #11 = Class              #63            // java/lang/Exception
  #12 = Methodref          #11.#64        // java/lang/Exception.printStackTrace:()V
  #13 = Class              #65            // java/lang/String
  #14 = Methodref          #17.#66        // com/listao/jvm/chapter09/java/MethodInnerStrucTest.compareTo:(Ljava/lang/String;)I
  #15 = String             #67            // 测试方法的内部结构
  #16 = Fieldref           #17.#68        // com/listao/jvm/chapter09/java/MethodInnerStrucTest.str:Ljava/lang/String;
  #17 = Class              #69            // com/listao/jvm/chapter09/java/MethodInnerStrucTest
  #18 = Class              #70            // java/lang/Object
  #19 = Class              #71            // java/lang/Comparable
  #20 = Class              #72            // java/io/Serializable
  #21 = Utf8               num
  #22 = Utf8               I
  #23 = Utf8               str
  #24 = Utf8               Ljava/lang/String;
  #25 = Utf8               <init>
  #26 = Utf8               ()V
  #27 = Utf8               Code
  #28 = Utf8               LineNumberTable
  #29 = Utf8               LocalVariableTable
  #30 = Utf8               this
  #31 = Utf8               Lcom/listao/jvm/chapter09/java/MethodInnerStrucTest;
  #32 = Utf8               test1
  #33 = Utf8               count
  #34 = Utf8               test2
  #35 = Utf8               (I)I
  #36 = Utf8               value
  #37 = Utf8               e
  #38 = Utf8               Ljava/lang/Exception;
  #39 = Utf8               cal
  #40 = Utf8               result
  #41 = Utf8               StackMapTable
  #42 = Class              #63            // java/lang/Exception
  #43 = Utf8               compareTo
  #44 = Utf8               (Ljava/lang/String;)I
  #45 = Utf8               o
  #46 = Utf8               (Ljava/lang/Object;)I
  #47 = Utf8               <clinit>
  #48 = Utf8               Signature
  #49 = Utf8               Ljava/lang/Object;Ljava/lang/Comparable<Ljava/lang/String;>;Ljava/io/Serializable;
  #50 = Utf8               SourceFile
  #51 = Utf8               MethodInnerStrucTest.java
  #52 = NameAndType        #25:#26        // "<init>":()V
  #53 = NameAndType        #21:#22        // num:I
  #54 = Class              #73            // java/lang/System
  #55 = NameAndType        #74:#75        // out:Ljava/io/PrintStream;
  #56 = Utf8               java/lang/StringBuilder
  #57 = Utf8               count =
  #58 = NameAndType        #76:#77        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #59 = NameAndType        #76:#78        // append:(I)Ljava/lang/StringBuilder;
  #60 = NameAndType        #79:#80        // toString:()Ljava/lang/String;
  #61 = Class              #81            // java/io/PrintStream
  #62 = NameAndType        #82:#83        // println:(Ljava/lang/String;)V
  #63 = Utf8               java/lang/Exception
  #64 = NameAndType        #84:#26        // printStackTrace:()V
  #65 = Utf8               java/lang/String
  #66 = NameAndType        #43:#44        // compareTo:(Ljava/lang/String;)I
  #67 = Utf8               测试方法的内部结构
  #68 = NameAndType        #23:#24        // str:Ljava/lang/String;
  #69 = Utf8               com/listao/jvm/chapter09/java/MethodInnerStrucTest
  #70 = Utf8               java/lang/Object
  #71 = Utf8               java/lang/Comparable
  #72 = Utf8               java/io/Serializable
  #73 = Utf8               java/lang/System
  #74 = Utf8               out
  #75 = Utf8               Ljava/io/PrintStream;
  #76 = Utf8               append
  #77 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #78 = Utf8               (I)Ljava/lang/StringBuilder;
  #79 = Utf8               toString
  #80 = Utf8               ()Ljava/lang/String;
  #81 = Utf8               java/io/PrintStream
  #82 = Utf8               println
  #83 = Utf8               (Ljava/lang/String;)V
  #84 = Utf8               printStackTrace
{
  // 2. 域信息
  public int num;
    descriptor: I
    flags: ACC_PUBLIC

  private static java.lang.String str;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE, ACC_STATIC

  // 3. 方法信息(构造器)
  public com.listao.jvm.chapter09.java.MethodInnerStrucTest();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: bipush        10
         7: putfield      #2                  // Field num:I
        10: return
      LineNumberTable:
        line 8: 0
        line 10: 4
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      11     0  this   Lcom/listao/jvm/chapter09/java/MethodInnerStrucTest;

  public void test1();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
    	// 3.1. 操作数栈、局部变量表
      stack=3, locals=2, args_size=1
         0: bipush        20
         2: istore_1
         3: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         6: new           #4                  // class java/lang/StringBuilder
         9: dup
        10: invokespecial #5                  // Method java/lang/StringBuilder."<init>":()V
        13: ldc           #6                  // String count =
        15: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        18: iload_1
        19: invokevirtual #8                  // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        22: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        25: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        28: return
      LineNumberTable:
        line 16: 0
        line 17: 3
        line 18: 28
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      29     0  this   Lcom/listao/jvm/chapter09/java/MethodInnerStrucTest;
            3      26     1 count   I

  public static int test2(int);
    descriptor: (I)I
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=3, args_size=1
         0: iconst_0
         1: istore_1
         2: bipush        30
         4: istore_2
         5: iload_2
         6: iload_0
         7: idiv
         8: istore_1
         9: goto          17
        12: astore_2
        13: aload_2
        14: invokevirtual #12                 // Method java/lang/Exception.printStackTrace:()V
        17: iload_1
        18: ireturn
      // 3.2. 异常表
      Exception table:
         from    to  target type
             2     9    12   Class java/lang/Exception
      LineNumberTable:
        line 21: 0
        line 23: 2
        line 24: 5
        line 27: 9
        line 25: 12
        line 26: 13
        line 28: 17
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            5       4     2 value   I
           13       4     2     e   Ljava/lang/Exception;
            0      19     0   cal   I
            2      17     1 result   I
      StackMapTable: number_of_entries = 2
        frame_type = 255 /* full_frame */
          offset_delta = 12
          locals = [ int, int ]
          stack = [ class java/lang/Exception ]
        frame_type = 4 /* same */

  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 33: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       2     0  this   Lcom/listao/jvm/chapter09/java/MethodInnerStrucTest;
            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     #13                 // class java/lang/String
         5: invokevirtual #14                 // Method compareTo:(Ljava/lang/String;)I
         8: ireturn
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/listao/jvm/chapter09/java/MethodInnerStrucTest;

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: ldc           #15                 // String 测试方法的内部结构
         2: putstatic     #16                 // Field str:Ljava/lang/String;
         5: return
      LineNumberTable:
        line 11: 0
}
Signature: #49                          // Ljava/lang/Object;Ljava/lang/Comparable<Ljava/lang/String;>;Ljava/io/Serializable;
SourceFile: "MethodInnerStrucTest.java"






 


























































































 








 






















 






















 



















 


































































4. non-final类变量

  • 静态变量和类关联在一起,随着类的加载而加载,成为类数据在逻辑上的一部分 => 类变量
  • 类变量被类的所有实例共享,即使没有类实例时,也可以访问它
/**
 * non-final的类变量
 * 即使order设置为null,也不会出现空指针异常
 */
public class MethodAreaTest {
    public static void main(String[] args) {
        Order order = null;
        order.hello();
        System.out.println(order.count);
    }
}
class Order {
    public static int count = 1;
    public static final int number = 2;

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








 



 






5. static final

  • 全局常量就是使用static final进行修饰
  • 全局常量在编译时就被分配了
{
  // 1. static属性。Prepare赋默认值,Initialization赋初始值
  public static int count;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC

  // 2. static, final属性。在编译时即赋值(.classe文件)
  public static final int number;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 2


 




 



6. 运行时常量池&常量池

image-20200708171151384
  • 理解方法区,需要理解清楚ClassFile。加载类的信息都在方法区。内部包含了运行时常量池
  • 理解运行时常量池,需要理解清楚ClassFile中的常量池

1. 常量池

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

image-20200708172357052

2. 常量池背景

一个java源文件中的类、接口,编译后都产生一个个字节码文件。而字节码需要数据支持,通常这种数据会很大以至于不能直接存到字节码里,换另一种方式,可以存到常量池,字节码包含了指向常量池的符号引用。动态链接时会用到运行时常量池,将符号引用转化为直接引用

public class SimpleClass {
    public void sayHello() {
        System.out.println("hello");
    }
}

虽然上述代码只有194字节,但里面却使用了String、System、PrintStream及Object等结构。代码量其实很少了,如果代码多的话,引用的结构将会更多,这里就需要用到常量池了

3. 常量池内容

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

  • 数量值
  • 字符串值
  • 类引用
  • 字段引用
  • 方法引用

常量池中主要存放的两大类常量:

  1. 字面量:字面量比较接近Java语言层次的常量概念
    • eg:文本字符串、被声明为final的常量值等
  2. 符号引用:属于编译原理方面的概念,包括下面三类常量
    • 类和接口的全限定名
    • 字段的名称和描述符
    • 方法的名称和描述符
public class MethodAreaTest2 {
    public static void main(String args[]) {
        Object obj = new Object();
    }
}
0 new #2 <java/lang/Object>
3 dup
4 invokespecial #1 <java/lang/Object.<init> : ()V>
7 astore_1
8 return

4. 运行时常量池

  • 运行时常量池(Runtime Constant Pool)是方法区的一部分
  • 常量池表(Constant Pool Table)是Class文件的一部分,用于存放编译期生成的各种字面量与符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中
  • JVM为每个已加载的类型(类或接口)都维护一个常量池。池中的数据项像数组项一样,通过索引访问
  • 运行时常量池中包含多种不同的常量,包括编译期就已经明确的数值字面量,也包括到运行期解析后才能够获得的方法或者字段引用。此时不再是常量池中的符号地址了,换为真实地址
    • 运行时常量池,相对于Class文件常量池的另一重要特征是:具备动态性
    • eg:String.intern()
  • 当创建类或接口的运行时常量池时,如果所需内存空间超过了方法区所能提供的最大值,则JVM会抛OutOfMemoryError异常

6. 方法区eg

public class MethodAreaDemo {
    public static void main(String args[]) {
        int x = 500;
        int y = 100;
        int a = x / y;
        int b = 50;
        System.out.println(a + b);
    }
}
Classfile /D:/workspace_idea5/JVMDemo/out/production/chapter09/com/atguigu/java1/MethodAreaDemo.class
  Last modified 2020-4-23; size 640 bytes
  MD5 checksum a2e8a0e034e2dd986b45d36a3401a63b
  Compiled from "MethodAreaDemo.java"
public class com.atguigu.java1.MethodAreaDemo
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#24         // java/lang/Object."<init>":()V
   #2 = Fieldref           #25.#26        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = Methodref          #27.#28        // java/io/PrintStream.println:(I)V
   #4 = Class              #29            // com/atguigu/java1/MethodAreaDemo
   #5 = Class              #30            // java/lang/Object
   #6 = Utf8               <init>
   #7 = Utf8               ()V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               LocalVariableTable
  #11 = Utf8               this
  #12 = Utf8               Lcom/atguigu/java1/MethodAreaDemo;
  #13 = Utf8               main
  #14 = Utf8               ([Ljava/lang/String;)V
  #15 = Utf8               args
  #16 = Utf8               [Ljava/lang/String;
  #17 = Utf8               x
  #18 = Utf8               I
  #19 = Utf8               y
  #20 = Utf8               a
  #21 = Utf8               b
  #22 = Utf8               SourceFile
  #23 = Utf8               MethodAreaDemo.java
  #24 = NameAndType        #6:#7          // "<init>":()V
  #25 = Class              #31            // java/lang/System
  #26 = NameAndType        #32:#33        // out:Ljava/io/PrintStream;
  #27 = Class              #34            // java/io/PrintStream
  #28 = NameAndType        #35:#36        // println:(I)V
  #29 = Utf8               com/atguigu/java1/MethodAreaDemo
  #30 = Utf8               java/lang/Object
  #31 = Utf8               java/lang/System
  #32 = Utf8               out
  #33 = Utf8               Ljava/io/PrintStream;
  #34 = Utf8               java/io/PrintStream
  #35 = Utf8               println
  #36 = Utf8               (I)V
{
  public com.atguigu.java1.MethodAreaDemo();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/atguigu/java1/MethodAreaDemo;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=5, args_size=1
         0: sipush        500
         3: istore_1
         4: bipush        100
         6: istore_2
         7: iload_1
         8: iload_2
         9: idiv
        10: istore_3
        11: bipush        50
        13: istore        4
        15: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
        18: iload_3
        19: iload         4
        21: iadd
        22: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        25: return
      LineNumberTable:
        line 9: 0
        line 10: 4
        line 11: 7
        line 12: 11
        line 13: 15
        line 14: 25
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      26     0  args   [Ljava/lang/String;
            4      22     1     x   I
            7      19     2     y   I
           11      15     3     a   I
           15      11     4     b   I
}
SourceFile: "MethodAreaDemo.java"
image-20200708204750374
  1. 将操作数500放入到操作数栈中
image-20200708204953552
  1. 500出栈,存储到局部变量表中
image-20200708205029376
  1. 把100存入局部变量表中。再将变量表中的500、100入栈
image-20200708205221737
  1. 将500、100进行除法运算,结果入栈
image-20200708205413721
  1. 输出流,调用运行时常量池的常量System.out
image-20200708205708057
  1. 调用invokevirtual(虚方法调用),创建并进入System.out.println()方法栈桢
image-20200708205909176
  1. 返回
image-20200708210540696

7. 静态、实例、局部变量位置

/*
 * 结论:
 * 静态引用对应的对象实体始终都存在堆空间
 *
 * JDK7:
 *      -Xms200m -Xmx200m -XX:PermSize=300m -XX:MaxPermSize=300m -XX:+PrintGCDetails
 * JDK8:
 *      -Xms200m -Xmx200m -XX:MetaspaceSize=300m -XX:MaxMetaspaceSize=300m -XX:+PrintGCDetails
 */
public class StaticFieldTest {
    private static byte[] arr = new byte[1024 * 1024 * 100]; // 100MB

    public static void main(String[] args) {

        System.out.println(StaticFieldTest.arr);
    }
}
/**
 * 《深入理解Java虚拟机》中的案例:
 * staticObj、instanceObj、localObj存放在哪里?
 */
public class StaticObjTest {
    static class Test {
        static ObjectHolder staticObj = new ObjectHolder(); // 1. Method_Area
        ObjectHolder instanceObj = new ObjectHolder();      // 2. Heap

        void foo() {
            ObjectHolder localObj = new ObjectHolder();     // 3. JVM_Stack
            System.out.println("done");
        }
    }

    private static class ObjectHolder {
    }

    public static void main(String[] args) {
        Test test = new StaticObjTest.Test();
        test.foo();
    }
}






 
 


 












使用jhsdb.exe,JDK9时才引入的

  • staticObj随着Test的类型信息存放在方法区
  • instanceObj随着Test的对象实例存放在Heap
  • localObj则是存放在foo()方法栈帧的局部变量表中
image-20200708215025527
  • 测试发现:三个Obj的数据在内存中的地址都落在Eden区。结论:只要是Obj实例必然会在Heap中分配
  • 接着,找到了一个引用staticObj的地方,是在一个java.lang.Class的实例里,并且给出了这个实例的地址。通过Inspector查看该Obj实例,可以清楚看到这确实是一个java.lang.Class类型的Obj实例,里面有一个名为staticObj的实例字段
image-20200708215218078
  • 从《Java虚拟机规范》所定义的概念模型来看,所有Class相关的信息都应该存放在方法区之中,但方法区该如何实现,并未做出规定,这就成了一件允许不同VM自己灵活把握的事情。JDK7及其以后版本的HotSpot选择把静态变量与Class类型的对象实例存放在一起,存储于方法区之中

8. 方法区GC

  • 有些人认为方法区(eg:Hotspot中的元空间、永久代)是没有GC行为的,其实不然。《Java虚拟机规范》对方法区的约束是非常宽松的,提到过可以不要求VM在方法区中实现GC。事实上也确实有未实现或未能完整实现方法区类型卸载的收集器存在(eg:JDK11就不支持类卸载)
  • 大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGi这类频繁进行类加载的场景中,通常都需要JVM具备类型卸载的能力,以保证不会对方法区造成过大的内存压力
  • 一般来说这个区域的回收效果比较难令人满意,尤其是类型的卸载,条件相当苛刻。但是这部分区域的回收有时又确实是必要的。以前Sun公司的Bug列表中,曾出现过的若干个严重的Bug,就是由于低版本的HotSpot对MethodArea未完全回收而导致内存泄漏
  • 方法区GC主要回收两部分内容:常量池中废弃的常量和不再使用的类型
  • HotSpot对常量池的回收策略是很明确的,只要常量池中的常量没有被任何地方引用,就可以被回收

1. 类卸载条件

回收废弃常量与回收Heap中的Obj非常类似(常量的回收比较简单,重点是类的回收)

  • 判定一个类型不再被使用比较苛刻。同时满足三个条件:
    1. 该类所有的实例都已经被回收,即Heap中不存在该类及其任何派生子类的实例
    2. 加载该类的ClassLoader已经被回收,这个条件除非是经过精心设计的可替换ClassLoader的场景(eg:OSGi、JSP的重加载等),否则通常是很难达成
    3. 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
  • JVM被允许对满足上述三个条件的无用类进行回收,仅仅是“被允许”,而并不是和对象一样,没有引用了就必然会回收。关于是否要对类型进行回收,HotSpot提供-Xnoclassgc参数进行控制,还可以使用-verbose:class-XX:+TraceClass-Loading-XX:+TraceClassUnLoading查看类加载和卸载信息