Java对象作为属性时的内存_java中类的成员属性是怎样分配
内存的呢
匿名⽤户
1级
2018-01-04 回答
属性、⽅法、构造⽅法和⾃由块都是类中的成员,在创建类的对象时,类中各成员的执⾏顺序:
1.⽗类静态成员和静态初始化快,按在代码中出现的顺序依次执⾏。
2.⼦类静态成员和静态初始化块,按在代码中出现的顺序依次执⾏。
3. ⽗类的实例成员和实例初始化块,按在代码中出现的顺序依次执⾏。
4.执⾏⽗类的构造⽅法。
5.⼦类实例成员和实例初始化块,按在代码中出现的顺序依次执⾏。
6.执⾏⼦类的构造⽅法。
最后,⽣成对象由main线程调⽤
再强调⼀遍:⽗静态->⼦静态->⽗变量->⽗初始化区->⽗构造–>⼦变量->⼦初始化区->⼦构造
知道了1+1=2,让我们来举个简单的栗⼦:
[java] view plain copy
package wclass;
public class A extends B {
public int a = 100;
public A() {
super();
System.out.println(a);
a = 200;
}
public static void main(String[] args) {
System.out.println(new A().a);
}
}
class B {
public B() {
System.out.println(((A) this).a);
}
}
这⾥其实只有两个trick:
第⼀,super()必须是A()构造⽅法中的第⼀⾏,也就是说Java必须保证构造⽅法的调⽤是符合上⾯顺序的;
第⼆,在B()构造⽅法中调⽤((A) this).a时,A类被分配到内存空间,但是它的成员a还没有被初始化,所以a的值是默认值0;(诡异的⽤法。。。)
[java] view plain copy
//B()
//A()
100
//main
200
所以,别忘了内存的分配过程:
[plain] view plain copy
new的时候
栈 堆
rectangle ------------------------------------------> new Rectangle(3,2);
new ClassName();//创建 ClassName类的⼀个实例时:
解释器截取new这个关键字时,就会为ClassName量⾝定做⼀个内存空间,这个时候也就是为该类中的所有成员变量分配内存空间之时,然后按照前⾯的顺序进⾏初始化,所有引⽤类型将其制成null 基本数据类型为0;
之后解释器会继续解释执⾏到 ClassName();这句话,也就是该类的构造器,调⽤指定的类的构造⽅法(根据⽤户的需求初始化对象)。
然⽽这⾥⾯有⼀种成员变量并不完全在此过程中被初始化,此成员变量为静态成员变量,它是在当类静态属性或⽅法第⼀次被调⽤或者该类第⼀次被创建对象时被初始化。
接下来是继承关系下⽗⼦类的创建和初始化过程:
当我们试图去创建⼀个⼦类时,java解释器发现该类继承了其他类,所以就会先去创建其⽗类,切记这个时候并没有为⼦类分配任何的内存空间,
⽽是直接越过⾃⼰的创建过程去创建⽗类,如果检查到⽗类也继承了其他类,java解释器就会依此类推继续创建⽗类的⽗类。直到最后⼀个根⽗类
被分配内存后才会创建⼦类。⽽构造⽅法的调⽤则是从⼦类开始的,但是在⼦类的构造⽅法中必须去调⽤⽗类的构造⽅法,经常性的我们没有看到
在⼦类的构造⽅法中显⽰的调⽤⽗类的构造⽅法,那是因为解释器隐式的调⽤了⽗类默认的⽆参构造⽅法,然⽽当我们通过重载机制将⽗类的默认
构造⽅法覆盖时,那么在⼦类中就必须显⽰的调⽤⽗类的构造⽅法
补充Java内存管理知识:原博客地址
1. 内存分配策略
按照编译原理的观点,程序运⾏时的内存分配有三种策略,分别是静态的,栈式的,和堆式的。
静态存储分配是指在编译时就能确定每个数据⽬标在运⾏时刻的存储空间需求,因⽽在编译时就可以给他们分配固定的内存空间。这种分配策略要求程序代码中不允许有可变数据结构(⽐如可变数组)的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序⽆法计算准确的存储空间需求。
栈式存储分配也可称为动态存储分配,是由⼀个类似于堆栈的运⾏栈来实现的。和静态存储分配相反,在栈式存储⽅案中,程序对数据区的需求在编译时是完全未知的,只有到运⾏的时候才能够知道,但是规定在运⾏中进⼊⼀个程序模块时,必须知道该程序模块所需的数据区⼤⼩才能够为其分配内存。和我们在数据结构所熟知的栈⼀样,栈式存储分配按照先进后出的原则进⾏分配。
静态存储分配要求在编译时能知道所有变量的存储要求,栈式存储分配要求在过程的⼊⼝处必须知道所有的存储要求,⽽堆式存储分配则专门负责在编译时或运⾏时模块⼊⼝处都⽆法确定存储要求的数据结构的内存分配,⽐如可变长度串和对象实例。堆由⼤⽚的可利⽤块或空闲块组成,堆中的内存可以按照任意顺序分配和释放。
2. JVM中的堆和栈
JVM是基于堆栈的虚拟机。JVM为每个新创建的线程都分配⼀个堆栈,也就是说,对于⼀个Java程序来说,它的运⾏就是通过对堆栈的操作来完成的。堆栈以帧为单位保存线程的状态。JVM对堆栈只进⾏两种操作:以帧为单位的压栈和出栈操作。
java把内存分两种:⼀种是栈内存,另⼀种是堆内存
栈(stack)与堆(heap)都是Java⽤来在Ram中存放数据的地⽅。与C++不同,Java⾃动管理栈和堆,程序员不能直接地设置栈或堆。
栈(stack):是⼀个先进后出的数据结构,通常⽤于保存⽅法(函数)中的参数,局部变量。
堆(heap):是⼀个可动态申请的内存空间(其记录空闲内存空间的链表由操作系统维护),是⼀个运⾏时数据区,C中的malloc语句所产⽣的内存空间就在堆中。
3. 堆和栈优缺点⽐较
栈的优势是,存取速度⽐堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据⼤⼩与⽣存期必须是确定的,缺乏灵活性。另外,栈数据可以共享,详见第3点。
堆的优势是可以动态地分配内存⼤⼩,⽣存期也不必事先告诉编译器,Java的垃圾收集器会⾃动收⾛这些不再使⽤的数据。但缺点是,由于要在运⾏时动态分配内存,存取速度较慢。
4. Java中的数据类型有两种
⼀种是基本类型
共有8种,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。
这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为⾃动变量。值得注意的是,⾃动变量存的是字⾯值,不是类的实例,即不是类的引⽤,这⾥并没有类的存在。如int a = 3; 这⾥的a是⼀个指向int类型的引⽤,指向3这个字⾯值。这些字⾯值的数据,由于⼤⼩可知,⽣存期可知(这些字⾯值固定定义在某个程序块⾥⾯,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。
另外,栈有⼀个很重要的特殊性,就是存在栈中的数据可以共享。假设我们同时定义:
int a = 3;
int b = 3;
编译器先处理int a = 3;⾸先它会在栈中创建⼀个变量为a的引⽤,然后查有没有字⾯值为3的地址,没到,就开辟⼀个存放3这个字⾯值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的引⽤变量后,由于在栈中已经有3这个字⾯值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。
特别注意的是,这种字⾯值的引⽤与类对象的引⽤不同。假定两个类对象的引⽤同时指向⼀个对象,如
果⼀个对象引⽤变量修改了这个对象的内部状态,那么另⼀个对象引⽤变量也即刻反映出这个变化。相反,通过字⾯值的引⽤来修改其值,不会导致另⼀个指向此字⾯值的引⽤的值也跟着改变的情况。如上例,我们定义完a与b的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字⾯值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。
另⼀种是包装类数据
如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java⽤new()语句来显⽰地告诉编译器,在运⾏时才根据需要动态创建,因此⽐较灵活,但缺点是要占⽤更多的时间。
String是⼀个特殊的包装类数据。即可以⽤String str = new String("abc");的形式来创建,也可以⽤String str = "abc";的形式来创建(作为对⽐,在JDK 5.0之前,你从未见过Integer i = 3;的表达式,因为类与字⾯值是不能通⽤的,除了String。⽽在JDK 5.0中,这种表达式是可以的!因为编译器在后台进⾏Integer i = new Integer(3)的转换)。前者是规范的类的创建过程,即在Java中,⼀切都是对象,⽽对象是类的实例,全部通过new()的形式来创建。Java中的有些类,如DateFormat类,可以通过该类的getInstance()⽅法来返回⼀个新创建的类,似乎违反了此原则。其实不然。该类运⽤了单例模式来返回
类的实例,只不过这个实例是在该类内部通过new()来创建的,⽽getInstance()向外部隐藏了此细节。那为什么在String str = "abc";中,并没有通过new()来创建实例,是不是违反了上述原则?其实没有。
5.String在内存中的存放
java系统变量设置String是⼀个特殊的包装类数据,可以⽤⽤以下两种⽅式创建:
String str = new String("abc");第⼀种创建⽅式是⽤new()来新建对象的,它会存放于堆中。每调⽤⼀次就会创建⼀个新的对象。
String str = "abc"; 第⼆种创建⽅式先在栈中创建⼀个对String类的对象引⽤变量str,然后在栈中查有没有存放值为"abc"的地址,如果没有,则开辟⼀个存放字⾯值为"abc"的地址,接着创建⼀个新的String类的对象o,并将o的字符串值指向这个地址,⽽且在栈中这个地址旁边记下这个引⽤的对象o。如果已经有了值为"abc"的地址,则查对象o,并返回o的地址,最后将str指向对象o的地址。
值得注意的是,⼀般String类中字符串值都是直接存值的。但像String str = "abc";这种场合下,其字符串值却是保存了⼀个指向存在栈中数据的引⽤!
6.数组在内存中的存放
int x[] 或者int []x 时,在内存栈空间中创建⼀个数组引⽤,通过该数组名来引⽤数组。
x = new int[5] 将在堆内存中分配5个保存int型数据的空间,堆内存的⾸地址放到栈内存中,每个数组元素被初始化为0。
7.static变量在内存中的存放
⽤ static的修饰的变量和⽅法,实际上是指定了这些变量和⽅法在内存中的“固定位置”-static storage。既然要有“固定位置”那么他们的 “⼤⼩”似乎就是固定的了,有了固定位置和固定⼤⼩的特征了,在栈中或堆中开辟空间那就是⾮常的⽅便了。如果静态的变量或⽅法在不出其作⽤域的情况下,其引⽤句柄是不会发⽣改变的。
8. java中变量在内存中的分配
1、类变量(static修饰的变量)
在程序加载时系统就为它在堆中开辟了内存,堆中的内存地址存放于栈以便于⾼速访问。静态变量的⽣命周期⼀直持续到整个"系统"关闭
2、实例变量
当你使⽤java关键字new的时候,系统在堆中开辟并不⼀定是连续的空间分配给变量(⽐如说类实例),然
后根据零散的堆内存地址,通过哈希算法换算为⼀长串数字以表征这个变量在堆中的"物理位置"。 实例变量的⽣命周期--当实例变量的引⽤丢失后,将被GC(垃圾回收器)列⼊可回收“名单”中,但并不是马上就释放堆中内存
3、局部变量
局部变量,由声明在某⽅法,或某代码段⾥(⽐如for循环),执⾏到它的时候在栈中开辟内存,当局部变量⼀但脱离作⽤域,内存⽴即释放
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论