
一、Class常量池与运行时常量池核心底层1.1 简介1.1.1 Class常量池静态常量池Class常量池也被称为静态常量池存在于.class 文件中是编译期生成的一张「常量资源表」。Java源码经过javac编译后并不会直接保存绝对内存地址而是将所有字面量和符号引用统一存入Class常量池用于运行时解析。核心特点属于文件层级结构运行前就存在只读、静态、存储编译期信息不包含内存地址只包含符号引用每个Class文件独占一份Class常量池。1.1.2 运行时常量池Runtime Constant Pool当JVM执行类加载加载-验证-准备-解析-初始化阶段会将Class文件常量池的内容加载到内存中存入方法区元空间形成运行时常量池。核心特点属于内存层级结构运行时创建具备动态性Java支持运行时动态生成常量如String.intern()完成符号引用 → 直接引用的解析替换JDK1.8之后存放在元空间本地内存不再占用堆内存。1.2 常见结构图底层逻辑架构1.2.1 整体层级结构Java代码编译与加载完整链路Java源码 → javac编译 → Class文件Class常量池 → 类加载 → 运行时常量池元空间 → 解析为直接引用1.2.2 内存结构归属图Class常量池.class 文件磁盘运行时常量池JDK1.6方法区堆内 / JDK1.7元空间堆外本地内存字符串常量池JDK1.6方法区 / JDK1.7堆内存包装类对象池堆内存常驻内存1.3 常用字节码指令常量池实操指令常量池的所有访问、加载、解析全部依赖JVM字节码指令核心高频指令如下ldc从常量池加载单字常量到操作数栈int、float、String、Classldc_w宽索引版本支持更大常量池索引ldc2_w加载double/long双字常量invokestatic / invokespecial调用常量池记录的方法符号引用getstatic / putstatic访问常量池静态字段符号引用核心作用所有代码的常量读取、方法调用、字段访问本质都是读取常量池索引再解析为真实内存地址。1.4 字面量Literal字面量代码中直接写死的常量值由字母、数字等构成的字符串或者数值常量编译期即可确定存入Class常量池。包含类型字符串字面量abc基本数据类型字面量10、3.14、true、afinal修饰的编译期常量特点编译期确定运行期不可变直接存入Class常量池无依赖、无需解析。1.5 符号引用Symbolic Reference符号引用是Class常量池最核心的数据也是JVM实现跨语言、动态链接的基础。定义以字符串描述的引用关系不包含真实内存地址仅包含文本信息。包含三类类/接口全限定名如 com.xxx.User字段符号引用字段名类型所属类方法符号引用方法名参数列表返回值Lcom/tuling/jvm/Math 是类的全限定名main和compute是方法名称()是一种UTF8格式的描述符这些都是符号引用。这些常量池现在是静态信息只有到运行时被加载到内存后这些符号才有对应的内存地址信息这些常量池一旦被装入内存就变成运行时常量池对应的符号引用在程序加载或运行时会被转变为被加载到内存区域的代码的直接引用也就是我们说的动态链接了。例如compute()这个符号引用在运行时就会被转变为compute()方法具体代码在内存中的地址主要通过对象头里的类型指针去转换直接引用。核心机制动态链接编译期只记录符号引用运行时类加载过程中JVM将符号引用解析为直接引用真实内存指针完成方法、字段的绑定。面试必背区别符号引用编译期、字符串、无地址、存在Class常量池直接引用运行期、内存指针、可直接访问、存在运行时常量池。二、字符串常量池String Pool深度详解2.1 简介字符串常量池String Constant Pool是JVM为了节省内存、复用字符串对象设计的全局缓存池是所有常量池中面试最难、版本差异最大的核心考点。所有双引号声明的字符串都会优先尝试从字符串常量池中获取实现字符串复用、避免重复创建。2.2 设计思想与设计原理2.2.1 核心设计思想String对象在业务代码中创建量极大、重复度极高如果每次创建都new新对象会造成严重内存浪费极大程度地影响程序的性能。JVM基于享元模式Flyweight设计字符串常量池重复字符串只存一份全局共享。2.2.2 底层原理字符串常量池底层是一个全局哈希表Hashtablekey为字符串内容value为字符串对象引用。创建字符串时JVM逻辑判断常量池是否存在该字符串存在直接返回池内引用不存在创建对象放入池中返回引用。2.3 三种字符串操作 JDK1.6 / 1.7 差异2.3.1 直接赋值方式最常用String s abc;执行逻辑编译期进入Class常量池运行时优先从字符串常量池取值。2.3.2 new String方式String s new String(abc);必会结论创建两个对象一个在字符串常量池编译期一个在堆内存运行期new。2.3.3 intern() 手动入池方法核心考点String.intern()主动将字符串放入常量池返回池中引用。JDK1.6 与 JDK1.7 重大差异面试必考JDK1.6intern() 会复制一份对象放入永久代常量池堆和池是两个独立对象JDK1.7intern()不会复制对象如果堆中已有对象常量池直接存储堆对象的引用地址极大节省内存。为了更好理解以上的赋值方式,以下举了个例子便于理解:代码:String s1 new String(he) new String(llo); String s2 s1.intern(); System.out.println(s1 s2); // 在 JDK 1.6 下输出是 false创建了 6 个对象 // 在 JDK 1.7 及以上的版本输出是 true创建了 5 个对象1、在 JDK 1.6 中调用 intern() 首先会在字符串池中寻找 equal() 相等的字符串假如字符串存在就返回该字符串在字符串池中的引用假如字符串不存在虚拟机会重新在永久代上创建一个实例将 StringTable 的一个表项指向这个新创建的实例。2、在 JDK 1.7 (及以上版本)中由于字符串池不在永久代了intern() 做了一些修改更方便地利用堆中的对象。字符串存在时和 JDK 1.6一样但是字符串不存在时不再需要重新创建实例可以直接指向堆上的实例。由上面两个图也不难理解为什么 JDK 1.6 字符串池溢出会抛出 OutOfMemoryError: PermGen space 而在 JDK 1.7 及以上版本抛出 OutOfMemoryError: Java heap space 。2.4 字符串常量池位置演变JDK1.6存放在方法区永久代JDK1.7从永久代移至堆内存JDK1.8继续保留在堆内存永久代彻底废弃改为元空间迁移原因永久代内存固定、容易溢出、GC效率极低堆内存动态扩容、GC回收高效更适合字符串池高频增减特性。2.5 JDK1.6 和 JDK1.7 常量池完整差异汇总对比维度JDK1.6JDK1.7/1.8存放位置永久代方法区堆内存intern()机制复制新对象入池存储堆对象引用不复制内存溢出风险高永久代固定大小低堆内存动态伸缩GC回收效率差永久代GC频率低高随堆GC自动回收无效常量对象复用逻辑堆与池对象相互独立池直接引用堆对象内存更省三、八种基本类型包装类与对象池缓存池3.1 核心简介为了节省内存、提升基础数据运算效率JVM对八种基本类型包装类提供了对象缓存池对象池机制也叫Integer Cache / Byte Cache。核心设计思想高频小数值提前缓存全局复用不重复创建对象。3.2 各包装类缓存范围Byte全部缓存-128 ~ 127Short-128 ~ 127Integer默认 -128 ~ 127可通过JVM参数修改上限Long-128 ~ 127Character0 ~ 127Boolean全部缓存true/false 两个全局常量Float / Double无缓存池浮点数值无限无法缓存3.3 底层原理以Integer为例底层静态代码块在类加载时提前初始化 -128~127 的所有Integer对象存入cache数组。当使用Integer.valueOf()自动装箱时数值在缓存区间直接返回缓存数组对象数值超出区间new 新对象。注意new Integer()永远不走缓存直接新建对象。3.4 经典面试题原理Integer a 100; Integer b 100; System.out.println(a b); // true 缓存复用 Integer c 200; Integer d 200; System.out.println(c d); // false 超出缓存两个新对象3.5 自定义Integer缓存上限JVM启动参数可修改Integer缓存最大值-XX:AutoBoxCacheMax256作用扩大Integer缓存池范围减少自动装箱对象创建提升性能。四、全文总结Class常量池编译期、磁盘文件、存字面量符号引用、静态只读。运行时常量池运行期、元空间、动态解析、符号引用转直接引用。符号引用字符串描述、无内存地址直接引用真实内存指针。字符串常量池JDK1.6永久代、JDK1.7堆内存intern()机制1.7开始优化为引用复用。包装类对象池Integer/Byte/Short/Long/Char/Boolean支持缓存浮点型无缓存默认缓存-128~127。