jvm初识之方法区

jvm初识之方法区

定义

(1)方法区与java堆一样,是各个线程共享的内存区域
(2)方法区在jvm启动的时候被创建,并且它的实际的物理内存空间中和java堆区一样都是可以不连续的
(3)方法区的大小,跟堆空间一样,可以选择固定大小或者扩展
(4)方法区的大小决定了系统可以保存多少个类,如果系统定义了太多的类,导致方法区溢出,虚拟机同样会抛出内存溢出错误
java8以前(java.lang.OutOfMemoryError:PermGen space)或者java8以后(java.lang.OutOfMemoryError:Metaspace)
(5)关闭jvm就会释放这个区域的内存

位置

jdk1.6和jdk1.8 方法区位置不同
jvm初识之方法区_第1张图片
jvm初识之方法区_第2张图片

区别

从图中可以看到方法区的演变:jdk7以前,方法区的实现是永久代,jdk8开始方法区的实现使用元空间取代了永久代,永久代和元空间的主要区别是,元空间不在虚拟机设置的内存中,而是在使用本地内存。如果无法满足新的内存分配需求的话,也会报OOM的错误。

替换原因

为什么永久代要被元空间代替:
(1)永久代的空间大小设置很难确定,比如在某些场景下,如果动态加载类过多,会发生OOM错误,而元空间直接使用本地内存,默认情况下,元空间大小只受本地内存限制
(2)对永久代调优很困难;调优是为了减少FullGC,如果永久代频繁触发FullGC,会使永久代调优变得困难
(3)合并HotSpot和JRockit的代码;JRockit从来没有所谓的永久代

StringTable为什么要放到堆

因为永久代的回收频率比较低,只在FullGC的时候才会被回收,FullGC只会在老年代或者永久代空间不足时才会触发。stringtable中存在大量的字符串,如果有大量的字符串被创建,放在永久代,由于永久代的回收频率低,会导致永久代空间不足。
  如果放到堆里,能够及时回收内存。

组成部分

用于存储被虚拟机加载的类型信息、常量、静态变量、即时编译期编译后的代码(JIT)
(1)类型信息:每个加载的类型(类class,接口interface,枚举enum、注解)
(2)域信息
(3)方法信息(方法名称啊,返回类型啊,修饰符啊之类的)
(4)静态变量和类关系在一起,随着类的加载而加载,成为类数据在逻辑上的一部分,类变量被所有实例所共享,即使没有类实例,也可以访问它全局常量(static final),每个全局常量在编译时就被确定了

常量池

常量池就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
如:执行hello word

 public static void main(String[] args) {
        System.out.println("Hello word");
    }

用 javap -v HelloWord.class 查看二进制文件

Classfile /E:/proxxy/target/classes/com/StringTable/HelloWord.class
  Last modified 2022-2-23; size 561 bytes
  MD5 checksum c115752e064012c1ab2f0ab073df470f
  Compiled from "HelloWord.java"
public class com.StringTable.HelloWord
  SourceFile: "HelloWord.java"
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#20         //  java/lang/Object."":()V
   #2 = Fieldref           #21.#22        //  java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            //  Hello word
   #4 = Methodref          #24.#25        //  java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #26            //  com/StringTable/HelloWord
   #6 = Class              #27            //  java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/StringTable/HelloWord;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWord.java
  #20 = NameAndType        #7:#8          //  "":()V
  #21 = Class              #28            //  java/lang/System
  #22 = NameAndType        #29:#30        //  out:Ljava/io/PrintStream;
  #23 = Utf8               Hello word
  #24 = Class              #31            //  java/io/PrintStream
  #25 = NameAndType        #32:#33        //  println:(Ljava/lang/String;)V
  #26 = Utf8               com/StringTable/HelloWord
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public com.StringTable.HelloWord();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4: return
      LineNumberTable:
        line 8: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       5     0  this   Lcom/StringTable/HelloWord;

  public static void main(java.lang.String[]);
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String Hello word
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 10: 0
        line 11: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
               0       9     0  args   [Ljava/lang/String;
}

jvm初识之方法区_第3张图片
如执行 0: getstatic #2 就会找到 Constant pool:中的 #2 = Fieldref #21.#22 然后在查找 #21 = Class #28 #22 = NameAndType #29:#30 然后继续找 #28 = Utf8 java/lang/System #29 = Utf8 out #30 = Utf8 Ljava/io/PrintStream; 至此这一段执行完:意思就是执行System.out方法,后面的类似。。

运行时常量池

常量池在*.class文件中,当该类被加载,它的常量池信息就会被放入运行时常量池,并把里面的符号地址变为真实地址

StringTable

字符串常量池(串池),1.8放入了堆中。字符串常量池中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的,注意它是和运行时常量池是不同的概念,运行时常量池是类加载中常量池中的如#2变成了真实地址;
常见面试题:

       String s1="ab";
        String s2=new String("ab");
          String s3="a"+"b";
        System.out.println(s1==s2);
        System.out.println(s1==s3);

看这个题首先要明白jvm是怎样把字符串放到字符串常量池中的
创建过程:
每当我们创建字符串常量时,JVM会首先检查字符串常量池,如果该字符串在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于String字符串的不可变性我们可以十分确定常量池中一定不存在两个相同的字符串。
jvm初识之方法区_第4张图片
String s1=“ab”;
创建过程是:
1、首先在常量池中查找是否存在内容为"ab"字符串对象。
2、如果不存在则在常量池中创建"ab",并让s1引用该对象。
String s2=new String(“ab”);
创建过程:
1、首先在堆中(不是常量池)创建一个指定的对象"ab",并让s2引用指向该对象。
2、在字符串常量池中查看是否存在内容为"ab"的字符串对象。
3、若存在,则让堆中创建好的字符串对象对字符串常量池的对象进行引用。
String s3=“a”+“b”;
创建过程
1、首先在常量池中查找是否存在内容为"ab"字符串对象。
2、如果不存在则在常量池中创建"ab",并让s1引用该对象。
为啥直接去找“ab”呢,因为javac在编译期间的优化,由于是“a”和“b”都是常量,所以在编译期确定为“ab”
运行结果为 false true
String s2=new String(“ab”);创建了几个对象呢
答案是1个 首先在堆中(不是常量池)创建一个指定的对象"ab",并让s2引用指向该对象(创建了1个)。在字符串常量池中查看是否存在内容为"ab"的字符串对象,由于String s1=“ab”;已经在字符串常量池中创建了“ab”的字符串对象,所以堆中创建好的字符串对象对字符串常量池的对象进行引用。(没创建)String s1="ab"如果放到String s2=new String(“ab”);的下面 ,则这段话创建了了2个对象,若不存在,则在常量池中添加一个内容为"abc"的字符串对象,并将堆中的String对象进行引用。、

intern方法

 String s=new String("a")+new String("b");
        String s1=s.intern();
        System.out.println(s=="ab");
        System.out.println(s1=="ab");

分析下 String s=new String(“a”)+new String(“b”);
明显:串池中存在【“a‘’、“b”】堆中有new String(“a”) 、new String(“b”)、 s相当于StringBuilder.append(“a”).append(“b”).toString();即new String(“ab”)
字节码文件中不一一分析,参照上面例子
jvm初识之方法区_第5张图片
intern()将这个字符串对象尝试放到串池中,如果有则不会放入,如果没有则放入串池,会将串池的对象返回(jdk1.7以后版本)
所以执行结果是 true ,true
若提前定义x为ab呢

 String x="ab";
        String s=new String("a")+new String("b");
        String s1=s.intern();
        System.out.println(s==x);
        System.out.println(s1==x);

执行结果则为 false true
jdk1.6版本 intern()将这个字符串对象尝试放到串池中,如果有则不会放入,如果没有则会把s拷贝一份放入串池,将串池的对象返回

 String s=new String("a")+new String("b");
        String s1=s.intern();
        System.out.println(s=="ab");
        System.out.println(s1=="ab");

则该执行结果为 false true

你可能感兴趣的:(jvm,java,开发语言,后端)