一个 static 还能难得住我?(二)

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: static 是我们日常生活中经常用到的关键字,也是 Java 中非常重要的一个关键字,static 可以修饰变量、方法、做静态代码块、静态导包等,下面我们就来具体聊一聊这个关键字,我们先从基础开始,从基本用法入手,然后分析其原理、优化等。

static 进阶知识


我们在了解了 static 关键字的用法之后,来看一下 static 深入的用法,也就是由浅入深,慢慢来,前戏要够~    

微信图片_20220414195807.png

关于 static 的所属类

static 所修饰的属性和方法都属于类的,不会属于任何对象;它们的调用方式都是 类名.属性名/方法名,而实例变量和局部变量都是属于具体的对象实例。

static 修饰变量的存储位置

首先,先来认识一下 JVM 的不同存储区域。

微信图片_20220414195812.png

  • 虚拟机栈 : Java 虚拟机栈是线程私有的数据区,Java 虚拟机栈的生命周期与线程相同,虚拟机栈也是局部变量的存储位置。方法在执行过程中,会在虚拟机栈中创建一个 栈帧(stack frame)
  • 本地方法栈: 本地方法栈也是线程私有的数据区,本地方法栈存储的区域主要是 Java 中使用 native 关键字修饰的方法所存储的区域
  • 程序计数器:程序计数器也是线程私有的数据区,这部分区域用于存储线程的指令地址,用于判断线程的分支、循环、跳转、异常、线程切换和恢复等功能,这些都通过程序计数器来完成。
  • 方法区:方法区是各个线程共享的内存区域,它用于存储虚拟机加载的 类信息、常量、静态变量、即时编译器编译后的代码等数据,也就是说,static 修饰的变量存储在方法区中
  • :堆是线程共享的数据区,堆是 JVM 中最大的一块存储区域,所有的对象实例,包括实例变量都在堆上进行相应的分配。

static 变量的生命周期

static 变量的生命周期与类的生命周期相同,随类的加载而创建,随类的销毁而销毁;普通成员变量和其所属的生命周期相同。

static 序列化

我们知道,序列化的目的就是为了 把 Java 对象转换为字节序列。对象转换为有序字节流,以便其能够在网络上传输或者保存在本地文件中。

声明为 static 和 transient 类型的变量不能被序列化,因为 static 修饰的变量保存在方法区中,只有堆内存才会被序列化。而 transient 关键字的作用就是防止对象进行序列化操作。

类加载顺序

我们前面提到了类加载顺序这么一个概念,static 修饰的变量和静态代码块在使用前已经被初始化好了,类的初始化顺序依次是

加载父类的静态字段 -> 父类的静态代码块 -> 子类静态字段 -> 子类静态代码块 -> 父类成员变量(非静态字段)

-> 父类非静态代码块 -> 父类构造器 -> 子类成员变量 -> 子类非静态代码块 -> 子类构造器

static 经常用作日志打印

我们在开发过程中,经常会使用 static 关键字作为日志打印,下面这行代码你应该经常看到

private static final Logger LOGGER = LogFactory.getLoggger(StaticTest.class);

然而把 static 和 final 去掉都可以打印日志

private final Logger LOGGER = LogFactory.getLoggger(StaticTest.class);
private Logger LOGGER = LogFactory.getLoggger(StaticTest.class);

但是这种打印日志的方式存在问题

对于每个 StaticTest 的实例化对象都会拥有一个 LOGGER,如果创建了1000个 StaticTest 对象,则会多出1000个Logger 对象,造成资源的浪费,因此通常会将 Logger 对象声明为 static 变量,这样一来,能够减少对内存资源的占用。

static 经常用作单例模式

由于单例模式指的就是对于不同的类来说,它的副本只有一个,因此 static 可以和单例模式完全匹配。

下面是一个经典的双重校验锁实现单例模式的场景

public class Singleton {
    private static volatile Singleton singleton;
    private Singleton() {}
    public static Singleton getInstance() {
        if (singleton == null) {
            synchronized (Singleton.class) {
                if (singleton == null) {
                    singleton = new Singleton();
                }
            }
        }
        return singleton;
    }
}

来对上面代码做一个简单的描述

使用 static 保证 singleton 变量是静态的,使用 volatile 保证 singleton 变量的可见性,使用私有构造器确保 Singleton 不能被 new 实例化。

使用 Singleton.getInstance() 获取 singleton 对象,首先会进行判断,如果 singleton 为空,会锁住 Singletion 类对象,这里有一些小伙伴们可能不知道为什么需要两次判断,这里来解释下

如果线程 t1 执行到 singleton == null 后,判断对象为 null,此时线程把执行权交给了 t2,t2 判断对象为 null,锁住 Singleton 类对象,进行下面的判断和实例化过程。如果不进行第二次判断的话,那么 t1 在进行第一次判空后,也会进行实例化过程,此时仍然会创建多个对象。


类的构造器是否是 static 的


这个问题我相信大部分小伙伴都没有考虑过,在 Java 编程思想中有这么一句话 类的构造器虽然没有用 static 修饰,但是实际上是 static 方法,但是并没有给出实际的解释,但是这个问题可以从下面几个方面来回答

  • static 最简单、最方便记忆的规则就是没有 this 引用。而在类的构造器中,是有隐含的 this 绑定的,因为构造方法是和类绑定的,从这个角度来看,构造器不是静态的。
  • 从类的方法这个角度来看,因为 类.方法名不需要新创建对象就能够访问,所以从这个角度来看,构造器也不是静态的
  • 从 JVM 指令角度去看,我们来看一个例子
public class StaticTest {
    public StaticTest(){}
    public static void test(){
    }
    public static void main(String[] args) {
        StaticTest.test();
        StaticTest staticTest = new StaticTest();
    }
}

我们使用 javap -c 生成 StaticTest 的字节码看一下

public class test.StaticTest {
  public test.StaticTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return
  public static void test();
    Code:
       0: return
  public static void main(java.lang.String[]);
    Code:
       0: invokestatic  #2                  // Method test:()V
       3: new           #3                  // class test/StaticTest
       6: dup
       7: invokespecial #4                  // Method "<init>":()V
      10: astore_1
      11: return
}

我们发现,在调用 static 方法时是使用的 invokestatic 指令,new 对象调用的是 invokespecial 指令,而且在 JVM 规范中 https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.invokestatic 说到

微信图片_20220414195822.png

微信图片_20220414195825.png

从这个角度来讲,invokestatic 指令是专门用来执行 static 方法的指令;invokespecial 是专门用来执行实例方法的指令;从这个角度来讲,构造器也不是静态的。

相关文章
|
6月前
|
编译器 程序员 C语言
【C深剖】详解static
【C深剖】详解static
|
4月前
|
Oracle Java 关系型数据库
农行1面:说说 final,finally,finalize的区别
在 Java中,“final”、“finally”和“finalize”是三个不同的关键字或方法,尽管它们的名字相似,但在功能和用途上却有显著的区别,这篇文章我们继续分析一篇农行1面的题目:说说 final,finally,finalize的区别。
27 3
|
9月前
|
存储 缓存 Java
从static说起
从static说起
50 0
|
存储 编译器 C语言
面试官:说一下你对const static volitale的理解 =_=
面试官:说一下你对const static volitale的理解 =_=
是否可以从一个static方法内部发出对非static方法的调用?
这里其实问的是静态方法和非静态方法的区别: 静态方法和非静态方法的区别可以总结如下:
805 0
|
存储 C语言
static的作用
static的作用
170 0
|
编译器
public final static搭配的好处
final 和 static 往往搭配使用,效率更高,不会导致类加载.底层编译器做了优化处理
193 0
public final static搭配的好处
|
程序员
【高并发】又一个朋友面试栽在了Thread类的stop()方法和interrupt()方法上!
新一轮的面试已经开始了,可能是疫情的原因吧,很多童鞋纷纷留言说今年的面试题难度又提高了,尤其是对并发编程的知识。我细想了下,也许有那么点疫情的原因吧,但无论面试的套路怎么变,只要掌握了核心知识和底层原理,吊打面试官应该不难吧。玩笑归玩笑,学习知识并不只是为了应付面试,更应该将这些知识运用到实际的工作中。
232 2
【高并发】又一个朋友面试栽在了Thread类的stop()方法和interrupt()方法上!
一个 static 还能难得住我?(一)
static 是我们日常生活中经常用到的关键字,也是 Java 中非常重要的一个关键字,static 可以修饰变量、方法、做静态代码块、静态导包等,下面我们就来具体聊一聊这个关键字,我们先从基础开始,从基本用法入手,然后分析其原理、优化等。
一个 static 还能难得住我?(一)