常量池介绍

 什么是“字面量”和“符号引用”和"直接引用"

最近看jvm时遇到了“字面量”和“符号引用”这两个概念,它们被存放在运行时常量池,看了一些博客以后对这两个概念有了初步认识。

字面量可以理解为实际值,int a = 8中的8和String a = "hello"中的hello都是字面量(值)

符号引用就是一个字符串,只要我们在代码中引用了一个非字面量的东西,不管它是变量还是常量,它都只是由一个字符串定义的符号,这个字符串存在Class文件里,类加载的时候第一次加载到这个符号时,就会将这个符号引用(字符串)解析成直接引用(指针)

 如int a= 1;   在Class文件中就是单单的一个"a"字符(符号引用).当类加载的时候,"a"这个字符就会获得了一个引用指向地址(直接引用),这个地址是就是指向1这个值(字面量)的内存地址.

符号引用主要包括三种常量:
    1.类和接口的全限定名
    2.字段的名称和描述符
    3.方法的名称和描述符

 直接引用可以是:
      1.直接指向目标的指针。(个人理解为:指向对象,类变量和类方法的指针)
      2.相对偏移量。      (指向实例的变量,方法的指针)
      3.一个间接定位到对象的句柄。

 java的变量有哪些

static修饰的成员变量,就是随着这个类的加载和类一起创建的,  然后后面再创建类的对象的时候就不用再创建这个成员变量了.

 什么是常量

定义一个变量,用final去修饰,这个变量被赋值一次就不可被改变了, 就成了常量

分为三种:

1).静态常量: final修饰的有static的局部变量, 存在方法区的类的信息的运行时常量池中

2).实例常量: final修饰的没有static的局部变量, 存在方法区的类的信息的运行时常量池中

3).局部常量: final修饰的局部变量, 百度说存在栈中,但具体不知道是栈的哪个位置.

常量池存什么

1).常量池存的是字符串常量.

2).常量池包含五种基本类型的包装类.即Byte,Short,Integer,Long,Character,Boolean, 

这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。

而两种浮点数类型的包装类Float,Double并没有实现常量池技术

3).被final修饰的变量, 它们是静态常量\实例常量\局部常量

什么是常量池?

0.什么是常量池

常量池的本质是缓存。
常量池分为:

  1. Class 文件常量池(非运行时常量池,本地文件)
  2. 运行时常量池(方法区内的类的信息下面,每个类都有一个,互不影响)
  3. 字符串常量池(堆内存,所有类共享)
  4. 基本类型常量池(堆内存,所有类共享)

1.Class 文件常量池(非运行时常量池,本地文件)

Class常量池中存放的编译期生成的各种字面量和符号引用。这部分内容将在类加载后进入方法区的运行时常量池中存放。
接口A源码

package com.xiaoer;

public interface A {
    default void defaultB() {
        System.out.println("defaultB");
    }

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

 执行javap -verbose ByteCode.class查看class文件结构。

Classfile /E:/work_space/04 study/test01/target/classes/com/xiaoer/A.class
  Last modified 2019-9-19; size 448 bytes
  MD5 checksum 2dd9130ca78cd6781198832576a02832
  Compiled from "A.java"
public interface com.xiaoer.A
  minor version: 0
  major version: 54
  flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
   #1 = Fieldref           #17.#18        // java/lang/System.out:Ljava/io/PrintStream;
   #2 = String             #7             // defaultB
   #3 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #4 = String             #14            // staticA
   #5 = Class              #21            // com/xiaoer/A
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               defaultB
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               Lcom/xiaoer/A;
  #14 = Utf8               staticA
  #15 = Utf8               SourceFile
  #16 = Utf8               A.java
  #17 = Class              #23            // java/lang/System
  #18 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #19 = Class              #26            // java/io/PrintStream
  #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #21 = Utf8               com/xiaoer/A
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
{
  public void defaultB();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #1                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #2                  // String defaultB
         5: invokevirtual #3                  // 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  this   Lcom/xiaoer/A;

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

2.运行时常量池(方法区内的类的信息下面,每个类都有一个,互不影响)

在jvm在执行某个类的时候,必须经过加载、链接(验证、准备、解析)、初始化,

在加载阶段:将class文件常量池加载到运行时常量池的过程.

运行时常量池相对于class常量池一大特征就是具有动态性,java规范并不要求常量只能在运行时才产生,也就是说运行时常量池的内容并不全部来自class常量池,在运行时可以通过代码生成常量并将其放入运行时常量池中,这种特性被用的最多的就是String.intern()。

3.字符串常量池(堆内存,所有类共享)

DK1.7 以后字符串常量池被从方法区拿到了堆中,这个池就是专门存字符串的.

4.基本类型常量池(堆内存,所有类共享)

个人理解应该存在一个基本类型常量池.

为什么需要常量池

常量池其实就是跟数据库连接池的目的都是一样的。节省资源,提高效率。

String s1 = “Hello”,到底有没有在堆中创建对象?常量池存放的到底是对象还是对象引用?

是有的,所有创建的对象都在堆上。只是将字符串的引用放进字符串常量池。

关于常量池的位置测试

此测试是把<局部变量和成员变量>那篇文件的测试直接拿过来用了

1.上测试代码

@Service
public class User {
    private final  static int a1 = 1;
    private final  static String b1 = "你";
    private final  static int[] c1 = {1,2,3};
    private int d1 = 2;
    private String e1 = "好";
    private int[] f1 = {4,5,6};

    public void aa() {
        int d1 = 2;
        System.out.println("User的aa()的d1" + "  值  " +d1+  "  内存  " + System.identityHashCode(d1));
        String e1 = "好";
        System.out.println("User的aa()的e1" + "  值  " +e1+  "  内存  " + System.identityHashCode(e1));
        int[] f1 = {4,5,6};
        System.out.println("User的aa()的f1" + "  值  " +f1+  "  内存  " + System.identityHashCode(f1));
    }
    public void bb() {
        int d1 = 2;
        System.out.println("User的bb()的d1" + "  值  " +d1+  "  内存  " + System.identityHashCode(d1));
        String e1 = "好";
        System.out.println("User的bb()的e1" + "  值  " +e1+  "  内存  " + System.identityHashCode(e1));
        int[] f1 = {4,5,6};
        System.out.println("User的bb()的f1" + "  值  " +f1+  "  内存  " + System.identityHashCode(f1));
    }

    public static int getA1() {
        return a1;
    }

    public static String getB1() {
        return b1;
    }

    public static int[] getC1() {
        return c1;
    }

    public  int getD1() {
        return d1;
    }

    public  String getE1() {
        return e1;
    }

    public  int[] getF1() {
        return f1;
    }
}
@Service
public class Nat {
    private final  static int a1 = 1;
    private final  static String b1 = "你";
    private final  static int[] c1 = {1,2,3};
    private int d1 = 2;
    private String e1 = "好";
    private int[] f1 = {4,5,6};

    public void aa() {
        int d1 = 2;
        System.out.println("Nat的aa()的d1" + "  值  " +d1+  "  内存  " + System.identityHashCode(d1));
        String e1 = "好";
        System.out.println("Nat的aa()的e1" + "  值  " +e1+  "  内存  " + System.identityHashCode(e1));
        int[] f1 = {4,5,6};
        System.out.println("Nat的aa()的f1" + "  值  " +f1+  "  内存  " + System.identityHashCode(f1));
    }
    public void bb() {
        int d1 = 2;
        System.out.println("Nat的bb()的d1" + "  值  " +d1+  "  内存  " + System.identityHashCode(d1));
        String e1 = "好";
        System.out.println("Nat的bb()的e1" + "  值  " +e1+  "  内存  " + System.identityHashCode(e1));
        int[] f1 = {4,5,6};
        System.out.println("Nat的bb()的f1" + "  值  " +f1+  "  内存  " + System.identityHashCode(f1));
    }

    public static int getA1() {
        return a1;
    }

    public static String getB1() {
        return b1;
    }

    public static int[] getC1() {
        return c1;
    }

    public  int getD1() {
        return d1;
    }

    public  String getE1() {
        return e1;
    }

    public  int[] getF1() {
        return f1;
    }
}
@RestController
@RequestMapping("/aaa")
public class Qqq {
@Autowired
private User user;
    @Autowired
    private Nat nat;
    @GetMapping("/bbb")
    public void ccc() {
        
        System.out.println("User的a1" + "  值  " +User.getA1()+  "  内存  " + System.identityHashCode(User.getA1()));
        System.out.println("User的b1" + "  值  " +User.getB1()+  "  内存  " + System.identityHashCode(User.getB1()));
        System.out.println("User的c1" + "  值  " +User.getC1()+  "  内存  " + System.identityHashCode(User.getC1()));
        System.out.println("User的d1" + "  值  " +user.getD1()+  "  内存  " + System.identityHashCode(user.getD1()));
        System.out.println("User的e1" + "  值  " +user.getE1()+  "  内存  " + System.identityHashCode(user.getE1()));
        System.out.println("User的f1" + "  值  " +user.getF1()+  "  内存  " + System.identityHashCode(user.getF1()));
        user.aa();
        user.bb();

        System.out.println("Nat的a1" + "  值  " +Nat.getA1()+  "  内存  " + System.identityHashCode(Nat.getA1()));
        System.out.println("Nat的b1" + "  值  " +Nat.getB1()+  "  内存  " + System.identityHashCode(Nat.getB1()));
        System.out.println("Nat的c1" + "  值  " +Nat.getC1()+  "  内存  " + System.identityHashCode(Nat.getC1()));
        System.out.println("Nat的d1" + "  值  " +nat.getD1()+  "  内存  " + System.identityHashCode(nat.getD1()));
        System.out.println("Nat的e1" + "  值  " +nat.getE1()+  "  内存  " + System.identityHashCode(nat.getE1()));
        System.out.println("Nat的f1" + "  值  " +nat.getF1()+  "  内存  " + System.identityHashCode(nat.getF1()));
        nat.aa();
        nat.bb();
    }
}

2.运行结果

User的a1  值  1  内存  697815822Nat的a1  值  1  内存  697815822
User的b1  值  你  内存  1403028226Nat的b1  值  你  内存  1403028226
User的c1  值  [I@2feff326  内存  804254502Nat的c1  值  [I@55f2d93a  内存  1441978682
User的d1  值  2  内存  387698249User的aa()的d1  值  2  内存  387698249User的bb()的d1  值  2  内存  387698249
User的e1  值  好  内存  225287User的aa()的e1  值  好  内存  225287User的bb()的e1  值  好  内存  225287
User的f1  值  [I@576c4b96  内存  1466715030User的aa()的f1  值  [I@3402bcd0  内存  872594640User的bb()的f1  值  [I@3ac55420  内存  986010656
Nat的d1  值  2  内存  387698249Nat的aa()的d1  值  2  内存  387698249Nat的bb()的d1  值  2  内存  387698249
Nat的e1  值  好  内存  225287Nat的aa()的e1  值  好  内存  225287Nat的bb()的e1  值  好  内存  225287
Nat的f1  值  [I@60f718e9  内存  1626806505Nat的aa()的f1  值  [I@3efad7a0  内存  1056626592Nat的bb()的f1  值  [I@7bf85e1b  内存  2079874587

3.结果分析

首先声明,我额外试验了,局部变量用final修饰和不用final修饰对指向的值的内存地址没有任何影响.因此上面的结果就没有出现用final修饰的局部变量

内存结果分析就是下面我画的图,都在图里面的

4.对上图结果分析总结

  1. Class 文件常量池(非运行时常量池,本地文件)
  2. 运行时常量池(方法区内的类的信息下面,每个类都有一个,互不影响)
  3. 字符串常量池(堆内存,所有类共享)
  4. 基本类型常量池(堆内存,所有类共享)

第二个关于常量池位置试验(参考即可)

实验思想,

第一种情况:在A和B两个类中定义一个名称相同的常量但值不同的常量,

第一种情况:在A和B两个类中定义一个名称相同的常量但值相同的常量,

看看它们的内存地址是不是一样的.

首先,有个情况,就是可以在两个不同的类定义名字一样但值不一样的常量!!!!!!

@Service
public class userServiceImpA implements UserService {
    private final  static int aaaaaa = 1;
    public static int getAaaaaa() {
        return aaaaaa;
    }
    @Override
    public void aa() {
        System.out.println(111);
    }
    @Override
    public void bb() {
        System.out.println(222);
    }
}
@Service
public class NatServiceImpA implements NatService {
    private final  static int aaaaaa = 2;
    public static int getAaaaaa() {
        return aaaaaa;
    }

    @Override
    public void aa() {
        System.out.println(111);
    }
    @Override
    public void bb() {
        System.out.println(222);
    }
}
@RestController
@RequestMapping("/aaa")
public class Qqq {
@Autowired
private userServiceImpA userServiceImp;
    @Autowired
    private NatServiceImpA natServiceImp;
    @GetMapping("/bbb")
    public void ccc() {
        System.out.println(".....1");
        System.out.println(Thread.currentThread().getName() + "  值  " +userServiceImpA.getAaaaaa()+  "  内存  " + System.identityHashCode(userServiceImpA.getAaaaaa()));
        System.out.println(".....2");
        System.out.println(Thread.currentThread().getName() + "  值  " +NatServiceImpA.getAaaaaa()+  "  内存  " + System.identityHashCode(NatServiceImpA.getAaaaaa()));
        System.out.println(".....3");
    }
}

1.int类型                                              值相同内存地址相同,值不同内存地址不同           

打印结果如下

 

值一个定1,一个定2,但是没报错,打印结果值不同内存地址不同,

值相同内存地址一样.

2.string类型                                       值相同内存地址相同,值不同内存地址不同

 

值不同,但是没报错,打印结果值不同,内存地址不同,

值相同,打印结果值相同,内存地址相同,

3.数组类型                                       内存地址都不同

定义数组

第一种,A类是private final static int[] aaaaaa = {123};

B类是private final static int[] aaaaaa = {456};

第二种,两个类都是private final static int[] aaaaaa = {123};

输出int[0]和内存

 

结果显示

值不同,但是没报错,打印结果值不同,内存地址不同,

值相同,打印结果值相同,内存地址相同,

4.ArrayList类型                                         内存地址都不同

定义集合

第一种,A类是private final static ArrayList aaaaaa = new ArrayList<Integer>();

aaaaaa.add(1)

B类是private final static ArrayList aaaaaa = new ArrayList<Integer>();

aaaaaa.add(2)

第二种,两个类都是private final static ArrayList aaaaaa = new ArrayList<Integer>();

aaaaaa.add(3)

输出aaaaaa的值和内存

 

结果显示

值不同,但是没报错,打印结果值不同,内存地址不同,

值相同,打印结果值相同,内存地址不同,

5.Map类型                                                         内存地址都不同

定义Map

第一种,A类是private final static HashMap aaaaaa = new HashMap<String,String>();

aaaaaa.add("1","你")

B类是private final static HashMap aaaaaa = new HashMap<String,String>();

aaaaaa.add("1","好")

第二种,两个类都是private final static HashMap aaaaaa = new HashMap<String,String>();

aaaaaa.add("3","啊")

输出aaaaaa的值和内存

结果如下:

 

结果显示

值不同,但是没报错,打印结果值不同,内存地址不同,

值相同,打印结果值相同,内存地址不同,

6.自定义的实体类                                                        内存地址都不同

定义Nat

第一种,A类是private final static Nat aaaaaa = new Nat();

aaaaaa.setAge(1)

B类是private final static Nat aaaaaa = new Nat();

aaaaaa.setAge(2)

第二种,private final static Nat aaaaaa = new Nat();

aaaaaa.setAge(3)

输出aaaaaa的值和内存

结果如下:

 

结果显示

值不同,但是没报错,打印结果值不同,内存地址不同,

值相同,打印结果值相同,内存地址不同,

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值