Java程序内存分配问题
通过学习毕向东、马士兵老师的视频对java程序的内存分配的理解。
先介绍5张图片
图1是UNIX环境高级编程(第二版)的第七章 进程环境,第7.6节 C程序的存储空间布局。
图1 C程序的存储空间布局
书中文字说明
正文:CPU执行的机器指令部分
初始化的数据:通常称为数据段,包含了程序中明确赋值的变量,例如,C程序中任何函数之外的声明:int max = 99;
未初始化的数据:通常称为bss段(blockstarted by symbol:符号开始块)在程序执行前,内核将此段中的数据初始化为0或空指针。例如,函数外的声明:
long sum [ ] = 1000.
栈:自动变量以及临时变量都存放在此段
堆:通常在堆中进行动态分配存储空间。
图2是操作系统概念(第九版)的第二部分进程管理 第三章 进程 截图。
图2 内存中的进程
书中文字说明
Code:文本段 程序代码。
Data:数据段 全局变量 。 (注意:java中没有全局变量这一说法。)
Heap:堆区 程序运行期间动态的分配内存。
Stack:栈区 临时数据(例如函数参数、返回地址和局部变量)。
图3是马士兵老师的java视频截图
图3 马士兵java 视频内存分配图
马老师的观点是:
Codesegment:代码区存放代码
Datasegment:数据区存放静态变量和字符串常量
Stack:栈区 存放局部变量
Heap:堆区 存放new出来的东西。
图4是毕老师在讲“074_面向对象(Static关键字)”时,讲到方法区也称为共享区或者数据区,毕老师把code区和data区放在一起了,如图4所示。
图4 根据毕老师java视频讲解画出的内存分配图
图5是本人根据图2结合图3改变而来的。
图5 改动后的内存分配图
本文以图5为主讲述java程序在内存中的分配
1、 代码区:加载要执行的程序代码
2、 数据区:存放程序中用static关键字修饰的静态变量和字符串常量
3、 堆区:new出来的东西,包括有名对象和匿名对象,类的成员变量(实例变量)
4、 栈区:保存临时数据,如局部变量,函数参数,函数返回值,函数入栈时所需保存的信息
以TestPerson.java为例讲解内存具体分配情况
TestPerson.java class Person { String name ; private static String city = "中国"; int age ; public Person(String name,int age) { //super(); == Object(); this.name = name ; this.age = age ; } public String talk() { return "我是:"+this.name+",今年: "+this.age+"岁,来自: "+city; } } public class TestPerson { publicstatic void main(String[] args) { Person p1 = new Person("张三",22); System.out.println(p1.talk()) ; } }
TestPerson.java内存分配图
图6 函数加载过程
程序TestPerson.java执行过程说明
1、把code区的主函数main( )入栈,图6序号1所示。
2、定义一个对象引用变量p1(栈中分配),图6序号2所示。
3、在new出对象前先将该类对应构造函数Person(String name, int age)入栈,图6序号3所示。
4、执行Person(Stringname, int age)构造函数时,先调用超类Object的默认无参构造函数Object( ),将其入栈,图6序号4所示。
5、超类构造函数Object()执行完毕后,它的栈空间被弹出,接着继续执行Person类的构造函数Person(String name, int age)自己的函数体,如图7所示。
6、 在new对象时,随着类的加载,静态变量 city 优先在数据块中分配存储空间。如图7序号5所示。
7、 在new对象时,将字符串实参"张三"分配到数据区(图7序号6所示),并赋值给构造函数Person(Stringname, int age)的形参neme(栈中分配空间),
即name = "张三"如图中黄色虚线。而形参name 又赋值给类Person的成员变量name,即this.name = name如图7中红色虚线,所以成员变量name也指向数
据区的"张三",即this.name= "张三"如图7中红色实线。
8、 将实参变量22赋值给构造函数的形参age(栈中分配空间),即age= 22,如图7中红色方框所示,形参age又赋值给类Person的成员变量age,
即this.age = age =22,如图中绿色虚线所示。
9、 构造函数执行完毕后,它的函数体内的临时变量消失,函数栈空间被弹出,只剩下引用变量P1指向堆中new出的对象,(即P1保存该对象的在堆空间的地
址),如图8所示。接着继续执行主函数中的System.out.println()函数。如图9所示
图7对象new出对象的过程
图8 对象 new出后的情况
10、执行主函数中System.out.println(p1.talk())函数,先把println()函数入栈(图9中绿色虚线框),又因为println调用了对象p1的talk( )函数,所以把talk( )加载到
栈顶,如图9中红色虚线框所示。
图9 函数出栈过程
11、当talk( )函数执行完毕后,它的栈块被弹出,对象消失。p1不再指向堆空间中new出的对象,接着执行println( )函数,如图10所示。
12、当println( )函数执行完毕后,它的栈块被弹出,接着执行main()函数,如图10所示
13、此时main函数也执行完毕,它的栈块被弹出。引用变量p1消失,如图10所示。
14、整个程序执行完毕后,由java虚拟机的垃圾回收站负责回收堆中的空间。
15、栈中的空间是自动释放的,当栈中临时变量的生命周期结束或函数调用结束函数出栈时,相应的栈空间就自动释放了。
16、疑问:数据区的静态变量city和字符串常量“张三”不知道由谁释放,难道也是java虚拟机的垃圾回收机制负责回收吗??
图10程序执行结束
通过学习两位老师的视频和查看相关书籍,对java程序在内存中的分配做出了自己的理解,总感觉某些地方理解的有误,甚至有些地方的描述可能有点牵强附会,请大家批评指正,谢谢。