内存分配

2016/09/28 Java

空间划分

Java 程序在运行时,需要在内存中的分配空间。为了提高运算效率,就对空间进行了不同区域的划分,因为每一片区域都有特定的处理数据方式和内存管理方式

空间划分

  • 栈存 储局部变量
    • 方法定义中或者方法声明上的所有变量
    • 使用完毕,立即消失
  • 堆 存储new出来的实体,对象。
    • 每一个实体都有首地址值
    • 每一个实体内的数据都有默认值
      • byte,short,int,long:0
      • float,double:0.0
      • char:‘\u0000’
      • boolean:false
      • 引用类型:null
    • 使用完毕后,会被垃圾回收器空闲的时候回收。
  • 方法区 (面向对象部分讲)
  • 本地方法区 (和系统相关)
    • Java方法是与平台无关的,但是本地方法不是。
  • 寄存器 (给CPU使用)

Java中的内存分配图解

递归内存图解

递归注意事项

  • 要有出口,否则就是死递归
  • 次数不能过多,否则内存溢出
  • 构造方法不能递归使用

阶乘问题递归图解

递归解决问题的思想及图解

递归解决问题的思想及图解

递归求阶乘的代码实现及内存图解

递归求阶乘的代码实现及内存图解.jpg

数组内存图解

一维数组

定义第一个数组,定义完毕后,给数组元素赋值。赋值完毕后,在输出数组名称和元素。
定义第二个数组,定义完毕后,给数组元素赋值。赋值完毕后,在输出数组名称和元素。
定义第三个数组,把第一个数组的地址值赋值给它。(注意类型一致),通过第三个数组的名称去把元素重复赋值。
最后,再次输出第一个数组数组名称和元素。

class ArrayDemo4 {
	public static void main(String[] args) {
		//定义第一个数组
		int[] arr = new int[3];
		arr[0] = 88;
		arr[1] = 33;
		arr[2] = 66;
		System.out.println(arr);//0x001
		System.out.println(arr[0]);//88
		System.out.println(arr[1]);//33
		System.out.println(arr[2]);//66
		System.out.println("----");
		
		//定义第二个数组
		int[] arr2 = new int[3];
		arr2[0] = 22;
		arr2[1] = 44;
		arr2[2] = 55;
		System.out.println(arr2);//0x002
		System.out.println(arr2[0]);//22
		System.out.println(arr2[1]);//44
		System.out.println(arr2[2]);//55
		System.out.println("----");
		
		//定义第三个数组
		int[] arr3 =  arr;
		arr3[0] = 100;
		arr3[1] = 200;
		System.out.println(arr);//0x001
		System.out.println(arr[0]);//100
		System.out.println(arr[1]);//200
		System.out.println(arr[2]);//66
	}
}

数组的内存图解

二维数组

二维数组:就是元素为一维数组的一个数组。

格式1:
数据类型[][] 数组名 = new 数据类型[m][n];

	m:表示这个二维数组有多少个一维数组。
	n:表示每一个一维数组的元素有多少个。
public static void main(String[] args) {
	 //定义一个二维数组
	 int[][] arr = new int[3][2];
	 //定义了一个二维数组arr
	 //这个二维数组有3个一维数组的元素
	 //每一个一维数组有2个元素
	 //输出二维数组名称
	 System.out.println(arr); //地址值	[[I@175078b
	 //输出二维数组的第一个元素一维数组的名称
	 System.out.println(arr[0]); //地址值	[I@42552c
	 System.out.println(arr[1]); //地址值	[I@e5bbd6
	 System.out.println(arr[2]); //地址值	[I@8ee016
	 //输出二维数组的元素
	 System.out.println(arr[0][0]); //0
	 System.out.println(arr[0][1]); //0
}

二维数组格式1的内存图解

格式2:
数据类型[][] 数组名 = new 数据类型[m][];

	m:表示这个二维数组有多少个一维数组。
	列数没有给出,可以动态的给。这一次是一个变化的列数。
public static void main(String[] args) {
	//定义数组
	int[][] arr = new int[3][];
	
	System.out.println(arr);	//[[I@175078b
	System.out.println(arr[0]); //null
	System.out.println(arr[1]); //null
	System.out.println(arr[2]); //null
	
	//动态的为每一个一维数组分配空间
	arr[0] = new int[2];
	arr[1] = new int[3];
	arr[2] = new int[1];
	
	System.out.println(arr[0]); //[I@42552c
	System.out.println(arr[1]); //[I@e5bbd6
	System.out.println(arr[2]); //[I@8ee016
	
	System.out.println(arr[0][0]); //0
	System.out.println(arr[0][1]); //0
	//ArrayIndexOutOfBoundsException
	//System.out.println(arr[0][2]); //错误
	
	arr[1][0] = 100;
	arr[1][2] = 200;
}

二维数组格式2的内存图解

格式3: 基本格式: 数据类型[][] 数组名 = new 数据类型[][]{ {元素1,元素2…},{元素1,元素2…},{元素1,元素2…}}; 简化版格式: 数据类型[][] 数组名 = { {元素1,元素2…},{元素1,元素2…},{元素1,元素2…}};

public static void main(String[] args) {
	//定义数组
	int[][] arr = { {1,2,3},{4,5},{6}};
	
	System.out.println(arr);
	System.out.println(arr[0]);
	System.out.println(arr[1]);
	System.out.println(arr[2]);
	
	System.out.println(arr[0][0]); //1
	System.out.println(arr[1][0]); //4
	System.out.println(arr[2][0]); //6
	
	System.out.println(arr[0][1]); //2
	System.out.println(arr[1][1]); //5
	//越界
	System.out.println(arr[2][1]); //错误
}

二维数组格式3的内存图解

对象数组

数组既可以存储基本数据类型,也可以存储引用类型。它存储引用类型的时候的数组就叫对象数组。

对象数组的内存图解

参数传递内存图解

Java中的参数传递问题:

	基本类型:形式参数的改变对实际参数没有影响。  
	引用类型:形式参数的改变直接影响实际参数。  
public static void main(String[] args) {
	int a = 10;
	int b = 20;
	System.out.println("a:"+a+",b:"+b); //a:10,b:20
	change(a,b);
	System.out.println("a:"+a+",b:"+b); //a:10,b:20

	int[] arr = {1,2,3,4,5}; 
	change(arr);
	System.out.println(arr[1]); //4
}

public static void change(int a,int b) { //a=10,b=20
	System.out.println("a:"+a+",b:"+b); //a:10,b:20
	a = b;	//a=20
	b = a + b; //b=40
	System.out.println("a:"+a+",b:"+b); //a:20,b:40
}

public static void change(int[] arr) { //arr={1,2,3,4,5};
	for(int x=0; x<arr.length; x++) {
		if(arr[x]%2==0) {
			arr[x]*=2;
		}
	}
	//arr={1,4,3,8,5};
}

Java中的参数传递问题图解

面向对象

创建对象

Student s = new Student();在内存中做了哪些事情?

  • 加载Student.class文件进内存
  • 在栈内存为s开辟空间
  • 在堆内存为学生对象开辟空间
  • 对学生对象的成员变量进行默认初始化
  • 对学生对象的成员变量进行显示初始化
  • 通过构造方法对学生对象的成员变量赋值
  • 学生对象初始化完毕,把对象地址赋值给s变量

创建对象做了哪些事情

对象内存图

方法区存放class文件内容,包括成员变量和成员方法。
成员方法在被调用的时候进入栈内存。

一个对象

一个对象的内存图

两个对象

二个对象的内存图

三个对象

三个对象的内存图

this关键字

this关键字的内存图解

static关键字

解决了局部变量隐藏成员变量的问题

特点

  • 随着类的加载而加载
  • 优先于对象存在
  • 被类的所有对象共享
    • 如果某个成员变量是被所有对象共享的,那么它就应该定义为静态的。
  • 可以通过类名调用
    • 也可以通过对象名调用。推荐使用类名调用。

静态修饰的内容一般我们称其为:与类相关的,类成员
静态方法只能访问静态的成员变量和静态的成员方法

static的内存图解

// 解决方案:
// 1.不进行空串初始化
// 2.需要进行初始化需放在util初始化之前
private static StaticTest staticTest = new StaticTest();//最先初始化构造
private static String str="";//这里的空串初始化在构造之后进行

private StaticTest() {
    str = "123";
}

public static String getStr() {
    return str;
}

public static void main(String[] args) {
    System.out.println(StaticTest.staticTest.getStr());//""
}
}

静态变量和成员变量的区别

  • 所属不同
    • 静态变量属于类,所以也称为为类变量
    • 成员变量属于对象,所以也称为实例变量(对象变量)
  • 内存位置不同
    • 静态变量存储于方法区的静态区
    • 成员变量存储于堆内存
  • 生命周期不同
    • 静态变量随着类的加载而加载,随着类的消失而消失
    • 成员变量随着对象的创建而存在,随着对象的消失而消失
  • 调用不同
    • 静态变量可以通过类名调用,也可以通过对象调用
    • 成员变量只能通过对象名调用

多态

继承内存图解

多态继承中的内存图解

转型内存图解

多态中的对象变化内存图解

String类相关内存分析

String的特点一旦被赋值就不能改变

//一旦被赋值,就不能改变 是值不能被改变,hello不能改变,
String s = "hello";
s += "world";//这里是s=s+world,引用是可以改变的
System.out.println("s:" + s); //helloworld

String的特点一旦被赋值就不能改变

String创建对象

String s = new String(“hello”)和String s = “hello”;的区别?

有。前者会创建2个对象,后者创建1个对象。

  • 创建new String的堆对象和常量池对象”hello”
  • 创建常量池对象”hello”

String初始化创建对象区别

垃圾回收

System.gc()可用于垃圾回收。当使用System.gc()回收某个对象所占用的内存之前,通过要求程序调用适当的方法来清理资源。在没有明确指定资源清理的情况下,Java提高了默认机制来清理该对象的资源,就是调用Object类的finalize()方法(不保证一定会被调用,比如kill进程)。finalize()方法的作用是释放一个对象占用的内存空间时,会被JVM调用。而子类重写该方法,就可以清理对象占用的资源,该方法有没有链式调用,所以必须手动实现。

从程序的运行结果可以发现,执行System.gc()前,系统会自动调用finalize()方法清除对象占有的资源,通过super.finalize()方式可以实现从下到上的finalize()方法的调用,即先释放自己的资源,再去释放父类的资源。

但是,不要在程序中频繁的调用垃圾回收,因为每一次执行垃圾回收,jvm都会强制启动垃圾回收器运行,这会耗费更多的系统资源,会与正常的Java程序运行争抢资源,只有在执行大量的对象的释放,才调用垃圾回收最好


以上概念总结于传智播客Java基础课程

Search

    Post Directory