您的位置 首页 java

从类的生命周期深入理解Java类静态/非静态成员/函数

Java中静态/非静态变量、方法是开发过程中的基础概念。这篇文章将从类的生命周期,JVM内存位置以及相互使用关系等几个方面详细介绍这些概念的定义及它们之间的区别联系。

首先简单提一下类的生命周期

一. 类的生命周期

从类被加载进内存开始直到卸载出内存为止,类的生命周期包括 装载 验证 准备 解析 初始化 使用 卸载 7个过程。到 初始化 完成为止,类的.class字节码的内容以及被加载到JVM的方法区了,同时也完成了类静态变量的初始化。

二. 静态成员

静态变量

被static修饰的变量称为静态变量或者称为类变量。类变量属于整个类,可以被类名直接调用,也可以被类所创建的所有实例化对象所共享调用。

  • 类加载过程中,于 准备阶段 在JVM方法区中被分配内存并赋上“零值”。
  • 类加载过程中,于 初始化阶段 按照类中定义的Java程序代码的值进行初始化。
  • 所有对象共享的类变量位于 方法区 的同一块内存处,对象对类变量的修改影响其他对象的使用。
  • 静态变量的生命周期基本等于类的生命周期,随着类的加载而创建,卸载而消失。

静态方法

用static修饰的方法称静态方法又称类方法。类方法是属于整个类的,可以被类名直接调用,被实例对象调用和被实例方法调用。

  • 类加载过程中,于 加载阶段 ,类方法的字节码被加载到JVM的方法区。
  • 类方法被加载到内存后会分配相应的入口地址。
  • 类方法的生命周期也基本等于类的生命周期。

三. 非静态成员

非静态变量

没有被static修饰的变量称非静态变量又称为成员变量,属于类实例化的对象,只能被类所创建的对象调用。

  • 在类生命周期的 使用阶段 ,在类实例化对象的过程中,成员变量于JVM堆空间中被分配内存。
  • 不同成员变量被分配到不同的堆空间中。一个对象成员变量的改变不会影响其他对象的成员变量。
  • 成员变量的生命周期等于对象的生命周期,随着对象的创建而创建,随着对象的回收而释放。

非静态方法

没有用static修饰的方法称非静态方法又称实例方法,属于类实例化的对象,只能被类所创建的对象调用。

  • 类加载过程中,于 加载阶段 ,类方法的字节码被加载到JVM的方法区。
  • 在类生命周期的 使用阶段 ,类被 第一次实例化后 ,实例方法才会被分配相应的入口地址。
  • 所有对象实例都共享一个实例的入口地址,但是传入的对象实例位于不同的堆内存空间。
  • 在类的对象都被回收后,实例方法入口地址被取消。

四. 相互调用

静态成员,非静态成员互相调用通过排列组合来看大致有如下16种,这里将结合上面内容和IDEA一一解析对应调用关系

1. 类变量调用类变量

OK,不过有顺序要求,因为在 初始化阶段 类变量是一个个进行初始化。不然IDEA会提示Illegal forward reference的错误。更具体地,在类的初始化阶段 执行类构造器<clinit>()方法 的过程中,<clinit>()方法是由编译器自动收集类中的所有 类变量的赋值动作 和**静态语句块(static{}块)**中的语句合并产生的。编译器收集的顺序是由语句在源文件中出现的顺序所决定的。所以,先定义的类静态变量无法访问后面定义的静态变量。

     public static int staticInt0 = 0;
    public static int staticInt = staticInt0;
复制代码  

2. 类变量调用类方法

OK,类方法是类加载过程中的 加载阶段 被加载到方法区,类变量初始化是在类方法加载后面的 初始化阶段

     public static int staticInt = staticMethod();

    private static int staticMethod() {
        System.out.println("StaticMethod is called~");
        return 1;
    }
复制代码  

3. 类变量调用实例变量

Error,因为实例变量是在类生命周期的 使用阶段 通过对象实例化的时候分配内存的,要晚于类变量在在 初始化阶段 初始化的时候,所以无法调用。

4. 类变量调用实例方法

Error,因为 初始化阶段 类变量初始化的时候,虽然实例方法已经被加载到JVM的方法区中,但是实例方法还没分配入口地址,所以无法访问。

5. 类方法调用类方法

OK, 即使是顺序不一致也没问题。在类加载和出现运行时都没问题。

     public static int staticMethod() {
        System.out.println("StaticMethod is called~");
        return staticMethod2();
    }

    public static int staticMethod2() {
        System.out.println("StaticMethod2 is called~");
        return 2;
    }
    public class ClassMemberTest {
    public static void main(String[] args) {
        System.out.println("ClassMemberDemo.staticInt:" + staticMethod());
    }
}
复制代码  

运行时输出

 StaticMethod is called~
StaticMethod2 is called~
ClassMemberDemo.staticInt:2
复制代码  

这里皮一下,如果staticMethod()和staticMethod2()相互调用会怎么样呢?这里留给读者测试下。

6. 类方法调用类变量

OK,运行时通过类名调用类方法,如果类还没被加载,首先加载该类,并完成类变量的初始化,然后调用类方法,类方法体内访问类变量。

     public static int staticInt = 1;

    public static int staticMethod() {
        System.out.println("StaticMethod is called~");
        return staticInt;
    }
    
    public class ClassMemberTest {
    public static void main(String[] args) {
        System.out.println("ClassMemberDemo.staticInt:" + staticMethod());
    }
}
复制代码  

运行时输出

 StaticMethod is called~
ClassMemberDemo.staticInt:1
复制代码  

7. 类方法调用实例方法

Error,无法通过编译。这里原因是类方法被调用的地方可能实例方法还没被分配入口,如类变量初始化调用的时候,该类第一次使用是使用类方法的时候。

8. 类方法调用成员变量

Error,这里指的是类方法体内使用成员变量,原因类似3。

9. 成员变量调用成员变量

OK,不过有顺序要求。因为对象实例化的时候,实例变量也是有顺序的。

     private int nullStaticInt ;

    private int nullStaticInt2 = nullStaticInt;
复制代码  

10. 成员变量调用实例方法

OK,成员变量初始化的时候是在对象实例化的时候进行的,这时候实例方法已经被分配入口地址了。

     public int nullStaticInt = nullStaticMethod();

    private int nullStaticMethod() {
        return 2;
    }
复制代码  

11. 成员变量调用类变量

OK,因为实例变量是在类生命周期的 使用阶段 通过对象实例化的时候分配内存的,这时候类变量早已在 初始化阶段 初始化了。

12. 成员变量调用类方法

OK,原因类似上面,类方法的字节码早已在 加载阶段 被加载到JVM的方法区。

13. 实例方法调用实例方法

OK,即使顺序不一致,代码编译和字节码加载到内存也没问题。只不过互相调用的时候,就会出现问题。具体的问题同5.

14. 实例方法调用成员变量

OK,这是正常常用场景。

15. 实例方法调用类变量

OK,这里由于实例方法是通过对象在运行时调用的,所以OK。虽然从加载到内存的时间点上,类变量比实例方法要晚。

16. 实例方法调用类方法

OK,这里原因类似10.

这里从类的生命周期,JVM内存位置以及相互使用关系梳理Java类静态/非静态成员/函数的梳理介绍完毕, 觉得有用的点个赞呗~ ♪(・ω・)ノ

文章来源:智云一二三科技

文章标题:从类的生命周期深入理解Java类静态/非静态成员/函数

文章地址:https://www.zhihuclub.com/182845.shtml

关于作者: 智云科技

热门文章

网站地图