从面试题说起
这行代码 String s = new string (“ddd”), 它创建了几个对象.
一个对象, 两个对象? 懵逼了吧, 我们细细说来
从常量谈起
java 中由 final 修饰的变量,就是常量, 有局部变量, 静态变量, 实例变量三种
常量池
- 编译时的常量池
java编译后会生成class字节码, class字节码在魔数和版本号之后就是常量池, 常量池之后主要就是访问描述符号, 类父类, 接口的信息各种表了
在常量池里面存储两类信息, 字面量和符号引用. 字面量就是字符串, 符号引用 就是java类中各部分, 包括属性, 方法, 类, 接口的字符串描述. 在第一次使用某变量和方法时, 会从符号引用拿到对应的名字直接地址, 然后从方法区拿到对应的信息.
- 运行时的常量池
java程序在运行时会有一个运行时常量池, 运行时常量池是方法区的一部分, 里面会加载编译时的静态常量池,加载的时机应该就是类生命周期中的连接中的解析阶段
解析阶段官方文档说法是将符号引用转化为直接引用,我的理解就是为java字节码中涉及到的各个信息在方法区分地址,再把符号引用指向对应的地址
加载
连接
准备 验证 解析
初始化
- String的常量池
Jvm 为String设计了一个运行时的常量池, 也在方法区中, 每次创建新的字符串时, 会先从方法区中找, 没有就会新建一个放入方法区中的常量池, 有就会直接将引用指向那里.
- java 语言层面的常量池
大部分包装类创建的时候如果是自动包装, 会调用 valueOf方法, 包装类内部会有一个静态类, 里面维护了一个数组做cache, 这样当有新的包装类对应的值需要创建对象的时候, 会先从内部的cache开始找, 有的话直接返回对应的引用, 如果没有在堆中生成新的对象
回到 String的创建
- 谈一谈 String s = “ddd”
针对这种代码, java在编译器就会生成一个 ddd的字面量常量, 放入常量池中, 运行时, 类被加载时, 该常量和静态常量池的其他常量一起被放入方法区中
所以说 String s = “ddd”会创建一个对象, “ddd”是在类加载的时候创建的
- 再谈 String s = new String(“ddd”);
首先, 编译期在字节码中会有一个常量池, 常量池前面一部分是字面量, ddd作为字面量会被保存在其中, 然后在运行时,
方法区中存在运行时常量池, 静态常量池中的内容会在类加载的时候存入方法区中的常量池中, 这个时候建立的第一个字面量对象ddd,
允许到该代码时创建第二个对象,放在堆中,但实际上还是指向的同一个字节数组
- 方法区的回收
默认是不会开启回收的, 需要加参数开启