Java static变量保存在哪?

本文通过实战案例,详细解析了Java对象在内存中的布局情况,包括对象头、实例数据及填充对齐等内容,并展示了如何使用工具进行内存布局的观察。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

测试环境:

Microsoft Windows [版本 10.0.17134.165]
java -version
java version "1.8.0_171"
Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)

测试代码:

import java.io.IOException;

public class Main {
    private static String name = "lgh";
    private static int age = 26;

    public int fun() {
        try {
            System.out.println(name);
            System.out.println(age);
            return System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return 0;
    }

    public static void main(String[] args) {
        new Main().fun();
    }
}

编译&运行:

D:\N3verL4nd\Desktop>javac Main.java

D:\N3verL4nd\Desktop>java -XX:+UseSerialGC -XX:-UseCompressedOops -Xms10m -Xmx10m Main
lgh
26

System.in.read() 的作用等同于断点。

使用 CLHSDB 连接:

// 查看进程 id
D:\>jps
5792 Jps
7932 Main

D:\>java -cp .;%JAVA_HOME%/lib/sa-jdi.jar sun.jvm.hotspot.CLHSDB
hsdb> attach 7932
Attaching to process 7932, please wait...

运行 universe

Heap Parameters:
Gen 0:   eden [0x0000000012600000,0x00000000127114d0,0x00000000128b0000) space capacity = 2818048, 39.7239507630814 used
  from [0x00000000128b0000,0x00000000128b0000,0x0000000012900000) space capacity = 327680, 0.0 used
  to   [0x0000000012900000,0x0000000012900000,0x0000000012950000) space capacity = 327680, 0.0 usedInvocations: 0

Gen 1:   old  [0x0000000012950000,0x0000000012950000,0x0000000013000000) space capacity = 7012352, 0.0 usedInvocations: 0

[eden] 0x00000000128b0000 - 0x0000000012600000 = 2B 0000(1260 0000)
[from] 0x0000000012900000 - 0x00000000128b0000 = 5 0000(120 0000)
[to] 0x0000000012950000 - 0x0000000012900000 = 5 0000(120 0000)

可以看到 eden:from:to 大致比例为8:1:1,可以看到新生代的[eden-from-to]内存是连续的。同时可以看新生代和老年代内存是连着的。大概和垃圾回收方式有关。

扫描我们的 Main 实例:

hsdb> scanoops 0x0000000012600000 0x00000000128b0000 Main
0x000000001270afd8 Main
hsdb> whatis 0x000000001270afd8
Address 0x000000001270afd8: In thread-local allocation buffer for thread "main" (1)  [0x0000000012703870,0x000000001270b6e8,0x00000000127114b8,{0x00000000127114d0})

hsdb> inspect 0x000000001270afd8
instance of Oop for Main @ 0x000000001270afd8 @ 0x000000001270afd8 (size = 16)
_mark: 1
_metadata._klass: InstanceKlass for Main
hsdb>

可见,Main 实例分配在了线程私有的 TLAB 中。
Main 类没有实例变量,所以他的大小是 16 字节,Mark Word + Klass 指针(64 位 JVM 关闭压缩指针的情况下)。

使用 inspect 命令没有显示出来 InstanceKlass 也就是类型指针的地址,据说是 HSDB 的bug。我们使用 mem 来获取更详细的信息。

hsdb> mem 0x000000001270afd8 2
0x000000001270afd8: 0x0000000000000001 // Mark Word
0x000000001270afe0: 0x0000000013400598 // 类型指针(与Mark Word 一起组成对象头)

由于 1 个十六进制位代表 4 个二进制位,所以以上 Mark Word 的最后一位 1 代表的二进制序列为0001。
这里写图片描述
也就是 Main 实例处在无锁状态。

查看该类型指针对应的数据:

hsdb> inspect 0x0000000013400598
Type is InstanceKlass (size of 440)
juint Klass::_super_check_offset: 48
Klass* Klass::_secondary_super_cache: Klass @ null
Array<Klass*>* Klass::_secondary_supers: Array<Klass*> @ 0x0000000013000f88
Klass* Klass::_primary_supers[0]: Klass @ 0x0000000013001c00
oop Klass::_java_mirror: Oop for java/lang/Class @ 0x0000000012709dc8 Oop for java/lang/Class @ 0x0000000012709dc8

或者使用 HSDB :
这里写图片描述

D:\Java\Tools\jol>java -XX:-UseCompressedOops -jar jol-cli.jar internals java.lang.Class
# Running 64-bit HotSpot VM.
# Objects are 8 bytes aligned.
# Field sizes by type: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 8, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]

Failed to find matching constructor, falling back to class-only introspection.

java.lang.Class object internals:
 OFFSET  SIZE                                              TYPE DESCRIPTION                               VALUE
      0    16                                                   (object header)                           N/A
     16     8                     java.lang.reflect.Constructor Class.cachedConstructor                   N/A
     24     8                                   java.lang.Class Class.newInstanceCallerCache              N/A
     32     8                                  java.lang.String Class.name                                N/A
     40     8                                                   (alignment/padding gap)
     48     8                       java.lang.ref.SoftReference Class.reflectionData                      N/A
     56     8   sun.reflect.generics.repository.ClassRepository Class.genericInfo                         N/A
     64     8                                java.lang.Object[] Class.enumConstants                       N/A
     72     8                                     java.util.Map Class.enumConstantDirectory               N/A
     80     8                    java.lang.Class.AnnotationData Class.annotationData                      N/A
     88     8             sun.reflect.annotation.AnnotationType Class.annotationType                      N/A
     96     8                java.lang.ClassValue.ClassValueMap Class.classValueMap                       N/A
    104    40                                                   (alignment/padding gap)
    144     4                                               int Class.classRedefinedCount                 N/A
    148     4                                                   (loss due to the next object alignment)
Instance size: 152 bytes
Space losses: 48 bytes internal + 4 bytes external = 52 bytes total

使用 jol 获得 Class 对象的大小为 152,也就是 19 个字长。

hsdb> inspect 0x0000000012709dc8
instance of Oop for java/lang/Class @ 0x0000000012709dc8 @ 0x0000000012709dc8 (size = 176)
name: "lgh" @ 0x000000001270af80 Oop for java/lang/String @ 0x000000001270af80
age: 26
hsdb> mem 0x0000000012709dc8 22
0x0000000012709dc8: 0x0000002a139a5501 // 1
0x0000000012709dd0: 0x0000000013013ed0 // 2
0x0000000012709dd8: 0x0000000000000000 // 3
0x0000000012709de0: 0x0000000000000000 // 4
0x0000000012709de8: 0x0000000000000000 // 5
0x0000000012709df0: 0x00000000126e5348 // 6
0x0000000012709df8: 0x000000001270a4c8 // 7
0x0000000012709e00: 0x0000000000000000 // 8
0x0000000012709e08: 0x0000000000000000 // 9
0x0000000012709e10: 0x0000000000000000 // 10
0x0000000012709e18: 0x0000000000000000 // 11
0x0000000012709e20: 0x0000000000000000 // 12
0x0000000012709e28: 0x0000000000000000 // 13
0x0000000012709e30: 0x00000000127097d0 // 14
0x0000000012709e38: 0x0000000000000000 // 15
0x0000000012709e40: 0x0000000000000000 // 16
0x0000000012709e48: 0x0000000013400598 // 17 类型指针
0x0000000012709e50: 0x0000000000000000 // 18
0x0000000012709e58: 0x0000001600000000 // 19
0x0000000012709e60: 0x0000000000000001 // 20
0x0000000012709e68: 0x000000001270af80 // 21 "lgh" 的引用
0x0000000012709e70: 0x000000000000001a // 22 "26" 的 16 进制表示

可以看到 static 变量保存在 Class 实例的尾部。
Class 对象确实在堆中。
类型指针保存在 Class 实例 17 * 8 的位置上。

<think>嗯,用户问的是静态变量Java中的作用。我需要先回忆一下静态变量的基本概念,然后想想它的主要用途。首先,静态变量属于类,而不是类的实例,这点很重要。那它们有什么具体的作用呢?可能包括共享数据、节省内存、工具类方法这些方面。 我记得静态变量在内存中只有一份拷贝,所有实例共享这个变量。这样当多个对象需要共享某个数据的时候,比如计数器或者配置项,静态变量就很有用了。例如,记录一个类被实例化了多少次,每次构造函数里给静态计数器加一,这样所有实例都共享这个计数器,对吧? 然后是节省内存。因为静态变量不属于实例,所以不管创建多少对象,静态变量只占一份内存空间。这对于那些需要被所有实例共同访问的变量来说,可以节省不少内存资源。比如,一个类的默认配置值,用静态变量存储的话,每个实例就不需要单独存储一份了。 工具类方法,比如Math类里的各种数学方法,都是静态的。这样不需要创建对象就能直接调用,方便使用。比如Math.sqrt(),直接用类名调用,不需要new一个Math实例。这可能也是静态方法的一个好处,但用户问的是静态变量,不过有时候静态变量和静态方法会一起用在工具类里。 还有单例模式,静态变量在这里用来持有唯一的实例。通过私有构造函数和静态变量来确保只有一个实例存在,并提供全局访问点。这应该是单例模式的核心,用静态变量保存那个唯一的实例。 不过要注意的是,静态变量生命周期长,会一直存在直到程序结束,可能导致内存泄漏。比如如果静态变量引用了大对象,即使不再需要,也无法被GC回收。这点需要提醒用户注意。 另外,多线程环境下,静态变量可能引发线程安全问题,因为所有线程都共享同一份变量。需要同步机制来处理,比如加锁或者使用Atomic类型。这也是使用静态变量时需要考虑的问题。 用户可能是在学习Java基础,或者在实际项目中遇到了共享数据的问题,想了解如何高效管理这类数据。他们可能对静态变量的使用场景和注意事项不太清楚,需要具体的例子来说明。比如工具类中的常量,或者单例模式的实现方式。 需要确保回答结构清晰,分点说明作用,并给出代码示例,这样用户更容易理解。同时要提到优缺点,避免用户误用。比如静态变量虽然方便,但滥用可能导致问题,所以需要根据实际情况决定是否使用。</think>在Java中,静态变量(用`static`修饰的成员变量)有以下几个核心作用: --- ### 一、数据共享 所有类的实例**共享同一份静态变量**,常用于: ```java class Counter { static int count = 0; // 所有实例共享的计数器 Counter() { count++; } } // 测试: new Counter(); new Counter(); System.out.println(Counter.count); // 输出2 ``` --- ### 二、内存优化 - **不依赖实例存在**:静态变量在类加载时初始化,不随对象创建重复分配内存 - **适用场景**:存储类级别的公共配置(如默认值、常量) ```java class Constants { static final double PI = 3.14159; // 常量只需存储一份 } ``` --- ### 三、工具类支持 配合静态方法实现**无需实例化即可调用**的工具类: ```java class MathUtils { static final int MAX_RETRY = 3; // 工具类参数 static int add(int a, int b) { return a + b; } } // 直接调用: MathUtils.add(2, 3); ``` --- ### 四、单例模式实现 通过静态变量保证**全局唯一实例**: ```java class Singleton { private static Singleton instance; // 静态持有唯一实例 private Singleton() {} // 私有构造器 public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } } ``` --- ### ⚠️ 注意事项 1. **生命周期问题**:静态变量会持续到程序结束,可能导致内存泄漏 2. **线程安全**:多线程环境下需通过`synchronized`或`Atomic`类型保证原子性 3. **设计原则**:过度使用静态变量会破坏面向对象特性,建议仅在必要时使用 实际开发中,静态变量常用于全局计数器、配置参数、工具类常量等场景,需根据具体需求权衡使用。
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

N3verL4nd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值