类变量存于方法区(JDK 8+为元空间),实例变量存于堆内存;类变量属类、共享且可直接用类名访问,实例变量属对象、独有且须通过对象访问;静态方法不能直接访问实例变量或this;初始化按声明顺序,未显式初始化取默认值;类变量多线程需同步,实例变量天然线程隔离(对象不共享时)。

类变量(static变量)存在方法区,实例变量存在堆内存
Java中static修饰的类变量在类加载时就分配内存,位置是方法区(JDK 8+ 是元空间),整个类只有一份;而实例变量每次new对象时才分配,在堆内存中,每个对象独有一份。
这意味着:改一个类变量,所有对象看到的值都变;改一个实例变量,只影响当前对象。
- 类变量生命周期 = 类的生命周期,从加载到卸载
- 实例变量生命周期 = 对象的生命周期,从
new到被GC回收 - JDK 7及以前,方法区在永久代;JDK 8+ 元空间用本地内存,不会OOM于永久代相关错误
访问方式不同:类变量可直接用类名调用,实例变量必须通过对象
static变量属于类本身,不依赖对象存在,所以能用ClassName.fieldName直接访问;实例变量属于具体对象,没对象就不存在,强行用类名点会编译报错non-static variable xxx cannot be referenced from a static context。
常见误写:
立即学习“Java免费学习笔记(深入)”;
public class Counter {
static int count = 0;
int id = 1;
public static void print() {
System.out.println(count); // ✅ OK
System.out.println(id); // ❌ 编译错误
}
}
- 静态方法里不能直接访问实例变量或
this - 但实例方法可以访问类变量和实例变量(两者都可见)
- 如果非要从静态上下文访问实例变量,得先创建对象:
new Counter().id
初始化时机和顺序影响结果
类变量在类初始化阶段按声明顺序执行赋值或静态代码块;实例变量在对象创建时(即构造器执行前)按声明顺序初始化。若两者混用且有依赖,顺序不对就会出问题。
例如:
public class InitOrder {
static int a = b + 1; // ❌ 编译通过,但运行时a=1(b还没赋值,取默认值0)
static int b = 2;
int x = y + 1; // ❌ 同理,x=1(y取默认值0)
int y = 3;
}
- 静态变量只看声明顺序,不看赋值表达式里的变量是否已定义
- 未显式初始化的
static变量取类型默认值(int→0,Object→null) - 建议把复杂初始化逻辑放进
static {}块,控制明确顺序
多线程下类变量需额外同步,实例变量天然隔离
类变量被所有线程共享,多个线程同时读写static int counter++会导致竞态条件;实例变量每个线程操作的是不同对象的字段,天然线程安全(前提是对象不被共享)。
-
static计数器、缓存、配置单例等,必须加锁或用AtomicInteger等线程安全类型 - 不要因为“只是个
int”就忽略并发问题——i++不是原子操作 - 实例变量若被多个线程共用同一个对象(比如Spring singleton bean里的字段),同样不安全










