对于静态变量、静态方法、内存泄漏的思考

我的问题

        之前写 Dialog 除了自定义炫酷弹窗时独立写个类,其他情况基本都是用默认样式的 Dialog,因为直接调用系统 API 比较方便,也可以避免内存泄漏问题。但是在重构优化公司项目时,发现之前有个仁兄搞了个 Dialog 的单例类,我就瞬间无语了,说好的防止内存泄漏呢?!然后就想另起个工具类来替代【总觉得怪怪的 ~~】。因为项目的普通弹窗很多,且这些弹窗对于需要的按钮数量不同,点击按钮后也不需要后续操作,所以之前的哥们就懒,抽了这么一个单例。到我这了,改一下吧,样式是特定的,那就改成工具类写静态方法吧。那么问题来了,那时突然有点蒙了,静态变量可能会引起内存泄漏,静态方法会不会引起内存泄漏呢?!其实这是不会的。
        每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 1

        方法体的局部变量(其中包括基础数据类型、对象的引用)会在栈区创建空间,并在方法执行结束后自动释放变量的空间和内存。也就是说,不管是静态方法还是非静态方法的局部变量都会在使用后等待被回收。而在这里讨论的可能会出现的内存泄漏问题都是静态变量引起的:我们知道,静态方法只能使用静态的全局变量,不能使用非静态的全局变量,如果在静态方法中使用了静态的全局变量,而这个静态变量又持有经静态方法传入的参数对象的引用,就有可能引起内存泄漏。说到底都是静态变量的锅!

静态方法和非静态方法 2

1、在加载时机和占用内存上,静态方法和实例方法是一样的,在类型第一次被使用时加载,调用的速度基本上没有差别。

2、方法占不占用更多内存,和它是不是 static 没什么关系。因为字段是用来存储每个实例对象的信息的,所以字段会占有内存,并且因为每个实例对象的状态都不一致(至少不能认为它们是一致的),所以 每个实例对象的所有字段都会在内存中有一分拷贝,也因为这样才能用它们来区分现在操作的是哪个对象。但方法不一样,不论有多少个实例对象,它的方法的代码都是一样的,所以只要有一份代码就够了。因此无论是 static 还是 non-static 的方法,都只存在一份代码,也就是只占用一份内存空间。 同样的代码,为什么运行起来表现却不一样?这就依赖于方法所用的数据了。主要有两种数据来源,一种就是通过方法的参数传进来,另一种就是使用 class 的成员变量的值。

3、大家都以为,实例方法需要先创建实例才可以调用,比较麻烦,静态方法不用,比较简单,事实上 如果一个方法与他所在类的实例对象无关,那么它就应该是静态的,而不应该把它写成实例方法。所以,所有的实例方法都与实例有关,既然与实例有关,那么创建实例就是必然的步骤,没有麻烦简单一说。当然你完全可以把所有的实例方法都写成静态的,将实例作为参数传入即可,一般情况下可能不会出什么问题。

4、从面向对象的角度上来说,在抉择使用实例化方法或静态方法时,应该根据该方法和实例化对象是否具有逻辑上的相关性,如果是就应该使用实例化对象,反之使用静态方法。这只是从面向对象角度上来说的。如果从线程安全、性能、兼容性上来看,也是选用实例化方法为宜。

5、我们为什么要把方法区分为:静态方法和实例化方法?如果我们继续深入研究的话,就要脱离技术谈理论了。早期的结构化编程,几乎所有的方法都是 “静态方法”,引入实例化方法概念是面向对象概念出现以后的事情了,区分静态方法和实例化方法不能单单从性能上去理解,创建 c++、java、c# 这样面向对象语言的大师引入实例化方法一定不是要解决什么性能、内存的问题,而是为了让开发更加模式化、面向对象化。这样说的话,静态方法和实例化方式的区分是为了解决模式的问题。

大家对这个问题都有一个共识:那就是实例化方法更多被使用比较稳妥,静态方法少使用。

static 关键字 3

        通常来说,当创建类时,就是在描述那个类的对象的外观与行为。除非用 new 创建那个类的对象,否则,实际上并未获得任何对象。执行 new 来创建对象时,数据存储空间才被分配,其方法才供外界调用。
        有两种情形用上述方法是无法解决的。一种情形是,只想为某特定域分配单一存储空间,而不去考虑究竟要创建多少对象,甚至根本就不创建任何对象。另一种情形是,希望某个方法不与包含它的类的任何对象关联在一起。也就是说,即使没有创建对象,也能够调用这个方法
        通过 static 关键字可以满足这两方面的需要。当声明一个事物是 static 时,就意味着这个域或方法不会与包含它的那个类的任何对象实例关联在一起。所以,即使从未创建某个类的任何对象,也可以调用其 static 方法或访问其 static 域。通常,你必须创建一个对象,并用它来访问数据或方法。因此非 static 域和方法必须知道它们一起运作的特定对象。

当然,由于在用 static 方法前不需要创建任何对象,所以对于 static 方法,不能简单的通过调用其他非 static 域或方法而没有指定某个命名对象,来直接访问非 static 域或方法(因为非 static 域或方法必须与某一特定对象关联)

        有些面向对象语言采用类数据 class data 和类方法 class methods 两个术语,代表那些数据和方法只是作为整个类,而不是类的某个特定对象而存在的。有时,一些 Java 文献里也有用到这两个术语 。
        只须将 static 关键字放在定义之前,就可以将字段或方法设定为 static。例如,下面的代买就生成了一个 static 字段,并对其进行了初始化:

class StaticTest {
	static int i = 47;
}

        现在,即使你创建了两个 StaticTest 对象,StaticTest.i 也只有一份存储空间,这两个对象共享同一个 i。再看看下面代码:

StaticTest st1 = new StaticTest();
StaticTest st2 = new StaticTest();

        在这里,st1.i 和 st2.i 指向同一个存储空间,因此它们具有相同的值 47.
        引用 static 变量有两种方法。如前例所示,可以通过一个对象去定位它,比如 st2.i;也可以通过其类名直接引用,而这对于非静态成员则不行。

StaticTest.i++;

        其中,++ 运算符对变量进行递加操作。此时,st1.i 和 st2.i 仍具有相同的值 48.
        使用类名是引用 static 变量的首选方式,这不仅是因为它强调了变量的 static 结构,而且在某些情况下它还为编译器进行优化提供了更好的机会。
        类似逻辑也应用于静态方法。既可以像其他方法一样,通过一个对象来引用某个静态方法,也可以通过特殊的语法形式 ClassName.method() 加以引用。定义静态方法的方式也与定义静态变量的方式相似:

class Incrementable {
	static void increment() {
		StaticTest.i++;
	}
}

        可以看到,Incrementable 的 increment() 方法通过 ++ 运算符将静态数据 i 递加。可以采取典型的方式,通过对象来调用 increment():

Incrementable sf = new Incrementable();
sf.increment();

        或者因为 increment() 是一个静态方法,所以也可以通过它的类直接调用:

Incrementable .increment();

        尽管当 static 作用于某个字段时,肯定会改变数据创建的方式(因为一个 static 字段对每个类来说都只有一份存储空间,而非 static 字段则是对每个对象有一个存储空间),但是 如果 static 作用于某个方法,差别却没有那么大。static 方法的一个重要用法就是在不创建任何对象的前提下就可以调用它。正如我们将会看到的那样,这一点对定义 main() 方法很重要,这个方法是运行应用时的入口点。
        和其他任何方法一样,static 方法可以创建或使用与其类型相同的被命名对象,因此,static 方法常常拿来做“牧羊人”的角色,负责看护与其隶属同一类型的实例群。


  1. 《深入理解 Java 虚拟机》【P39】 ↩︎

  2. Java内存的一点理解, 静态方法和实例方法的区别及使用场景↩︎

  3. 《Thinking inJava 中文第 4 版》【P29】 ↩︎

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值