关于String对象创建的问题
前言
这里的思路的话,从代码入手就不太合适了,就写吧!
阅读之前建议看一下笔者之前的博客,和这篇博客有很深的关联。
浅谈类的大致加载过程
一丶关于常量池需要了解的东西
在JVM中大致被划分为了五个区域:
数据共享区:虚拟机栈,堆
线程私有区:程序计数器,本地方法栈,方法区
我们在这一部分,最经常提到的就是“字符串常量池”,它是在堆当中的。
而常量池并不仅仅只是这一个。下面给出具体:
- Class文件常量池:每个.Java源文件编译后生成.Class文件中会保存当前类中的字面常量以及符号信息
- 运行时常量池:在.Class文件被加载时,.Class文件中的常量池被加载到内存中称为运行时常量池,运行时常量池每个类都有一份
- 字符串常量池
(1)class常量池(静态常量池)
这里的静态常量池其实就是class常量池。class常量池实际上就是一个二进制数据流,和数组有些类似。
在java代码的编译期间,我们编写的java文件就被编译为.class文件格式的二进制数据存放在磁盘中。一份完整的class文件包含版本信息,接口,字段,属性等信息,class常量池就是包含在其中。其中class常量池当中存的其实就是字面量和符号引用
这里有两点需要说明一下:
1.关于字面量:就是文本字符串和被final修饰的成员变量(比如静态变量,实例变量,静态变量等)
2.关于符号引用:
1>类的全限定修饰名
2>类的字段名称和描述符,字段也就是类或者接口中生命的变量。而且如果是局部变量,那么常量池仅仅保留字段名
3>方法中的名称和描述符
(2)运行时常量池
运行时常量池是方法区的一部分,我们class文件编辑好了,现在该解释运行到JVM内存里面了。在这个时候,就会生成运行时的常量池。
我们知道,一个完整的class文件,也就是这个类被执行的过程总共要分为加载,验证,准备,解析,初始化这几个阶段。而验证,准备,解析又被成为链接阶段。而当这个类被送入内存当中的时候,JVM就会把class常量池中的东西给送到运行时常量池当中,所以运行时常量池每个类都会有一个。
关于加载阶段
加载阶段:
1>通过一个类的全限定修饰名来获取当前这个类的二进制字节流
2>将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3>在内存当中生成一个类的.class对象,作为当前类的数据访问接口
这里需要说明的点:
关
于
类
的
对
象
和
普
通
对
象
\color{red}{关于类的对象和普通对象}
关于类的对象和普通对象
类的对象是在类的加载阶段完成,是JVM创建的,而普通对象是在new调用之后创建的。
关 于 字 节 流 转 换 问 题 \color{red}{关于字节流转换问题} 关于字节流转换问题
class字节流代表的静态存储结构转化为方法区的运行时数据结构,其实就是class文件常量池进入运行时常量池的过程
关于解析阶段(resolve)
这一个阶段也是我要提到的,因为resolve阶段玩的就是常量池。
我们在上面说了,class常量池存的就是字面量和符号引用,就是说它们存的根本就不是对象实例,而是符号引用。所以到了resolve阶段后,我们会把运行时常量池中的符号引用替换为直接引用,也就是换成对应地址。在这个过程中,解析时候他会去查询字符串常量池,以此来保证运行时常量池所引用字符串和字符串常量池中引用一致。(PS:这里的直接引用也是在存储在运行时常量池当中。)
运行时常量池相对于class常量池一大特征就是具有动态性,java规范并不要求常量只能在运行时才产生,也就是说运行时常量池的内容并不全部来自class常量池,在运行时可以通过代码生成常量并将其放入运行时常量池中,这种特性被用的最多的就是String.intern()。
(PS:后面会有关于intern方法的实例)
(3)字符串常量池
所谓字符串常量池,其实就是一个StringTable类,它本质上就是一个hash表,里面存的其实就是字符串的引用,而且这个字符串常量池被所有的类所共享。这是个纯运行时的结构,而且是惰性(lazy)维护的。注意它只存储对java.lang.String 实例的引用,而不存储 String 对象的内容。 注意,它只存了引用,根据这个引用可以得到具体的 String 对象。
二丶关于两种创建对象的方式
(1)常量池储存字符串的方式
首先,在讲解创建对象的具体方式之前的,要先知道字符串常量池当中储存字符串的具体方法。我们前面说了,字符串常量池的存储方式其实就是HashTable,这里的话笔者对这里的东西尽力叙述一下,因为真的把握不住。
如果我们用一个像数组这样的东西来表示hash表,那么每一个hash表的位置存储的其实就是对应节点的位置。然后节点连节点,看图吧。
这里叙述一下,每一个节点后面还可以继续连结点,组成一个队列。
对这里叙述完毕之后。那么接下来的创建对象的方式,就可以进行叙述了。
(2)关于new和直接赋值
这两种创建的方式的大概原理在之前都已经说过了,这里的话进行一下细化。
首先它们都会在栈上有引用地址,但是一个引用的是堆上的对象地址,一个是字符串常量池中的地址。
然后这里的话,用一个图来解释吧。
所以在这里:
S
1
:
0
x
444
,
S
2
:
0
x
111
\color{red}{S1:0x444,S2:0x111}
S1:0x444,S2:0x111
三丶总结
还是有点不太满意,对于字符串部分有点虎头蛇尾的感觉,想好好写好的,但是发现以自己目前的知识量不能很好的解释,所以后续等知识体系更加完善了,再好好补充一下吧。