第一章 面向对象
第二章 异常
第三章 数组
第四章 常用类
第五章 集合
第六章 IO流
第七章 线程
第八章 反射
第九章 Socket编程

java虚拟机内存管理

 

为了更好的理解上面的程序,先来看看java虚拟机是如何管理它的内存的,请看下图:

 

图9-2:java虚拟机内存管理

 

  • 程序计数器:
    1. 概念:可以看做当前线程所执行的字节码的行号指示器。
    2. 特点:线程私有的内存

 

  • java虚拟机栈(重点):
    1. 概念:描述的是java方法执行的内存模型。(每个方法在执行的时候会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。每个方法从调用直至完成的过程,就对应一个栈帧从入栈到出栈的过程。)
    2. 特点:线程私有,生命周期和线程相同。这个区域会出现两种异常:StackOverflowError异常:若线程请求的深度大于虚拟机所允许的深度。OutOfMemoryError异常:若虚拟机可以动态扩展,如果扩展是无法申请到足够的内存。

 

  • 本地方法栈:
    1. 概念:它与虚拟机栈所发挥的作用是相似的,区别是java虚拟机栈为执行java方法服务,而本地方法栈是为本地方法服务。
    2. 特点:线程私有,也会抛出两类异常:StackOverflowError和OutOfMemoryError。

 

  • java堆(重点):
    1. 概念:是被所有线程共享的一块区域,在虚拟机启动时创建。
    2. 特点:线程共享,存放的是对象实例(所有的对象实例和数组),GC管理的主要区域。可以处于物理上不连续的内存空间。

 

  • 方法区(重点):
    1. 概念:存储已被虚拟机加载的类信息、常量、静态变量,即时编译器编译后的代码等数据。
    2. 特点:线程共享的区域,抛出异常OutOfMemory异常:当方法区无法满足内存分配需求的时候。

 

以上所描述内容,有看得懂的,也有看不懂的,例如:线程、本地方法等,这个需要大家在学习后面内容之后,返回来再看一看,那个时候你就全部明白了。针对于目前来说,大家必须要知道java虚拟机有三块主要的内存空间,分别是“虚拟机栈(后面简称栈)”、“方法区”、“堆区”,方法区存储类的信息,栈中存储方法执行时的栈帧以及局部变量,堆区中主要存储new出来的对象,以及对象内部的实例变量。其中垃圾回收器主要针对的是堆内存,方法区中最先有数据,因为程序执行之前会先进行类加载。栈内存活动最频繁,因为方法不断的执行并结束,不断的进行压栈弹栈操作。将目前阶段需要掌握的内存空间使用一张简单的图表示出来,这个图是大家需要掌握的:

 

图9-3:java虚拟机内存管理简图

 

大概了解了java虚拟机内存分配之后,来看看以下代码在执行过程中,内存是如何变化的:

 

public class StudentTest {
	public static void main(String[] args) {
		int i = 10;
		Student s1 = new Student();
	}
}

 

以上代码在执行过程中内存的变化如下图所示:

 

图9-4:第一步进行类加载

 

图9-5:第二步main方法调用,给main方法分配栈帧(压栈)

 

图9-6:第三步执行int i = 10,局部变量

 

图9-7:第四步执行new Student(),在堆中创建对象,同时初始化实例变量

 

图9-8:第五步将堆区中学生对象的内存地址赋值给局部变量s1

 

注意:上图所描述内存图有些地方为了帮助大家更好的理解,有些位置画的不是很精确,随着后面内容的学习我们再进一步修改,目前上图已经够大家用了。

 

上图中i变量和s1变量都是局部变量,都在栈内存当中,只不过i变量是基本数据类型int,而s1变量是引用数据类型Student。

 

上图中堆区当中的称为“对象”,该“对象”内部no、name、age、sex都是实例变量/属性,这些变量在new对象的时候初始化,如果没有手动赋值,系统会赋默认值。

 

上图堆区中“对象”创建完成之后,该对象在堆区当中的内存地址是:0x1111,程序中的“=”将0x1111这个堆内存地址赋值给s1变量,也就是说s1变量保存了堆内存对象的内存地址,我们对于这种变量有一种特殊的称呼,叫做“引用”。也就是说对于Student s1 = new Student()代码来说,s1不是对象,是一个引用,对象实际上是在堆区当中,s1变量持有这个对象的内存地址。

 

java中没有指针的概念(指针是C语言当中的机制),所以java程序员没有权利直接操作堆内存,只能通过“引用”去访问堆内存中的对象,例如:s1.no、s1.name、s1.sex、s1.age。访问一个对象的内存,其实就是访问该对象的实例变量,而访问实例变量通常包括两种形式,要么就是读取数据,要么就是修改数据,例如:System.out.println(s1.no)这就是读取数据,s1.no = 100这就是修改数据。请看以下代码:

 

public class StudentTest {
	public static void main(String[] args) {
		int i = 10;
		Student s1 = new Student();
		s1.no = 100;
		s1.name = "zhangsan";
		s1.sex = true;
		s1.age = 20;
		System.out.println("学号 = " + s1.no);
		System.out.println("姓名 = " + s1.name);
		System.out.println("性别 = " + s1.sex);
		System.out.println("年龄 = " + s1.age);
}
}

 

运行结果如下所示:

 

图9-9:修改实例变量之后的执行结果

 

执行了以上程序之后,堆内存对象的实例变量发生了变化,如下图所示:

 

图9-10:实例变量执行赋值运算之后的内存图

 

如果基于以上的代码再创建一个对象,内存图会是怎么的呢?先看代码:

 

public class StudentTest {
	public static void main(String[] args) {
		int i = 10;
		Student s1 = new Student();
		Student s2 = new Student();
	}
}

 

JVM内存结构图如下所示:

 

图9-11:创建多个对象的内存结构图

 

通过上图的学习,可以看出假设new出100个学生对象,会有100个no,100个age...是这样吧。

 

通过以上内容的学习,需要每位同学掌握:局部变量存储在哪里?实例变量存储在哪里?实例变量在什么时候初始化?对象和引用有什么区别?在java中怎么访问堆内存当中的对象?这些你都掌握了吗。

 

全部教程