类的常量和静态变量相关知识

类的的常量放哪里呢?和平时的常量一样吗?放到堆的常量池中

非常好的问题!这里“类的常量”要分两种情况来看,容易混淆。我们要区分:
1. 编译期常量(Compile-time Constant)
2. 运行期常量(Runtime Constant)

🔹1. 编译期常量(static final + 基本类型/字符串字面量)
• 典型例子:

class Demo {
static final int A = 10;
static final String S = “Hello”;
}

•	特点:
•	这些常量在编译期就确定了值。
•	编译器会直接把常量值写死到字节码中,使用时不会访问 Demo.A,而是直接用常量字面量。
•	不需要在运行时分配额外存储位置。
•	存储位置:
•	数字等字面量存在 Class 文件的常量池(Constant Pool Table) 中。
•	运行时这些字面量会放在 JVM 的运行时常量池中(在元空间里),如果是字符串字面量,还会加载到堆的字符串常量池。

🔹2. 运行期常量(static final 但值在运行时才能确定)
• 典型例子:

class Demo {
static final int B = new Integer(5); // 或调用方法计算得出
}

•	特点:
•	编译期无法确定,必须运行时初始化。
•	运行时会在堆中分配内存,并存储在类变量表(堆上),和普通静态变量类似。

🔹3. 和“普通常量池”的关系
• Java 中的“常量池”分两种:
1. Class文件常量池:class 文件里存字面量、符号引用、字段/方法引用等。
2. 运行时常量池:类加载时,把 Class 文件常量池加载到内存的元空间。
• 字符串字面量会进一步进入 堆中的字符串常量池(String Intern Pool),保证相同字面量共享同一个对象。

🔹存储位置总结表

常量类型 编译期已知值? 存放位置 说明
static final int A = 10 ✅ Class文件常量池 → 运行时常量池(元空间) 编译期直接内联,无需运行时存储
static final String S = “Hello” ✅ 常量池 → 字符串常量池(堆) 共享字面量
static final int B = foo()(运行时求值) ❌ 类变量表(堆),对象在堆 和静态变量一样

🔑一句话记忆:

真正的编译期常量(static final + 编译期可确定值)在编译时就直接写死在字节码里,不会真正分配“类静态字段内存”;
其他 static final 常量在运行时和普通静态变量类似,放堆中类变量表,引用的对象也在堆。

💡所以:
• 不是所有常量都放“堆的常量池”,只有字符串字面量和部分符号引用会加载到堆中的字符串常量池,
• 编译期常量更多是“写死在字节码”,而不是动态放在堆中。

编译期常量更多是“写死在字节码”,这里我以为是将所有代码引用了该常量的地方,编译后直接放对应的值。同时,我不知道“Class 文件的常量池”,这个直接在class编译后的文件中?运行时这些字面量会放在 JVM 的运行时常量池中(在元空间里),这里的先后顺序讲清楚一些

很好 — 你的想法基本正确,我把“先后顺序”和各处到底放什么讲得更严谨、按步骤来,带上一个小例子帮助记忆。

先给结论(一句话)
• 编译期常量:编译器会把常量值“写入(内联到)引用它的类的字节码 / 常量池里”,因此被引用处在编译后直接使用该值。
• Class 文件常量池:确实存在于 .class 文件里,是编译器把字面量和符号引用放进 class 文件的常量表。
• 运行时常量池:类加载时 JVM 会把 class 文件的常量池转换成该类的运行时常量池(属于方法区/元空间的一部分),其中的某些常量会在运行时被解析并(例如字符串)放到堆的字符串常量池里。

详细顺序(从源码到运行时)
1. 编译阶段(javac)
• 编译器把源代码编译为 .class。
• 所有字面量、符号引用等被放入该类的 Class 文件常量池(constant_pool)(这是 class 文件格式的一部分)。
• 对于 编译期可确定的 static final 常量(基本类型或字面字符串),编译器会内联:
• 访问该常量的其他类在编译时,会把常量值写入到引用类自己的 class 文件常量池 / 字节码中(即引用处不再在运行时去访问定义常量的类),所以引用处“看起来”是直接用了常量值。
• 因此如果你只改了常量定义类并且只重新加载/替换了该类,引用它的其它类不会自动看到新值,除非那些类也被重新编译(或重新加载)。
2. 类加载时(JVM)
• JVM 读取 .class,把 Class 文件常量池 转换为 运行时常量池(Runtime Constant Pool),这个运行时常量池属于方法区/元空间的数据结构(HotSpot 实现上是方法区的一部分,元空间存放类元数据)。
• 运行时常量池里存的是符号引用、字面值等,某些条目在“解析(resolution)”时会被转换为直接指针/引用(延迟解析,按需解析)。
3. 解析与存放
• 基本类型常量(int、float 等)通常以值形式存放在运行时常量池条目里或被编译成字节码常量指令(如 iconst、ldc)直接使用。
• 字符串字面量:在解析/初始化时,JVM 会在**堆上的字符串池(String Intern Pool)**创建(或复用)一个 java.lang.String 对象;运行时常量池中相应条目可以引用这个堆上的字符串对象。
• 符号引用(方法/字段等):在第一次用到时解析成直接方法/字段指针。
4. 运行时使用
• 字节码(如 ldc)从该类的运行时常量池取常量(或经过解析后的引用)执行。
• 如果别的类在编译时已将常量内联,那么它将从自己运行时常量池取值,而不是去读取定义常量的类。

举个小例子说明“内联带来的副作用”

// A.java
public class A {
public static final int X = 1; // 编译期常量
}

// B.java
public class B {
public static void print() {
System.out.println(A.X); // 编译时会把 1 内联到 B 的字节码/常量池
}
}

•	编译 A、B 后,B 的字节码中已经把 1 放入自己的常量池/字节码。
•	如果你改 A.X = 2; 并只 重新编译 A(不重新编译 B),运行 B.print() 仍然打印 1 —— 因为 B 在编译时已经内联了原来的值。

小结表(方便记忆)
• Class 文件常量池:存在于 .class 文件中(编译器写入)。
• 运行时常量池:类加载时由 JVM 创建,属于方法区/元空间的一部分,存运行时需要的常量条目(解析可产生堆对象引用)。
• 字符串常量池(intern):实际 String 对象放在 堆(以便共享),运行时常量池的字符串条目会指向堆中的 String 对象。
• 编译期常量内联:编译器把值写入引用类的 class 文件/字节码,引用处不再在运行时去访问定义处。

类的静态变量不是放在堆区吗?

感谢你提出这个问题。关于 Java 中静态变量的存储位置,确实存在一些概念上的混淆,特别是在不同的 JVM 实现和版本之间。为了帮助你理清这个问题,我将详细分析 Java 8 及以上版本中静态变量的存储机制,并结合官方文档和权威资料进行解释。()

🔍 静态变量的存储位置(Java 8 及以上)

  1. 类元信息(Class Metadata)
    • 存储位置:元空间(Metaspace)()
    • 内容:类的结构信息,包括类名、父类、字段、方法的定义等。()
    • 说明:在 Java 8 之前,这部分信息存储在永久代(PermGen)中。()

  2. 静态变量的存储
    • 存储位置:堆内存(Heap)()
    • 内容:静态变量的实际值,包括基本类型的值和对象引用。()
    • 说明:静态变量的存储位置已从 Java 8 开始发生变化。()

🧠 Java 8 之前的情况
• 永久代(PermGen):在 Java 8 之前,静态变量存储在永久代中。永久代是 JVM 的一部分,用于存储类的元数据、静态变量和常量池等信息。()
• 堆内存:对象实例和数组等数据存储在堆内存中。()

🔄 Java 8 之后的变化
• 元空间(Metaspace):Java 8 引入了元空间,替代了永久代。元空间存储类的元数据,但不再存储静态变量的实际值。()
• 堆内存:静态变量的实际值(如基本类型的值和对象引用)存储在堆内存中。()
• 类数据区(Class Data Area):JVM 为每个类分配一块内存区域,存储该类的静态变量。该区域位于堆内存中。()

📘 官方文档说明

根据《Java 虚拟机规范》第 2.5.4 节:

方法区是 JVM 的一部分,用于存储类结构、字段和方法数据,以及方法和构造函数的代码。 

方法区是所有线程共享的内存区域。

方法区的实现可以选择是否进行垃圾回收。()

这意味着,方法区主要存储类的结构信息,而静态变量的实际值存储在堆内存中。 ()

✅ 总结
• 类的元信息(如类名、字段、方法定义等)存储在 元空间 中。
• 静态变量的实际值(如基本类型的值和对象引用)存储在 堆内存 中。
• 类数据区:JVM 为每个类分配一块内存区域,存储该类的静态变量。该区域位于堆内存中。()

就算类的常量是字符串常量,在运行时常量池(元空间中),也有对应的引用吧,这个引用指向堆中字符串常量池的字符串?

完全正确,你理解得很精确!我把这个过程仔细梳理一下:

🔹 1. 编译期字符串常量

class Demo {
static final String S = “Hello”;
}

•	"Hello" 是编译期字符串字面量。
•	编译器会把它放入 Class 文件常量池(constant_pool)里。
•	对于引用它的地方,编译器可能直接内联(比如方法里使用 "Hello"),也可能保留为对常量池条目的引用。

🔹 2. 类加载后(运行时常量池)
• 类被加载到 JVM,Class 文件的常量池会转换成 运行时常量池(元空间中)。
• 运行时常量池中对应 “Hello” 的条目存放的 不是字符串对象本身,而是 指向字符串对象的引用。
• 字符串对象本身存在 堆中的字符串常量池(String Intern Pool)。

🔹 3. 内存关系

元空间(运行时常量池):
└── S (Runtime Constant Pool Entry) ──▶ 引用

堆(String Intern Pool):
└── “Hello” 对象 (java.lang.String)

•	访问 Demo.S 时,JVM 会通过运行时常量池的条目找到堆上的字符串对象。
•	因此即使是常量,也有一条“引用链”指向堆上的对象。

🔹 4. 总结
1. 编译期常量的值(字符串)存入 Class 文件常量池。
2. 类加载后,常量池条目进入 运行时常量池(元空间)。
3. 字符串对象驻留在 堆的字符串常量池。
4. 元空间中的运行时常量池条目持有一个 指向堆中字符串对象的引用。

💡一句话记忆:

字符串常量在元空间有条目,条目里存引用,指向堆上的字符串对象。

那静态变量什么时候被回收?它的生命周期为什么说一般和程序生命周期一样

好问题!这个点很多人容易误解。我们仔细拆开来看:

🔍 1. 静态变量的存储和生命周期
• 存储位置:
静态变量本身(变量引用或基本类型的值)存储在 Java 堆区的类对象(java.lang.Class实例)里。
每个类在加载后,JVM 会创建一个对应的 Class 对象放在堆区,静态变量跟着这个 Class 对象走。
• 生命周期:
静态变量的生命周期 ≈ Class 对象的生命周期。
而 Class 对象什么时候被回收,取决于:
1. 该类的 ClassLoader 被回收;
2. 程序中没有该类的任何实例或引用;
3. JVM 确定该类不再使用。
一般情况下,应用的主类和常用类是由系统类加载器(AppClassLoader)加载的,这个加载器生命周期和整个 JVM 进程一致,所以类也不会卸载,静态变量自然也就“和程序同生共死”。

🔍 2. 为什么看起来像“程序生命周期一致”

因为在大多数应用中:
• 系统类加载器不会被回收;
• 主业务类和核心工具类不会卸载;
• 所以静态变量的生命周期和 JVM 生命周期一致,看起来“静态变量就不会被回收”。

但这是因为类一直被引用着,并不是 JVM 特意让静态变量永远不回收。

🔍 3. 特殊情况:静态变量也会被回收

如果:
• 某个类是由自定义 ClassLoader 加载的;
• 卸载掉这个 ClassLoader,并且清理所有相关引用;

那么这个类对应的 Class 对象会被 GC 回收,同时类里的静态变量也会被回收。

这在 应用服务器、插件系统、热加载场景 下很常见,比如:
• Tomcat/Jetty 的 WebAppClassLoader;
• OSGi 模块热插拔;
• Java Agent 动态加载。

🔍 4. 重点总结

对象 存储位置 生命周期
静态变量本身(字段) Class 对象中(堆区) 跟随 Class 对象
Class 元数据 元空间(Metaspace) 跟随 ClassLoader
常量池项 元空间中(运行时常量池) 跟随 ClassLoader
静态变量引用指向的对象 堆区 普通对象,GC 根可达时不回收

•	所以静态变量“通常和程序同生命周期”是因为类不卸载。
•	但如果类被卸载,静态变量也可以被回收。

💡 一句话记忆:

静态变量本身存活在堆里的 Class 对象中,生命周期取决于类加载器是否还活着;大多数情况下类加载器伴随 JVM 生命周期,所以看起来和程序同生共死。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值