什么是“字面量”和“符号引用”和"直接引用"
最近看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.什么是常量池
常量池的本质是缓存。
常量池分为:
- Class 文件常量池(非运行时常量池,本地文件)
- 运行时常量池(方法区内的类的信息下面,每个类都有一个,互不影响)
- 字符串常量池(堆内存,所有类共享)
- 基本类型常量池(堆内存,所有类共享)
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 内存 697815822 Nat的a1 值 1 内存 697815822 User的b1 值 你 内存 1403028226 Nat的b1 值 你 内存 1403028226 User的c1 值 [I@2feff326 内存 804254502 Nat的c1 值 [I@55f2d93a 内存 1441978682 User的d1 值 2 内存 387698249 User的aa()的d1 值 2 内存 387698249 User的bb()的d1 值 2 内存 387698249 User的e1 值 好 内存 225287 User的aa()的e1 值 好 内存 225287 User的bb()的e1 值 好 内存 225287 User的f1 值 [I@576c4b96 内存 1466715030 User的aa()的f1 值 [I@3402bcd0 内存 872594640 User的bb()的f1 值 [I@3ac55420 内存 986010656 Nat的d1 值 2 内存 387698249 Nat的aa()的d1 值 2 内存 387698249 Nat的bb()的d1 值 2 内存 387698249 Nat的e1 值 好 内存 225287 Nat的aa()的e1 值 好 内存 225287 Nat的bb()的e1 值 好 内存 225287 Nat的f1 值 [I@60f718e9 内存 1626806505 Nat的aa()的f1 值 [I@3efad7a0 内存 1056626592 Nat的bb()的f1 值 [I@7bf85e1b 内存 2079874587
3.结果分析
首先声明,我额外试验了,局部变量用final修饰和不用final修饰对指向的值的内存地址没有任何影响.因此上面的结果就没有出现用final修饰的局部变量
内存结果分析就是下面我画的图,都在图里面的
4.对上图结果分析总结
- Class 文件常量池(非运行时常量池,本地文件)
- 运行时常量池(方法区内的类的信息下面,每个类都有一个,互不影响)
- 字符串常量池(堆内存,所有类共享)
- 基本类型常量池(堆内存,所有类共享)
第二个关于常量池位置试验(参考即可)
实验思想,
第一种情况:在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的值和内存
结果如下:
结果显示
值不同,但是没报错,打印结果值不同,内存地址不同,
值相同,打印结果值相同,内存地址不同,