java 内存管理_JAVA内存管理

被问到有关Java内存管理的知识,所以要搜集整理一下了。开始之前,我们要明白一点,我们所使用的变量就是一块一块的内存空间!!

一、内存管理原理:

在java中,有java程序、虚拟机、操作系统三个层次,其中java程序与虚拟机交互,而虚拟机与操作系统间交互!这就保证了java程序的平台无关性!下面我们从程序运行前,程序运行中、程序运行内存溢出三个阶段来说一下内存管理原理!

1、程序运行前:JVM向操作系统请求一定的内存空间,称为初始内存空间!程序执行过程中所需的内存都是由java虚拟机从这片内存空间中划分的。

2、程序运行中:java程序一直向java虚拟机申请内存,当程序所需要的内存空间超出初始内存空间时,java虚拟机会再次向操作系统申请更多的内存供程序使用!

3、内存溢出:程序接着运行,当java虚拟机已申请的内存达到了规定的最大内存空间,但程序还需要更多的内存,这时会出现内存溢出的错误!

至此可以看出,Java 程序所使用的内存是由 Java 虚拟机进行管理、分配的。Java 虚拟机规定了 Java 程序的初始内存空间和最大内存空间,开发者只需要关心 Java 虚拟机是如何管理内存空间的,而不用关心某一种操作系统是如何管理内存的。

二、 RUNTIME 类的使用:

Java 给我们提供了Runtime 类得到JVM 内存的信息

方法名称

参数

作用

返回值

getRuntime

获取 Runtime 对象

Runtime 对象

totalMemory

获取 JVM 分配给程序的内存数量

long:内存数量

freeMemory

获取 当前可用的内存数量

long:内存数量

maxMemory

获取 JVM 可以申请到的最大内存数量

long:内存数量

三、内存空间逻辑划分:

JVM 会把申请的内存从逻辑上划分为三个区域,即:方法区、堆与栈。

方法区:方法区默认最大容量为64M,Java虚拟机会将加载的java类存入方法区,保存类的结构(属性与方法),类静态成员等内容。

堆:默认最大容量为64M,堆存放对象持有的数据,同时保持对原类的引用。可以简单的理解为对象属性的值保存在堆中,对象调用的方法保存在方法区。

栈:栈默认最大容量为1M,在程序运行时,每当遇到方法调用时,Java虚拟机就会在栈中划分一块内存称为栈帧(Stack frame),栈帧中的内存供局部变量(包括基本类型与引用类型)使用,当方法调用结束后,Java虚拟机会收回此栈帧占用的内存。

四、java数据类型

33db0069bafe588ddf0f29811cdab071.png

1、基本数据类型:没封装指针的变量。

声明此类型变量,只会在栈中分配一块内存空间。

2、引用类型:就是底层封装指针的数据类型。

他们在内存中分配两块空间,第一块内存分配在栈中,只存放别的内存地址,不存放具体数值,我们也把它叫指针类型的变量,第二块内存分配在堆中,存放的是具体数值,如对象属性值等。

3、下面我们从一个例子来看一看:

public classStudent {

String stuId;

String stuName;intstuAge;

}public classTestStudent {public static voidmain(String[] args) {

Student s= newStudent();

String name= new String("云鹤");int a = 10;char b = 'm';

s.stuId= "6363";

s.stuName= "刘德华";

s.stuAge= 25;

}

}

(1)类当然是存放在方法区里面的。

(2)Student s = new Student();

这行代码就创建了两块内存空间,第一个在栈中,名字叫s,它就相当于指针类型的变量,我们看到它并不存放学生的姓名、年龄等具体的数值,而是存放堆中第二块内存的地址,第二块才存放具体的数值,如学生的编号、姓名、年龄等信息。

(3)int a = 10;

这是 基本数据类型 变量,具体的值就存放在栈中,并没有只指针的概念!

下图就是本例的内存布置图:

a9e8f00c27e1cc57d86282b7fc450ad5.png

此外我们还要知道Student s = new Student(); 包括了声明和创建,即:

声明:Student s;

创建:s = new Student();

其中声明只是在栈中声明一个空间,但还没有具体的值,声明后的情况如下图所示:

6901b9b9f9d3784222b5e398fa21f1b6.png

创建后的情况如下图所示:

4b6274aa13484c3fc331cc51efc19301.png

(4)引用类型中的数组也封装了指针,即便是基本数据类型的数组也封装了指针,数组也是引用类型。比如代码int[] arr = new int[]{3, 6, 12, 9, 66, 31};如下图所示:

b738370d9b742ad88bb867d8c55fcee3.png

五、java值传参与引用传参

(1)参数根据调用后的效果不同,即是否改变参数的原始数值,又可以分为两种:按值传递的参数与按引用传递的参数。

按值传递的参数原始数值不改变,按引用传递的参数原始数值改变!这是为什么呢?其实相当简单:

我们知道基本数据类型的变量存放在栈里面,变量名处存放的就是变量的值,那么当基本数据类型的变量作为参数时,传递的就是这个值,只是把变量的值传递了过去,不管对这个值如何操作,都不会改变变量的原始值。而对引用数据类型的变量来说,变量名处存放的地址,所以引用数据类型的变量作为传参时,传递的实际上是地址,对地址处的内容进行操作,当然会改变变量的值了!

正常情况下,我们用数组测试TestArray类如下:

public classTestArray {void change(int[] arr) {for(int i=0;i

arr[i]=1000;

System.out.println("方法体内修改值后:");for(int i=0;i

System.out.println(arr[i]);

}public static voidmain(String[] args) {int[] a = {1,2,3,4};

TestArray testString= newTestArray();

System.out.println("方法调用前:");for(int i=0;i

System.out.println(a[i]);

testString.change(a);

System.out.println("方法调用后:");for(int i=0;i

System.out.println(a[i]);

}

}

输出结果如下:

方法调用前:1

2

3

4方法体内修改值后:1000

2

1000

4方法调用后:1000

2

1000

4

数组实际上也是引用类型,在调用函数的过程中改变了其值。

(2)特例:String

public classTestString {voidchange(String str) {

str= "吴奇隆";

System.out.println("方法体内修改值后:" +str);

}public static voidmain(String[] args) {

String name= "歌星";

TestString testString= newTestString();

System.out.println("方法调用前:" +name);

testString.change(name);

System.out.println("方法调用后:" +name);

}

结果:

方法调用前:歌星

方法体内修改值后:吴奇隆

方法调用后:歌星

分析:

上例中,虽然参数String 是引用数据类型,但其值没有发生改变,这是因为String 类是final 的,它是定长,不允许对其进行改变,而StringBuffer(多线程下使用性能优)和StringBuilder(单线程下面使用性能优)是可以改变的。如果这里用StringBuffer和SringBuiler替代,结果和Array的使用一样,中间结果会被改变。

我们看初始情况,即String name = "歌星";这行代码运行

完,如下图:

e87c4ea4cde995e28b52fe41a3314a33.png

当调用方法时testString.change(name),内存变化为:

c2e7f0be8819b7e3a45a44119e556459.png

在方法体内,参数str赋予一个新值,str = "吴奇隆"。因为"吴奇隆"这个String是定长,系统就会在堆中分配一块新的内存空间37DF,这样str指向了新的内存空间37DF,而name还是指向36DF, 37DF的改变对它已没影响:

ae16827b526d6b93c02a9784730c1b03.png

最后,方法调用结束,str与37DF的内存空间消亡。Name的值依然为歌星,并没有改变。

所以String虽然是引用类型参数,但值依然不变:

673511d38f5a54fa1f1eae22239f3132.png

(3)无法交换的例子:

public classTestChange {voidchange(Student stu1, Student stu2) {

stu1.stuAge++;

stu2.stuAge++;

Student stu=stu1;

stu1=stu2;

stu2=stu;

}public static voidmain(String[] args) {

Student z= newStudent();

z.stuName= "张信哲";

z.stuAge= 40;

Student r= newStudent();

r.stuName= "任贤齐";

r.stuAge= 30;

System.out.println("交换前z:\t"+z.stuName+"\t"+z.stuAge);

System.out.println("交换前r:\t"+r.stuName+"\t"+r.stuAge);

TestChange testChange= newTestChange();

testChange.change(z, r);

System.out.println("交换后z:\t"+z.stuName+"\t"+z.stuAge);

System.out.println("交换后r:\t"+r.stuName+"\t"+r.stuAge);

}

}

运行结果:

交换前z: 张信哲 40交换前r: 任贤齐30交换后z: 张信哲41交换后r: 任贤齐31

93c1592bb02995e4c71af5fe8c9de7be.png

806839f7cd5c3e4c322c97b0445b70cb.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值