简单理解常量、常量池、运行时常量池和字符串常量池

1、常量
常量在java中就值的是一般的字面量,比如字符串,整数,浮点数等等数据。简单理解java中什么叫常量

2、常量池,也叫静态常量池或者class文件常量池,说常量池一定要指明是编译器产生的。它的组成为字面量和符号引用
在这里插入图片描述
3、运行时常量池。当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。运行时常量池在jdk1.8时,在方法区(即元空间)中。

4、字符串常量池。就是String类型的字符串,包括代码写的字符串,比如方法名,类名都是字面量,还有String定义的字符串。字符串常量池,jdk1.8时在堆中,全局共享,独一份,之前在方法区中。

那么问题就来了,
1、字符串常量池在1.8时在堆内存中,如何证明。
2、字符串常量能被GC回收吗?
3、如何证明jdk1.8方法区为元空间。参考简单理解jdk1.8中的方法区


上面1和2问题,通过一个实例可以证明,
先设置JVM虚拟机启动参数-Xms1m -Xmx1m -XX:+PrintGCDetails -XX:MaxMetaspaceSize=8m。把堆内存设置小一点,1M。

public class DemoClass {
    public static void main(String[] args) throws Exception {
        //字符串常量池在哪,在堆中。并且常量也会被GC回收。
        String s = "love";
        for (int i = 0; i < 10000000; i++) {
            s = s + "qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm-qsm";
            if (i%10==0){
                System.out.println("第"+i+"次");
                Thread.sleep(100);
            }
        }
    }
}

运行的结果截取一部分,如下

[GC (Allocation Failure) [PSYoungGen: 512K->488K(1024K)] 512K->496K(1536K), 0.0010906 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Connected to the target VM, address: '127.0.0.1:51561', transport: 'socket'
[GC (Allocation Failure) [PSYoungGen: 1000K->504K(1024K)] 1008K->632K(1536K), 0.0045804 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[GC (Allocation Failure) [PSYoungGen: 1002K->506K(1024K)] 1130K->786K(1536K), 0.0192090 secs] [Times: user=0.02 sys=0.00, real=0.02 secs] 
[GC (Allocation Failure) [PSYoungGen: 1013K->488K(1024K)] 1294K->896K(1536K), 0.0077057 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 488K->463K(1024K)] [ParOldGen: 408K->358K(512K)] 896K->822K(1536K), [Metaspace: 2930K->2930K(1056768K)], 0.0062971 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 974K->442K(1024K)] [ParOldGen: 358K->343K(512K)] 1332K->785K(1536K), [Metaspace: 3141K->3141K(1056768K)], 0.0066005 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
第0次
[Full GC (Ergonomics) [PSYoungGen: 954K->429K(1024K)] [ParOldGen: 343K->363K(512K)] 1297K->793K(1536K), [Metaspace: 3399K->3399K(1056768K)], 0.0065477 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
第10次
---
---
[Full GC (Ergonomics) [PSYoungGen: 603K->599K(1024K)] [ParOldGen: 480K->480K(512K)] 1084K->1080K(1536K), [Metaspace: 3405K->3405K(1056768K)], 0.0065548 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 940K->769K(1024K)] [ParOldGen: 480K->480K(512K)] 1421K->1250K(1536K), [Metaspace: 3405K->3405K(1056768K)], 0.0064790 secs] [Times: user=0.06 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 940K->428K(1024K)] [ParOldGen: 480K->481K(512K)] 1421K->909K(1536K), [Metaspace: 3405K->3405K(1056768K)], 0.0071489 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
[Full GC (Ergonomics) [PSYoungGen: 604K->599K(1024K)] [ParOldGen: 481K->481K(512K)] 1085K->1080K(1536K), [Metaspace: 3405K->3405K(1056768K)], 0.0113774 secs] [Times: user=0.05 sys=0.00, real=0.01 secs] 
[Full GC (Allocation Failure) [PSYoungGen: 599K->599K(1024K)] [ParOldGen: 481K->481K(512K)] 1080K->1080K(1536K), [Metaspace: 3405K->3405K(1056768K)], 0.0066770 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
Heap
 PSYoungGen      total 1024K, used 649K [0x00000000ffe80000, 0x0000000100000000, 0x0000000100000000)
  eden space 512K, 43% used [0x00000000ffe80000,0x00000000ffeb71a8,0x00000000fff00000)
  from space 512K, 83% used [0x00000000fff80000,0x00000000fffeb2f8,0x0000000100000000)
  to   space 512K, 0% used [0x00000000fff00000,0x00000000fff00000,0x00000000fff80000)
 ParOldGen       total 512K, used 481K [0x00000000ffe00000, 0x00000000ffe80000, 0x00000000ffe80000)
  object space 512K, 93% used [0x00000000ffe00000,0x00000000ffe78488,0x00000000ffe80000)
 Metaspace       used 3436K, capacity 4556K, committed 4864K, reserved 1056768K
  class space    used 370K, capacity 392K, committed 512K, reserved 1048576K
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3332)
	at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
	at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
	at java.lang.StringBuilder.append(StringBuilder.java:136)
	at com.jd.qsm.first.demo.controller.DemoClass.main(DemoClass.java:8)
Disconnected from the target VM, address: '127.0.0.1:51561', transport: 'socket'

回答第一个问题,由于我们一直生成的是字符串,最终导致OOM的原因是Java heap space堆空间,而在生成字符串时Metaspace几乎就没有什么变化过。所以证明字符串常量池在堆空间中。

回答第二个问题,可以看到程序经历了很多次的GC和fullGC,看到新生代和老年代的内存占用都有减少,最终由于堆内存不足才OOM的。所以由于每次GC都有减少,所以字符串是可以被GC回收的。


4、常量池怎么查看。
5、常量池有什么作用,为什么需要它
6、常量池与运行时常量池有什么关联

回答4-6问题
编写一段代码

public class DemoClass {
    public static void main(String[] args) throws Exception {
        System.out.println("qsm");
    }
}

找到字节码文件,运行javap -v DemoClass.class

D:\idea\code\firstdemo\target\classes\com\jd\qsm\first\demo\controller>javap -v DemoClass.class
Classfile /D:/idea/code/firstdemo/target/classes/com/jd/qsm/first/demo/controller/DemoClass.class
  Last modified 2021-1-9; size 666 bytes
  MD5 checksum 711ba65168cdd7d23241623fe88153f6
  Compiled from "DemoClass.java"
public class com.jd.qsm.first.demo.controller.DemoClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#23         // java/lang/Object."<init>":()V
   #2 = Fieldref           #24.#25        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #26            // qsm
   #4 = Methodref          #27.#28        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #29            // com/jd/qsm/first/demo/controller/DemoClass
   #6 = Class              #30            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/jd/qsm/first/demo/controller/DemoClass;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               Exceptions
  #19 = Class              #31            // java/lang/Exception
  #20 = Utf8               MethodParameters
  #21 = Utf8               SourceFile
  #22 = Utf8               DemoClass.java
  #23 = NameAndType        #7:#8          // "<init>":()V
  #24 = Class              #32            // java/lang/System
  #25 = NameAndType        #33:#34        // out:Ljava/io/PrintStream;
  #26 = Utf8               qsm
  #27 = Class              #35            // java/io/PrintStream
  #28 = NameAndType        #36:#37        // println:(Ljava/lang/String;)V
  #29 = Utf8               com/jd/qsm/first/demo/controller/DemoClass
  #30 = Utf8               java/lang/Object
  #31 = Utf8               java/lang/Exception
  #32 = Utf8               java/lang/System
  #33 = Utf8               out
  #34 = Utf8               Ljava/io/PrintStream;
  #35 = Utf8               java/io/PrintStream
  #36 = Utf8               println
  #37 = Utf8               (Ljava/lang/String;)V
{
  public com.jd.qsm.first.demo.controller.DemoClass();
    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 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/jd/qsm/first/demo/controller/DemoClass;

  public static void main(java.lang.String[]) throws java.lang.Exception;
    descriptor: ([Ljava/lang/String;)V
    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 qsm
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 5: 0
        line 6: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
    Exceptions:
      throws java.lang.Exception
    MethodParameters:
      Name                           Flags
      args
}
SourceFile: "DemoClass.java"

从的出来的结果可以看到有一部分叫做Constant pool:,这里就是class文件常量池,是编译之后生成的,是一个静态的概念。它包含了字面量和符号引用。比如

		 0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String qsm
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return

比如ldc指令指向了#3,而#3在常量池中,指的是#3 = String #26 // qsm#26指的是#26 = Utf8 qsm,也就是说这里的#3就是符号引用。这些符号引用到加载到jvm(解析阶段)或者运行时(虚拟机栈的动态链接),才会变成直接引用。

问题4的答案,使用javap -v xxx.class命令就可以查看到常量池。

问题5的答案,一个类编译为字节码中,肯定是需要了很多其他的类,比如本代码就需要了System,Object等类信息,这些数据很大,不可能全部放在一个类的字节码文件中,所以先使用常量池,对这些数据的引用使用符号引用,等真正加载到jvm或者运行时才将这些符号引用转换为直接引用。

问题6、当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中了。但是jdk1.8把其中的字符串常量池放入了jvm中。


7、什么叫符号引用,什么叫直接引用
8、什么地方进行了符号引用转换为直接引用
8、什么叫静态链接和动态链接

还是上面的javap解析出来的的字节码文件为例

		 0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String qsm
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return

0: getstatic #2,第一句getstatic得到静态属性,里面的#2就是符号引用,在常量池中它又指向了其他符号引用,但最终实际代表了PrintStream类的一个对象。这些符号引用在编译的时候,仅仅是表面自己需要什么,到加载jvm或者运行时才执行真正的地址,这个就是直接引用了。

jvm加载的时候有一个阶段叫做解析,这里就把能够确定的符号引用转变为直接引用了。这里的解析也成为静态链接。还有一个地方,线程的虚拟机栈执行方法的时候,有一个动态链接,这个地方也是将符号引用转为直接引用的,由于是在运行中,所以叫做动态链接。

那么什么情况下使用静态链接,什么情况使用动态链接。一般静态链接的时候,都是能够直接确定地址的,比如类的静态方法,final方法,private方法,构造器等,这些都叫做非虚方法。
而一般的public void hi();等成员方法就是虚方法。这些在加载的时候,是无法确定的。只有正运行的时候才知道。主要原因还是多态(重写)因素。
比如,Father person= new SonA();,执行person.hi()的时候,是不知道这个hi方法具体是执行谁的,是SonA的还是Father的,并不清楚SonA是否重写了父类的hi方法。所以这里只有真正运行方法的时候,才知道才符号引用指向直接引用。
再看5: invokevirtual #4,invokevirtual 就是调用虚方法,它下面还有子类。
在这里插入图片描述

【完,喜欢就点个赞呗】

正在去BAT的路上修行

  • 14
    点赞
  • 43
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值