类变量存于方法区(jdk 8+为元空间),随类加载分配、类卸载释放,所有实例共享;实例变量存于堆内存,随对象创建分配、gc回收时释放,各对象独有一份。

Java中类变量(静态变量)和实例变量的内存布局差异,核心在于它们归属的对象不同:一个属于类,一个属于对象。这直接决定了它们在JVM内存中的落脚点、生命周期和共享行为。
类变量存在方法区(JDK 8+为元空间)
类变量用static修饰,在类加载阶段就完成内存分配,位置在方法区——JDK 8起该区域被元空间(Metaspace)取代,使用本地内存而非堆内存。
- 随类加载而存在,随类卸载而释放,生命周期与类一致
- 所有实例共享同一份数据,修改任一地方,其他访问立即可见
- 不归垃圾回收器(GC)常规管理;若长期持有大对象引用,易引发内存泄漏
- JDK 7以前存于永久代(PermGen),错误提示为
OutOfMemoryError: PermGen space;JDK 8+变为OutOfMemoryError: Metaspace
实例变量分配在堆内存中
实例变量即非static字段,是对象状态的一部分,每次执行new时,连同对象头、实例数据一起在堆中开辟独立空间。
- 每个对象拥有自己的一份副本,互不影响
- 生命周期绑定对象:对象创建时分配,GC回收该对象时一并清理其成员变量
- 即使多个对象属于同一类,它们的同名实例变量在堆中地址不同、内容独立
- 受GC强管控——但前提是对象本身可被回收;若被静态集合、缓存等强引用持有,成员变量也会“陪绑”驻留
验证方式不能靠代码打印,得靠JVM工具
无法通过System.identityHashCode()或==判断变量所在区域,因为这些操作反映的是对象标识或引用关系,不是内存段归属。
-
jhsdb jmap --heap --pid <pid></pid>可查看堆使用概况,但看不到方法区细节 - 要确认类变量是否挂载在类结构上,需进入调试模式:
jhsdb clhsdb --pid <pid></pid>,再执行inspect ClassName,观察静态字段是否出现在Klass结构下 - 配合
jstat -class或jstat -gc可辅助分析类加载与堆回收行为
访问方式与线程可见性也由此衍生
存储位置差异进一步影响了编程约束和并发行为:
- 类变量可通过
ClassName.field直接访问,无需实例;实例变量必须通过对象引用,否则编译报错non-static variable cannot be referenced from a static context - 方法区线程共享 → 类变量天然跨线程可见,多线程读写需同步(如synchronized、volatile或Atomic类)
- 堆虽也是线程共享区域,但实例变量需通过各自对象引用访问 → 若对象不被多线程共享,其实例变量天然具备线程隔离性
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











