原文地址:
1、http://hupy.iteye.com/blog/796265
http://blog.csdn.net/wind5shy/article/details/3776931
将某 class 产生出一个 instance 之后,此 class 所有的 instance field 都会新增一份,那么所有的 instance method 是否也会新增一份?答案是不会,我们用field表示字段,用method表示方法,那么加上static区分后就 有四种:
class field:有用static修饰的field class method:有用static修饰的method instance field:没有用static修饰的field instance method:没有用static修饰的method
那么他们在内存中的表示为:
class field:共用一块记忆体 class method:共用一块记忆体 instance field:随着每个instance各有一块记忆体 instance method:共用一块记忆体
如果instance method也随着instance增加而增加的话,那内存消耗也太大了,为了做到共用一小段内存,Java 是根据this关键字做到的,比如:instance1.instanceMethod(); instance2.instanceMethod(); 在传递给对象参数的时候,Java 编译器自动先加上了一个this参数,它表示传递的是这个对象引用,虽然他们两个对象共用一个方法,但是他们的方法中所产生的数据是私有的,这是因为参数被传进来变成call stack内的entry,而各个对象都有不同call stack,所以不会混淆。其实调用每个非static方法时,Java 编译器都会自动的先加上当前调用此方法对象的参数,有时候在一个方法调用另一个方法,这时可以不用在前面加上this的,因为要传递的对象参数就是当前执行这个方法的对象。
为什么静态方法中不能调用非静态方法?这是因为静态方法直接跟class相关,调用此方法的时候是类直接调用的,而不是对象,所以Java 编译器就没有对象参数可以传递,这样,如果你在静态方法内部调用非静态方法,那么Java 编译器怎么判断这个非静态方法是哪个对象调用的?对吧,所以Java 编译器就会报错,但是也不是绝对的,Java 编译器是隐式的传递对象参数,那么我们总可以显示的传递对象参数吧,如果我们把某个对象的引用传递到static方法里,然后通过这个引用就可以调用非静态方法和访问非静态数据成员了。
作者 robbin (http://hibernate.fankai.com/站长) 我试着从JVM的内存管理原理的角度来谈一下静态方法和静态属性的问题,不对的地方请指正。 JVM的内存分为两部分:stack和heap: stack(栈)是JVM的内存指令区。stack管理很简单,push一定长度字节的数据或者指令,stack指针压栈相应的字节位移;pop一定字节长度数据或者指令,stack指针弹栈。stack的速度很快,管理很简单,并且每次操作的数据或者指令字节长度是已知的。所以Java基本数据类型,Java指令代码,常量都保存在stack中。 heap(堆)是JVM的内存数据区。heap的管理很复杂,每次分配不定长的内存空间,专门用来保存对象的实例。在heap中分配一定的内存来保存对象实例,实际上也只是保存对象实例的属性值,属性的类型和对象本身的类型标记等,并不保存对象的方法(方法是指令,保存在stack中),在heap中分配一定的内存保存对象实例和对象的序列化比较类似。而对象实例在heap中分配好以后,需要在stack中保存一个4字节的heap内存地址,用来定位该对象实例在heap中的位置,便于找到该对象实例。 由于stack的内存管理是顺序分配的,而且定长,不存在内存回收问题;而heap则是随机分配内存,不定长度,存在内存分配和回收的问题;因此在JVM中另有一个GC进程,定期扫描heap,它根据stack中保存的4字节对象地址扫描heap,定位heap中这些对象,进行一些优化(例如合并空闲内存块什么的),并且假设heap中没有扫描到的区域都是空闲的,统统refresh(实际上是把stack中丢失了对象地址的无用对象清除了),这就是垃圾收集的过程。 我们首先要搞清楚的是什么是数据,什么是指令?然后要搞清楚对象的方法和对象的属性分别保存在哪里? 为了便于描述,我简单的统称: 1)方法本身是指令的操作码部分,保存在stack中; 2)方法内部变量作为指令的操作数部分,跟在指令的操作码之后,保存在stack中(实际上是简单类型保存在stack中,对象类型在stack中保存地址,在heap中保存值); 上述的指令操作码和指令操作数构成了完整的Java指令。 3)对象实例包括其属性值作为数据,保存在数据区heap中。 非静态的对象属性作为对象实例的一部分保存在heap中,而对象实例必须通过stack中保存的地址指针才能访问到。因此能否访问到对象实例以及它的非静态属性值完全取决于能否获得对象实例在stack中的地址指针。 先分析一下非静态方法和静态方法的区别: 非静态方法有一个和静态方法很重大的不同:非静态方法有一个隐含的传入参数,该参数是JVM给它的,和我们怎么写代码无关,这个隐含的参数就是对象实例在stack中的地址指针。因此非静态方法(在stack中的指令代码)总是可以找到自己的专用数据(在heap中的对象属性值)。当然非静态方法也必须获得该隐含参数,因此非静态方法在调用前,必须先new一个对象实例,获得stack中的地址指针,否则JVM将无法将隐含参数传给非静态方法。 而静态方法无此隐含参数,因此也不需要new对象,只要class文件被ClassLoader load进入JVM的stack,该静态方法即可被调用。当然此时静态方法是存取不到heap中的对象属性的。 总结一下该过程:当一个class文件被ClassLoader load进入JVM后,方法指令保存在stack中,此时heap区没有数据。然后程序技术器开始执行指令,如果是静态方法,直接依次执行指令代码,当然此时指令代码是不能访问heap数据区的;如果是非静态方法,由于隐含参数没有值,会报错。因此在非静态方法执行前,要先new对象,在heap中分配数据,并把stack中的地址指针交给非静态方法,这样程序技术器依次执行指令,而指令代码此时能够访问到heap数据区了。 再说一下静态属性和动态属性: 前面提到对象实例以及动态属性都是保存在heap中的,而heap必须通过stack中的地址指针才能够被指令(类的方法)访问到。因此可以推断出:静态属性是保存在stack中的(基本类型保存在stack中,对象类型地址保存在stack,值保存在heap中),而不同于动态属性保存在heap中。正因为都是在stack中,而stack中指令和数据都是定长的,因此很容易算出偏移量,也因此不管什么指令(类的方法),都可以访问到类的静态属性。也正因为静态属性被保存在stack中,所以具有了全局属性。 总结一下:静态属性保存在stack指令内存区,动态属性保存在heap数据内存区。
以下是关于这篇文章的一些讨论:
20 楼 fankai(fankai) 回复于 2003-09-23 11:54:01 得分 10
Hi, 大家好,我就是本文的作者robbin,真要彻底明确无误搞清楚静态方法和静态属性的问题,就必须去读JVM的C++源代码才能够说自己的结论是正确的,舍此别无它法。 不过我不会C++,所以也没有达到那种境界,只是根据自己的 计算机指令系统和CPU原理的知识,结合Java的实际经验进行反向推测,所以也许会有不准确,甚至错误的地方。
……
21 楼 fankai(fankai) 回复于 2003-09-23 12:08:23 得分 5
静态属性如果是基本类型,或者是这样的样子: static String str = "Hello World"; 在声明的同时就赋值,那么是存放在类的常量池中的,也就是存放在内存指令区,如果是 static String str ; .... str = "Hello World"; 那么静态属性的地址指针存放在内存指令区(stack),而内容放在heap中,地址指针是一个4字节的定长数据结构。
22 楼 fankai(fankai) 回复于 2003-09-23 12:09:57 得分 5
>> 类的静态方法在 classLoader是 push stack,那么在什么时候弹出 stack 方法是指令代码段,只要load进而JVM内存,就不会再释放。指令执行的时候有一个程序计数器,按照一定的顺序读程序,所谓pop stack,只是不在PC指示的代码段中而已。
39 楼 wolfsquare(狼平方 Swing报表工人) 回复于 2003-09-23 21:57:22 得分 5
为了避免误导,还是由我来总结一下: 1.非静态方法和静态方法的不同:非静态方法有一个隐含的传入参数,该参数是JVM给它的类实例, 静态方法也有一个隐含的传入参数,该参数是该类的Class对象的引用,这里有点难理解,需要弄明白Class A 和 A.class这两个概念的不一样. 2.静态属性和动态属性: 正式的说法是静态成员变量和动态成员变量,从以下可以看出静态成员变量和动态成员变量都是存放在堆中的. The Java virtual machine has a heap that is shared among all Java virtual machine threads. The heap is the runtime data area from which memory for all class instances and arrays is allocated.