java对象内存存储结构理解

一、java程序的操作系统的内存申请

操作系统的空间申请是以进程为单位。进程包含至少一个线程,当我们运行一个java程序时,开启一个javaw虚拟机进程,并且默认构建一个线程(即main线程),进程向操作系统申请操作系统空间。操作系统空间有两种使用方式,一种叫做栈,另一种叫做堆。java程序中,一个线程就是一个栈,一个进程只有一个堆。如果我们在java程序中没有额外的构建线程,那我们称这个java程序为单进程单线程程序。

我们在java程序中创建对象时,将引用变量存入栈中 ,将所创建对象和代码存入堆中,由栈中的引用变量指向堆中的对象,实现远程调用,以下进行java实例在内存中创建和使用的详细分析。

 二、java对象内存存储结构理解

我们都知道通过 类名  引用变量 = new 类名()可以创建一个对象并调用构造函数,通过 引用变量.方法 可以调用函数,但在内存中具体是怎样实现的呢?以下通过举例进行解析。

	public static void main(String[] args) {
		
		Cat c1 = new Cat("黑",4.2);
		c1.climbTree();

	}

java程序中,代码的解析是从左到右的。

1.首先是遇到Cat类,解析并将Cat类代码加载到堆中的代码区,包括类名、属性和方法,这个过程称作类加载过程。

2.其次是Cat c1,创建一个引用变量,将引用变量保存到栈中。

3.之后是new Cat,在堆中创建Cat裸对象,对象中只 有显示属性和隐式属性,显示属性即类中定义的属性,隐式属性有两个,分别为this和super,this指向自己,super指向父类。此外还有一个方法指针,指向了代码区中的类方法,所以说类中的方法是所有本类对象共享的方法,比较两个对象的大小也只是比较两个类实例属性的大小,至于方法的多少并不重要。

4.然后是Cat("黑",4.2),由于创建的对象只是裸对象,所有属性都为空,无法使对象特征化,因此java提供了一种方法叫做构造方法,对象一旦创建就会自动运行来为对象进行初始化操作。构造函数有以下特征:

a.与类同名

b.不能有返回值,void也不行

c.如果在类中没有手动书写构造方法,系统会自动的创建一个无参无实现的构造函数。所以说每个类至少有一个构造函数

 且这里有一个误区,其实这里是缩写的形式,应该是【new Cat ;Cat("黑","4.2");】   ;只是由于后面的同名,于是舍去一个。

5.第五步就是中间的=号了,这里实现了栈中的引用变量指向了堆中创建的裸对象

6. 下一行c1.climbTree();,实现了调用类中的方法。这里通过栈中的引用变量找到堆中的裸对象,在通过裸对象中的方法指针找到代码区中的类中的方法,然后调用这个方法。假如说方法还要访问实例属性,那还要通过裸对象中的this指针来获取类中的实例属性。

三、静态数据区的存储

1.静态属性的存储

在java程序的内存堆中,还有一块区域用于存放类中定义的静态数据,我们称为静态数据区。这个区域存放了程序中所有类所定义的静态变量,是一个杂居区域。

静态数据是如何载入内存的呢?在类加载的过程中,其实就已经将类中定义的静态变量加载到了静态数据区,但这时对象还没有创建。

静态方法也是在类加载时就存在,但是它被保存在方法区,而不是静态数据区

2.静态属性的使用

当类已加载,但实例还未创建时,静态属性可以通过类名来进行访问。而实例属性不能通过类名进行方位,因为这时静态属性已经加载,而实例属性还不存在。

当类已加载,实例也创建时,静态属性可以通过类名,也可以通过所创建的实例引用变量进行访问。但最好使用类名进行访问,因为使用实例引用变量进行访问的话,效率太低,而且容易误解为实例变量。为什么效率低呢?稍微想一下也知道,通过引用变量去找到实例对象,然后再通过实例对象的方法指针去找代码区的静态变量,再去找到静态数据区的静态变量,这是饶了一大圈,还不如直接用类名进行访问来的高效。

实例方法也可以通过this来访问静态变量,因为this存在的时候,静态变量早已经存在。但是效率低,易误解。

3.静态属性与实例属性的区别

实例属性与静态属性的差别在于实例属性是属于实例的,一个实例一份拷贝,而静态属性是属于类的,不属于任何实例,每个类只有一份静态属性拷贝,这个静态属性被所属类的所有实例共享。

静态方法只能访问静态属性,而不能访问实例属性;而实例方法既能访问实例属性,又能访问静态属性。

静态方法无需创建实例,只要类加载就能访问,而实例方法必须通过实例引用变量才能进行访问。

静态属性存在的时候,实例属性不一定存在。静态属性的诞生周期早于实例属性的诞生周期。

类加载完成时,静态属性就可以被访问,而实例属性必须等创建实例对象后才能被访问。

四、 java对象内存存储结构理解图(基于以上事例)

 java对象内存存储结构理解_第1张图片

五、构造函数的重载以及层叠构造函数

构造函数是类实例化时自动运行的对属性进行初始化的函数。java类中,构造函数可以不写,由系统自动生成无参空实现的构造函数,也可以自己制定构造函数,甚至可以有多个构造函数,只要参数的类型或者个数不同,那么这些构造函数就构成了重载。

public class Car {
	
	//实例属性
	//实例属性属于对象本身,每个对象都有一份拷贝,不能共享,都是对象私有的。
	String carBrand;
	Integer carPrice;
	
	//静态属性
	//静态属性属于类本身,属于模具本身,不属于任何对象,一个类只有一份静态属性的拷贝,这个属性被这个类所有的实例共享。
	//静态属性空间被开辟,此时对象还不存在。
	static int carCnt=0; //统计汽车实例数量
	
	//一个JAVA类一定有构造方法,如果你没有为这个类书写任何构造方法,系统也会给这个类默认提供一个无参空实现的构造方法。

	//无参构造方法
	//层叠构造方法 (cascading constructors)
	public Car() {
		
		//System.out.println("Car constructor is invoked now!"); //invoke 调用
		this("福特",120000);	//构造方法调用本类构造方法,不能直接使用本类名,只能够使用this来表征。
	}
	
	public Car(String carBrand){
		this(carBrand,120000);
	}
	
	public Car(Integer carPrice){
		this("福特",carPrice);
	}
	
		
	//全参构造方法
	public Car(String newCarBrand, Integer carPrice){
		carBrand="("+newCarBrand+")";
		this.carPrice=carPrice;
		carCnt++;
	}
	//一个类可以拥有多个构造方法,一般是参数数量不同,或者在参数数量相同的情况下,数据类型不同,我们称这样的现象叫做构造方法的重载。(overload)
	//构造方法的重载提供了类对象多样性构建策略,为这个类的使用提供了便利。
	
	
	public void run(){
		//访问实例变量,请尽量加上this,以避免后期有可能本地出现相同名称的局部变量,导致运行出错!
		//在属性前头写上this,也利于代码的阅读者理解该名称是实例变量
		System.out.println("一辆"+this.carBrand+"品牌的价位为"+this.carPrice+"元的汽车在奔驰!");
	}
	
	public static void showCarCnt(){
		System.out.println("您到现在为止已经创建了"+Car.carCnt+"辆汽车!");
	}
	

}

 

以上的代码中,car类中有四个构造方法, 他们之间构成了重载,程序员可以根据不同的情况调用不同的构造函数来进行不同方式的属性的初始化。构造函数的多样化为程序员开发提供了很大的便利。

在java类中,如果我们要重载多个构造方法,而属性又很多的情况下,那程序员需要耗费大量的时间去进行属性初始化的逻辑书写,这是非常繁琐的,且一旦要修改,每个构造函数都需要进行修改,不利于程序的维护。因此,java提供了一种方法,叫做层叠构造函数:

层叠构造函数是以全参构造函数为基础(或者所需的足够的初始化属性),其他构造函数调用该构造函数,从而实现构造函数之间的相互调用,那样的话大大节省了程序员的时间,且一旦要修改,只需要修改被调用构造函数即可,方便快捷。但是构造方法调用本类构造方法,不能直接使用本类名,只能够使用this来表征。如以上代码中事例。

你可能感兴趣的:(java语言基础)