目录

java静态方法什么时候被装载的 java静态方法存储在哪个区_JVM

预备知识

Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存划分为若干个不同的数据区域。

java静态方法什么时候被装载的 java静态方法存储在哪个区_static_02

下面简单介绍一下,更多内容可至 JVM学习(三)JVM内存模型 && Java 的堆,栈,方法区你都搞清楚了吗

  • 方法区 / 静态存储区:内存在程序编译的时候就已经分配好,这块内存在程序整个运行期间都存在。它主要存放静态数据、全局 static 数据和常量。
  • Java 虚拟机栈:虚拟机栈描述的是 Java方法 执行的内存模型,每个方法执行时都会创建一个栈帧用来存储局部变量表(存放编译器可知的各种基本数据类型、对象引用和 returnAddress 类型,所需的内存空间在编译器完成分配)、操作数栈、动态链接、方法出口等信息。Java 虚拟机栈 有两种异常情况:OutOfMemoryError(扩展时无法申请到足够内存)和 StackOverflowError(线程请求的栈深度大于虚拟机所允许的深度)。
  • 本地方法栈:同 Java 虚拟机栈 类似,只不过 Java 虚拟机栈 为虚拟机执行 Java 方法 服务,本地方法栈为虚拟机使用 Native 方法 服务。HotSpot 直接将两个栈合二为一。也规定了两种异常:OutOfMemoryErrorStackOverflowError
  • Java 堆(Java Heap)Java 虚拟机 所管理的内存中最大的一块。Java 堆 是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。

需要 特别注意 的是:

java 中的 static C/C++ 中的 static 关键字的不同。Java 中的 static 关键字不会影响到变量或者方法的作用域。

C/C++static 是可以作用域局部变量的,但是在 Java 中:static 是不允许用来修饰局部变量。

具体原因可参考:Java static 关键字为什么不能应用于局部变量?

是什么

概念

Java 中并 不存在 全局变量的概念,但是我们可以通过 static 来实现一个 “伪全局” 的概念,在 Java static 表示 “静态” ,用来修饰成员变量和成员方法,当然也可以修饰代码块。

static 修饰的成员变量和成员方法是独立于该类的,它不依赖于某个特定的实例变量,也就是说它被该类的所有实例共享。所有实例的引用都指向同一个地方,任何一个实例对其的修改都会导致其他实例的变化。

格式

  • 静态变量:static 变量类型 变量名

static int a;

  • 静态方法:【访问权限修饰符】 static 方法返回值 方法名 (参数列表)

public static void static_funname(){ //... ; }

  • 静态代码块:static {  // ...  }

static { System.out.println("Static code block:I only run once"); //... }

 

特点

  • static 修饰的变量或者方法是独立于该类的任何对象,也就是说,这些变量和方法不属于任何一个实例对象,而是被类的实例对象所共享。
  • 在类被加载的时候,就会去加载被 static 修饰的部分。
  • 基于以上一点:被 static 修饰的变量或者方法是优先于对象存在的,也就是说当一个类加载完毕之后,即便没有实例化对象,也可以去访问它的静态变量/方法。
  • 基于以上两点:静态的方法,不能直接使用非静态的变量或方法。

为什么

为什么我们需要 static ?

Java 中当我们通过 new 关键字去创建对象的时候,那么数据的存储空间才会被分配,类中的成员方法才能被对象所使用。但是呢这无法解决以下两种特殊的需求:

1、我们希望通过 new 关键字创建的对象共享同一个资源,而不是说每个对象都拥有自己的数据;

      或者说我们根本就不没有必要去创建对象,该资源和对象之间是没有关系的。

2、希望某个方法不与包含它的类的任何对象联系在一起。

      总结下来就是说:即使没有创建对象,我们也需要能使用属性和调用方法,static 目的就是在于解决这个问题。

咋整

基础用法

静态变量

Demo

public class test {
    private static int i = 20;
    private int j = 20;
    public static void main(String[] args) {

        test static_demo = new test();
        test static_demo1 = new test();

        //不同实例的同一静态变量
        System.out.println(static_demo.i);
        System.out.println(static_demo1.i);

        //改变静态变量的值
        static_demo.i = 21;
        static_demo1.i = 22;
        System.out.println(static_demo.i);
        System.out.println(static_demo1.i);

        //普通变量
        static_demo.j =  21;
        static_demo1.j = 22;
        System.out.println(static_demo.j);
        System.out.println(static_demo1.j);

    }

}

结果

java静态方法什么时候被装载的 java静态方法存储在哪个区_Java_03

static 修饰的变量存储至方法区,为所有实例所共享;与之相对的是没有被 static 修饰的成员变量,称为实例变量,说明这个变量是属于某个具体的对象,每 new 一次,就多创建了一份拷贝。

静态方法

说到静态函数,就不得不提 Java 另一个关键词 this,指的是当前的引用。意思是调用这个函数的对象,这意味着和 static 修饰的函数水火不容。被 static 修饰的函数,不能出现 this 关键字,否则便会报错。

此外,静态资源是在类初始化的时候加载的,而非静态资源是在实例化的时候加载的。

import java.time.chrono.ThaiBuddhistChronology;

public class test {
    private static int i = 20;
    private int j = 20;

    public static void main(String[] args) {
        
        //...

    }
    private static void hello(){

        i = 21;

        //Cannot make a static reference to the non-static field
        //j = 21;

        //Cannot use this in a static context
        //this.j = 1;

        System.out.println("Hello 11-1.");
    }

}

总结如下:

  • 静态函数不能引用非静态资源;(已加载的方法无法调用未加载的资源)
  • 非静态函数可以引用静态资源;(非静态函数实例化时加载,这时静态资源肯定已加载)
  • 静态函数可以相互调用(只要引入所在包/类即可);(在已导入的情况下,均在初始化时加载)
  • 静态函数没有 this,因为它不依附于任何对象。(上面说了)

ps

构造方法是不是静态方法?

主要疑点是《Thinking in Java》Even though it doesn't explicitly use the static keyword, the constructor is actually a static method. So the first time an object of type Dog is created, or the first time a static method or static field of class Dog is accessed, the Java interpreter must locate Dog.class, which it does by searching through the classpath. 

翻译过来就是

即使它没有明确使用静态关键字,构造函数实际上也是一种静态方法。

因此,将创建第一个类型为 Dog 的对象,或访问类 Dog 的第一个静态方法或静态字段,Java解释器 必须找到通过类路径搜索的Dog.class

下面我们通过Demo验证:

构造方法中使用 this,构造方法调用非静态资源。

public class test {
    private static int i;
    private int j;

    
    public test(int i,int j)
    {
        this.i = i;
        this.j = j;

        hownumber();
    
    }
    public static void main(String[] args) {

        test demo1 = new test(1,2);
        test demo2 = new test(4,5);


        System.out.println("The demo1.i is:"+demo1.i);
        System.out.println("The demo2.i is:"+demo2.i);
        System.out.println("The demo1.j is:"+demo1.j);
        System.out.println("The demo2.j is:"+demo2.j);


    }

    public void hownumber(){
        System.out.println("The i is:"+i);
        System.out.println("The j is:"+j);
    }

}

结果显示,构造方法可以使用 this,同时也可以调用非静态资源。

此外,如果我们将构造函数声明为 static,则会出现以下错误提示:

Illegal modifier for the constructor in type test; only public, protected & private are permitted

因此本人更加倾向于 构造方法非静态方法

进阶用法

静态代码块

静态代码块可以有多个,位置可以随便放,它不在任何的方法体内,JVM 加载类时会执行这些静态的代码块,如果 static 代码块有多个,JVM 将按照它们在类中出现的先后顺序依次执行它们,每个代码块只会被执行一次。

public class Person{
    private Date birthDate;

    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }

    //isBornBoomer 是用来这个人是否是 1990-1999 年出生的
    boolean isBornBoomer() {
        Date startDate = Date.valueOf("1990");
        Date endDate = Date.valueOf("1999");
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}

注意到:每次 isBornBoomer() 被调用的时候,都会生成 startDate birthDate 两个对象,这将造成空间的浪费

基于静态代码块只被执行一次的特性,可修改为如下代码以优化程序性能:

public class Person{
    private Date birthDate;
    private static Date startDate,endDate;
    static{
        startDate = Date.valueOf("1990");
        endDate = Date.valueOf("1999");
    }

    public Person(Date birthDate) {
        this.birthDate = birthDate;
    }

    boolean isBornBoomer() {
        return birthDate.compareTo(startDate)>=0 && birthDate.compareTo(endDate) < 0;
    }
}

静态内部类

static 修饰的内部类,它可以不依赖于外部类实例对象而被实例化,而通常的内部类需要在外部类实例化后才能实例化。

静态内部类不能与外部类有相同的名字,不能访问外部类的普通成员变量,只能访问内部类中的静态成员和静态方法(包括私有类型)。

只有内部类才能被 static 修饰,普通的类不可以。

public class test {
    private static int i = 1;
    private int j = 1;

    private static void hello(){
        System.out.println("Hello");;
    } 

    public static void main(String[] args) {
        test test1 = new test();
 
    }

    public static class test_mode {
        //访问静态变量
        private static int x = i;
        //Cannot make a static reference to the non-static field j
        //private int y = j;

        //访问静态方法
        private void sayhello(){
            hello();
        }

    }

}

静态导包

静态导入是 jdk5 引入的新特性,我们先看下面这个Demo,

public class test {
    public static void main(String[] args) {
        
        double a = Math.cos(Math.PI / 2);

        double b = Math.sin(Math.PI);

        double c = Math.tan(Math.PI);

    }

}

可以发现,Math出现的频率特别高,每次调用 Math 库包的函数或常量都需要先输入 Math,这是及其繁琐的。

懒惰是人类进步的原动力。   ---俺也不知道谁说的

为此我们可以使用 静态导入

import static java.lang.Math.*;

public class test {
    public static void main(String[] args) {
        
        double a = cos(PI / 2);

        double b = sin(PI);

        double c = tan(PI);

    }

}

静态导入虽然方便,但是如果滥用,由于我们不知道某个方法的具体来自那个类中,则更容易造成程序的误解。

import static java.lang.Double.*;
import static java.lang.Integer.*;
import static java.lang.Math.*;
import static java.text.NumberFormat.*;

import java.text.NumberFormat;

public class test {
    public static void main(String[] args) {

        //计算圆的面积
        double r = parseDouble(args[0]);
        double s = PI *r*r;
        NumberFormat nf = getInstance();
        nf.setMaximumFractionDigits(parseInt(args[1]));
        
        System.out.println(" 面积:"+s);
    }

}

正确的使用姿势应该是:

不将整个 jar 包都导入,而只是想使用某部分函数。提高代码的阅读性,更好的理解程序。

import java.text.NumberFormat;

import static java.lang.Double.parseDouble;
import static java.lang.Integer.parseInt;
import static java.lang.Math.PI;
import static java.text.NumberFormat.getInstance;

public class test {
    // 输入半径和精度要求,计算面积
    public static void main(String[] args) {
        double s = PI * parseDouble(args[0]);
        NumberFormat nf = getInstance();
        nf.setMaximumFractionDigits(parseInt(args[1]));
        formatMessage(nf.format(s));
    }
    // 格式化消息输出
    public static void formatMessage(String s){
        System.out.println(" 面积:"+s);
    }
}

单例模式

单例模式的特点是一个类只能有一个实例,为了实现这一功能,必须隐藏该类的构造函数,即把构造函数声明为 private,并提供一个创建对象的方法。

public class test {

    private int a;
    private int b;

    private static test experiment;

    public static test getexperiment() {
        if (experiment == null) {
            experiment = new test();
        }
        return experiment;
    }

    private test() {
        a = 1;
        b = 1;
    }

    public static void main(String[] args) {
        getexperiment();
    }
}

Static 的局限

  • 只能调用 static 变量。
  • 只能调用 static 方法。
  • 不能以任何形式引用 this、super。
  • static 变量在定义时必须要进行初始化,且初始化时间要早于非静态变量。