hi 大家好,我是 DHL。大厂程序员,上任于美团、快手、小米。大众号:ByteCode,共享技术干货和编程知识点

最近一个小伙伴反馈了他在面试过中遇到的问题。

  • 为什么成员变量不需要手动初始化,而局部变量需要手动初始化?
  • 成员变量、静态变量、局部变量存放在哪里?

这个问题考察了咱们很多知识点,触及了 Jvm 内存结构、类加载机制、 Java 目标模型等等知识点。

Jvm 内存结构和 Java 虚拟机的运行时区域有关 Java 目标模型和 Java 目标在虚拟机中的表现方式有关

如果对这些知识点,不是很了解的话,是很难悉数回答上来,今日这篇文章咱们环绕这些问题,一起来剖析一下。

在 Java 中咱们能够将变量分为两大类成员变量和局部变量,而成员变量又分为两大类静态成员变量和非静态成员变量。

在 Java 语言中,没有赋值的变量是不能运用的,无论是成员变量还是局部变量,在 “运用之前” 都需要初始化,而咱们经常在代码中看到成员变量没有手动初始化,也能够正常运用,这是由于成员变量在类的加载进程现已被初始化了。

类的加载进程被分为 5 个阶段:”加载” -> “验证” -> “预备” -> “解析” -> “初始化”,而不同的变量,在不同的阶段会被赋予不同的值。

字节:成员变量,局部变量,存放在哪里,为什么局部变量需要初始化

静态成员变量

静态成员变量在类加载进程中会被赋值 2 次。

public static int code = 3;
  • 第一次赋值是在 “预备” 阶段,赋上 Jvm 给的默认值,这个阶段 code = 0
  • 第2次赋值是在 “初始化” 阶段,赋上在代码中声明的默认值,这个阶段 code = 3

静态成员变量存放在办法区。办法区在 JVM 内存结构中是一个非常重要的区域,存储了每个类的信息( 包含类的称号、办法信息、字段信息)、静态变量、常量等等。

办法区中有一个常量池,用来存储编译期间生成的字面量和符号引证。

  • 字面量:指由字母、数字等构成的字符串或许数字常量,包含文本字符串,final 的常量值等
  • 符号引证:类和接口的全限定名,字段的称号和描述符,办法的称号和描述符

当程序运行起来,被加载到内存后,才会为这些常量分配内存地址,这时原来的常量池改变成了运行时常量池,在运行期间也能够将新的常量放入运行时常量池,比方 Stringintern 办法。

非静态成员变量

非静态成员变量在类加载进程中会被赋值 1 次。

public int age = 18;

在 “初始化” 阶段赋上在代码中声明的默认值,这个阶段 age = 18,目标实例化之后,该目标存放在 Java 堆中,没有赋值的话,默认就为 0。

局部变量

Java 办法以栈帧的方式,运行在虚拟机栈(Java 栈)中,栈是线程私有的,程序发动的时分,会创立一个 main 线程,操作体系会为每一个线程分配一段内存,线程创立的时分会创立一个虚拟机栈,虚拟机栈的生命周期和线程一样,线程完毕了,虚拟机栈也销毁了。

每个 Java 办法,对应一个个栈帧,所以办法开端和完毕,都是一个个栈帧入栈和出栈的进程,效果如下图所示。更多内容能够前往检查我另外一篇文章 内存如何记载办法调用和回来进程

字节:成员变量,局部变量,存放在哪里,为什么局部变量需要初始化

每个 Java 办法,都是一个个栈帧,每个栈帧包含了:局部变量表、操作数栈、办法回来地址、动态链接、附加信息。

字节:成员变量,局部变量,存放在哪里,为什么局部变量需要初始化

办法中的局部变量会存放在栈帧的局部变量表中,局部变量表首要用于存放办法参数和办法内部定义的局部变量。例如下面的代码。

public class Main5 {
    public static void main(String... args) {
        Main5 main = new Main5();
    }
}

办法 main 的对应的局部变量表,如下图所示。

字节:成员变量,局部变量,存放在哪里,为什么局部变量需要初始化

正如你所见,局部变量表存放了办法参数和办法中的局部变量。

局部变量和成员变量不一样,Jvm 不会给局部变量一个默认值,所以咱们在运用局部变量的时分需要手动初始化,否则是无法运用的。

为什么局部变量必须手动初始化呢?

原因之一为了防止引证到一个错误的值。

为了达到节约资源的目的,局部变量表的空间是能够被复用的,如果一个变量超过了它的效果域,在效果域之后声明的变量,能够复用之前的变量的方位,如果咱们不手动初始化,很或许访问到之前变量的内容。

public void byteCode(){
    boolean devEnv = true;
    if(devEnv){
        int a = 1;
        int b = 2;
    }
    // 为了节约资源 c 或许复用 a 或许 b 的方位,经过变量 c 或许会访问到 a 或许 b 的内容
    int c;
}

为了达到节约资源的目的 c 或许复用 a 或许 b 的方位,经过变量 c 或许会访问到 a 或许 b 的内容,​并不是一个正确的值,如果咱们自动初始化能够避免这类问题的呈现。

为什么 Jvm 不给局部变量一个默认值呢?

这里借用《Thinking in Java》作者 Bruce Eckel 的一句话来回答这个问题。

编译器能够为局部变量赋上一个默认值,可是未初始化的局部变量更有或许是程序员的疏忽,所以采用默认值范围会掩盖这种失误。因此强制程序员提供一个初始值,往往能够协助找出程序里的缺陷。


全文到这里就完毕了,感谢你的阅读,坚持原创不易,欢迎在看、点赞、共享给身边的小伙伴,我会继续共享原创干货!!!


我开了一个云同步编译东西(SyncKit),首要用于本地写代码,同步到长途设备,在长途设备上进行编译,最终将编译的结果同步到本地,代码现已上传到 Github,欢迎前往库房 hi-dhl/SyncKit 检查。

  • 库房 SyncKit:https://github.com/hi-dhl/SyncKit
  • 下载地址:https://github.com/hi-dhl/SyncKit/releases

Hi 大家好,我是 DHL,上任于美团、快手、小米。大众号:ByteCode ,共享有用、有趣的硬核原创内容,Kotlin、Jetpack、功能优化、体系源码、算法及数据结构、动画、大厂面经,真诚推荐你重视我。

  • 大众号:ByteCode
  • 哔哩哔哩: space.bilibili.com/498153238
  • : juejin.im/user/259450…
  • 博客: hi-dhl.com
  • Github: github.com/hi-dhl

最新文章

  • Android 14 新增权限
  • 90% 的 Java 程序员都会犯的错
  • 适配 Android 14,功能和权限的变更,你的应用受影响了吗
  • 国外大厂面试题, 7 个 Android Lifecycle 重要的知识点
  • Android 13这些权限废弃,你的应用受影响了吗?
  • Android 12 已来,你的 App 溃散了吗?
  • Android 利器,我开发了云同步编译东西
  • Twitter 上有趣的代码
  • 谁动了我的内存,揭秘 OOM 溃散下降 90% 的隐秘
  • 反射技巧让你的功能提升 N 倍
  • 90%人不明白的泛型局限性,泛型擦除,星投影
  • 揭秘反射真的很耗时吗,射 10 万次耗时多久
  • 影响功能的 Kotlin 代码(一)
  • 揭秘 Kotlin 中的 == 和 ===

开源新项目

  • 云同步编译东西(SyncKit),本地写代码,长途编译,欢迎前去检查 SyncKit

  • KtKit 小巧而实用,用 Kotlin 语言编写的东西库,欢迎前去检查 KtKit

  • 最全、最新的 AndroidX Jetpack 相关组件的实战项目以及相关组件原理剖析文章,正在逐渐增加 Jetpack 新成员,库房继续更新,欢迎前去检查 AndroidX-Jetpack-Practice

  • LeetCode / 剑指 offer,包含多种解题思路、时间复杂度、空间复杂度剖析,在线阅读