我试着从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 数据内存区。
分享到:
相关推荐
在内存管理上,静态属性和方法对JVM的影响主要体现在以下几个方面: 1. **内存分配**:静态属性在类加载时分配,占用的是方法区的内存,而非堆。这意味着静态属性的生命周期与类的生命周期相同,直到类卸载才会被...
7. 内存管理:包括堆内存和栈内存,对象主要存储在堆中,局部变量存储在栈中。Java的垃圾回收机制自动管理堆内存,释放不再使用的对象。 以上只是对Java对象、类对象和类的初步介绍,实际编程中,我们需要结合这些...
- 继承:一个类可以继承另一个类的属性和方法,实现代码重用,体现面向对象的层次结构。 - 多态:同一个接口,不同对象可以有不同的实现,提供灵活性,使程序更具扩展性。 3. **Java中的访问修饰符有哪些?** ...
Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。 Anonymous Inner Class 是否可以 extends 其它类,是否可以 implements 接口?匿名的内部类是没有名字的内部类。不能...
程序员不用担心内存管理,因为垃圾收集器会自动进行管理。要请求垃圾收集,可以调用下面的方法之一: System.gc() Runtime.getRuntime().gc() 37.String s = new String(\"xyz\");创建了几个String Object? 答:...
21、当一个对象被当作参数传递到一个方法后,此方法可改变这个对象的属性,并可返回变化后的结果,那么这里到底是值传递还是引用传递? 10 22、我们在web 应用开发过程中经常遇到输出某种编码的字符,如iso8859-1等,...
** 封装是面向对象的三大特性之一,通过访问修饰符限制类的成员(属性和方法)的访问,提高代码的安全性和维护性。 3. **类与对象** - **如何创建一个类?** 使用关键字`class`定义类。 - **构造函数的作用是什么...
70、TreeSet里面放对象,如果同时放入了父类和子类的实例对象,那比较时使用的是父类的compareTo方法,还是使用的子类的compareTo方法,还是抛异常! 48 71、说出一些常用的类,包,接口,请各举5个 49 72、java中有...
70、TreeSet里面放对象,如果同时放入了父类和子类的实例对象,那比较时使用的是父类的compareTo方法,还是使用的子类的compareTo方法,还是抛异常! 48 71、说出一些常用的类,包,接口,请各举5个 49 72、java中有...
70、TreeSet里面放对象,如果同时放入了父类和子类的实例对象,那比较时使用的是父类的compareTo方法,还是使用的子类的compareTo方法,还是抛异常! 48 71、说出一些常用的类,包,接口,请各举5个 49 72、java中有...
70、TreeSet里面放对象,如果同时放入了父类和子类的实例对象,那比较时使用的是父类的compareTo方法,还是使用的子类的compareTo方法,还是抛异常! 48 71、说出一些常用的类,包,接口,请各举5个 49 72、java中...
70、TreeSet里面放对象,如果同时放入了父类和子类的实例对象,那比较时使用的是父类的compareTo方法,还是使用的子类的compareTo方法,还是抛异常! 52 71、说出一些常用的类,包,接口,请各举5个 54 72、java中有...
70、TreeSet里面放对象,如果同时放入了父类和子类的实例对象,那比较时使用的是父类的compareTo方法,还是使用的子类的compareTo方法,还是抛异常! 48 71、说出一些常用的类,包,接口,请各举5个 49 72、java中有...
- 存储在方法区的静态存储区域中。 - 被所有对象共享,即不论创建多少个对象,静态变量都只有一个副本。 - 通过类名直接访问。 - **实例变量**: - 每个对象都拥有一份独立的副本。 - 存储在堆内存中,随着...