一个简单的java的内存分析
一、什么是内存
关于java的内存解释每个人都有自己的解释,网上也看了一大堆,云里雾里。
我个人对java内存的理解:简化的理解为就是存放数据的区域。因为java有虚拟机的存在,它自动会从电脑的内存中分配一定的空间,暂时不需要我们考虑这些。
二、java中的内存中的模块分法
对于内存中的模块分法,目前好多种分法,3块的分法(栈、堆、方法区),4块分法(代码区、栈、堆、静态区),5块、甚至7块区域的都有。这些分法仁者见仁智者见智了,我自己倾向于3块区域的划分。
3块区域的划分:(概念解释)
1、栈:就是一种线性表
特点:先进后出,后进先出【指的是数据的存放读取顺序】。记得大学时老师把栈比喻成:枪的弹夹,满梭子的子弹夹,最先放进去的,肯定是最后的一个被打出去。
栈中存放的内容:局部变量、形参。
优点:存取速度快;缺点:栈中存放的数据生命周期是确定的(即:只在一个方法内有效)。
2、堆:就是一种线性表(一种树形的数据结构,)
特点:先进先出。比喻:一辆火车穿越山洞,火车头先进入山洞,同时也是火车头先出山洞。
堆中存放的内容:new出来的东西(例如:Student s=new Student();)。
3、方法区:
存放内容:方法的字节码、和常量池。’
三、内存中的数据存储细节具体分析
简单案例1 :先用java代码引入这个问题:
public class Student {
String name; //姓名
int age; //年龄
double hight; //身高
public Student(){} //无参构造函数
public Student(String name,int age,double hight) //有参构造函数
{
this.name=name;
this.age=age;
this.hight=hight;
}
public static void main(String[] args)
{
int salary=199;// 工资 这个是局部变量
Student stu=new Student(“刘诗雯”,25,175);
System.out.println(“暂时不打印数据,此行代码”);
}
}
内存如下图:
解释:
关于对0x99、0x88代表的内存中的16位地址。或者就叫地址,粗暴的理解如同家庭住址一样。
具体分析:
1. 程序执行的入口是main()方法,所以在main()方法中的代码第一行是int salary=199;
salary 在方法体内部,所以是局部变量【关于局部变量、成员变量、类变量也叫静态变量的我个人理解,会在文章最后写出来】
按语法局部变量存放在栈中,所以在图中的栈中有一个salary 变量名,而且它的生命周期也是确定的(main方法结束,局部变量的salary变在内存中消失。此时第一行代码执行完成)。
2. 执行第二行代码
Student stu=new Student(String name,int age,double hight);
等号运算符的执行顺序是从右到左。
所以 执行new Student(String name,int age,double hight);
因此在方法块中分出一块是编译后的new Student(String name,int age,double hight)字节码信息。其中关键字new 按照定义在堆中开辟一块空间(0x99),包括name 、age、hight属性信息。
又因name 是String类型。
String 在jdk源码修饰的是public final class String。
final关键字修饰的数据类型是常量。所以String name=”刘诗雯”是常量。因此在方法块的常量池中以存放一个 ”刘诗雯”,--》地址为(0x88) 。它指向堆中name【此时name在堆中存放的是指向常量池中”刘诗雯”的地址(0x88)】
age、higth 属性是基本数据类型修饰,所以在new 方法中存放的是内容。age=25,hight=175.0;(图中画的时候有瑕疵,double的默认值是0.0);
右边的代码分析结束,将左边赋值给右边。右边是String stu ,stu是String引用类型的变量,变量是放在栈中的。在栈中有一个变量名stu,引用变量名在栈中存放的不是内容,而是指向的一个地址(0x99)。
第二行的代码运行结束,这个简单的内存就分析结束了。
案例二:
public class Demo {
public static void main(String[] args) {
Demo t=new Demo();
int age=40;
Person tom=new Person(1,20,"海淀");
Person jack=new Person(2,30,"朝阳");
t.method0(age);
t.method1(tom);
t.change2(jack);
System.out.println(age);
System.out.println("id:"+tom.id+",age:"+tom.age+",school:"+tom.school);
System.out.println("id:"+jack.id+",age:"+jack.age+",school:"+jack.school);
}
public void method0(int i){
i=3366;
}
public void method1(Person p0){
p0=new Person(3,22,"西城");
}
public void change2(Person p1){
p1.setAge(66);
}
}
class Person{
int id;
int age;
String school;
public Person (int a,int b,String c){
id=a;
age=b;
school=c;
}
public void setAge(int a){
age=a;
}
}
内存分析图
内存具体分析:
1.从程序的入口main开始分析:
代码执行的第一行:
Demo t=new Demo();
通过关键字new 在堆中开辟一个空间,指向方法块中的newDemo()的字节码。
Dem是一个类,类是引用数据类型,所有它在栈中创建一个变量名t存放指向堆中的new出来的地址0x77,堆中的0x77指向方法块中的new Demo()字节码。
2.代码执行第二行:
int age=40;
创建局部变量age ,并初始化赋值为40,在栈空间中。
3. 代码执行第三行
Person tom=new Person(1,20,"海淀");
右边通过new在堆中开辟一块空间存放id、age、school 属性信息。地址为0x99,并为属性初始化赋值,id=1,age=20,school=”海淀”
school字段是String类型,在方法块的常量池中创建一个 ”海淀” ,school中存放为常量池中”海淀”的地址值。
左侧通过类Person 创建一个栈中变量名为tom对象,tom中存放0x99地址。
4.代码执行第四行、
Person jack=new Person(2,30,"朝阳");
解释同上。
5.代码执行第5行。
t.method0(age);
将变量age=40 的值传给方法method0(int i);中形参i,此时形参在栈中创建一个变量名为int 类型的i,i在栈中的值为40;
public void method0(int i){
i=3366;
}
method0方法体内又进行一次,将3366赋值给i,此时i值被修改为3366;
因为i为局部变量,局部变量的有效性范围在方法体中,所以当方法method0方法运行结束,i变失效,在栈中的被释放。而age依然为40。因为变量age的有效范围在main方法中。同理当main方法运行完,age也在栈中会失效,
6.代码执行第六行
t.method1(tom);
通过类的对象调用method1()方法
public void method1(Person p0){
p0=new Person(3,22,"西城");
}
形参为Person p0
根据概念形参在栈中,栈中创建一个变量名为p0的变量,p0的类型为类,所以在p0中存放是一个地址,将Person 类型的tom 对象传给p0,因此p0存放的是tom相同指向的地址0x99。然后执行方法体内的语句,等号左边又进行一次new关键字通过new(int id,int age,String school)字节码信息匹配创建一个堆空间0x66,p0存放的地址变更为0x66,并为其属性赋值,id=3、age=22、school存放一个指向常量池中”西城”的地址。到此方法method1执行结束。而方法中所有的局部变量在栈中失效被释放。所以p0失效,堆中开辟的空间0x66因缺少指向,被java 虚拟机视为垃圾,被自动回收(java中垃圾回收只回收堆中的垃圾)。
方法块的常量池中 ”西城” 依然存在。
7.代码执行第七行
t.change2(jack);
解释同代码执行第六行。不同点是,方法体的语句是
p1.setAge(66);
p1也是引用类型存放也是jack传递过来的地址0x88。通过setAge法修改0x88地中的age属性值,此时0x88中age不在等于30,被修改为66了。
方法运行结束,栈中的局部变量名p1失效。
8、至此内存分析结束。
System.out.println(age);
System.out.println("id:"+tom.id+",age:"+tom.age+",school:"+tom.school);
System.out.println("id:"+jack.id+",age:"+jack.age+",school:"+jack.school);
通过上面的输出语句,然后在内存中找到其对应的值。
最后输出结果为
40
id: 1 age: 20 school:海淀
id: 2 age: 66 school:朝阳
【关于局部变量 成员变量 静态变量解读】
局部变量:定义在方法体内部的变量,有效范围,仅在方法体内有效
成员变量:定义在类中的变量,伴随着类的有效期
静态变量;也成为类变量 有个关键字修饰 static