J2SE自学(五)——Java内存管理

1、变量就是计算机中一块块内存空间

2、 [color=red][size=large]Java内存管理原理(是三方关系——程序<-->JVM<-->操作系统) :
1)、程序运行前:有些编程语言编写的程序会直接向操作系统请求内存,但 Java 并不支持那么做。这样做的一个重要优点是保证了程序的平台无关性。既然 Java 程序不负责创建内存空间,那由谁创建呢?是由 JVM 完成。当程序准备执行时,由JVM 预先向操作系统请求一定的内存空间,称为初始内存空间。程序执行过程中所需的内存都由Java 虚拟机从这片内存空间中划分。

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

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


至此可以看出,Java 程序所使用的内存是由 Java 虚拟机进行管理、分配的。Java 虚拟
机规定了 Java 程序的初始内存空间和最大内存空间,开发者只需要关心 Java 虚拟机是如何
管理内存空间的,而不用关心某一种操作系统是如何管理内存的。
[/size][/color]
3、 [color=blue]内存空间逻辑划分:方法区,堆与栈 ——JVM 会把申请的内存从逻辑上划分为三个区域,即:方法区、堆与栈。
[/color]

//这个class整体就是一个方法区!

public class Student {

public String name ;

public int age ;

public void study() {
System.out.println("我在学习java!");
}
}




//这个class中的stu和i都是变量,而 new Student()是对象
public class Demo {

public static void main(String[] args) {

Student stu = new Student() ;
stu.name = "张三" ;
stu.age = 18 ;
int i = 10 ;
}

}


[color=red]
Java虚拟机对应的在内存中划分了三个区域“方法区”、“堆区”和“栈区”,分别保存类结构、对象中的数据和变量(基本型和引用型)。 这三个内存区域都有大小限制,任何一个区域内存溢出都会导致程序出现错误,栈内存溢出会发生StackOverflowException错误,堆内存溢出会发生OutOfMemoryError错误。

1)、方法区中的内存分配:方法区默认最大容量为64M,Java虚拟机会将加载的java类存入
方法区,保存类的结构(属性与方法),类静态成员等内容。编写中小型程序时,一般不会
造成方法区的内存溢出。

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

3)、栈中的内存分配:栈默认最大容量为1M,在程序运行时,每当遇到方法调用时,Java虚拟机就会在栈中划分一块内存称为栈帧(Stack frame),栈帧中的内存供局部变量(包括基本类型与引用类型)使用,当方法调用结束后,Java虚拟机会收回此栈帧占用的内存。
另:还有数组内部也封装了指针,即便是基本数据类型的数组,也封装了指针,数组也是引用类型。
[/color]

4、[color=blue]JAVA 数据类型
C 语言可以声明指针类型的变量。那就会问,java 中有没有指针呢?您一定要明确回答:“JAVA 中没有指针”。问题并没有到此终结,您要紧接着说“但 JAVA 底层封装了指针”。对,Java 底层封装了指针,但我们要说java 中没有指针,也就是不能创建指针类型的变量。
但也并不是所有的类型都封装了指针根据这点不同,java 的数据类型分为两类,即:值类型(基本数据类型)和引用类型。

1)、分类原则:根据是否封装指针(分配空间形式)
2)、基本数据类型:值类型(又称基本数据类型和基元数据类型)——只在栈中分配一块内存(四类八种,详见J2SE自学(三)——Java语法基础(一))
3)、引用数据类型(就是底层封装指针的数据类型。):在栈和堆中各各分配一块内存,例如:类、数组、接口等——他们在内存中分配两块空间,第一块内存分配在栈中,只存放别的内存地址,不存放具体数值,我们也把它叫指针类型的变量,第二块内存分配在堆中,存放的是具体数值,如对象属性值等。
[/color]

5、[color=darkred][size=large]声明对象与创建对象
声明对象,相当分配指针类型变量,在栈中分配内存
类是引用类型,那声明对象就相当于在栈中声明指针类型的变量,它的内存不存放具体的数值,而只存放另一块堆中内存的地址。

创建对象,创建具体内存空间,在堆中分配内存
在java 中一般使用new 关键字创建对象(当然还有更高级的反射可以不用new 关键字)例如:
zhouxingxing = new Student()就是创建对象。这行代码一共做了两件事情:第一件是在堆中分配一块存放学生具体数值的内存(例如:35DF),第二件是把这个内存的首地址赋给上面声明的指针变量。

对象必须创建后才能使用,如果只声明不创建,那么调用对象属性和方法时将会报空指针异常(NullPointerException)
只声明对象,只相当于在栈中分配一块不存放具体数值的内存;没有创建,在堆中没创建存放具体数值的内存,当我们调用其属性方法时,系统找不到具体堆内存,则报空指针异常。同时我们也可以反过来说,空指针异常是因为没有指向堆中内存引起的。
[/size][/color]

6、JAVA 值传参与引用传参——定义:方法根据调用后的效果不同,即是否改变参数的原始数值
按值传递参数数值不变,按引用传递参数数值改变。
对于java的数据类型而言,基本数据类型就是按值传递的,又称为值类型,而引用类型是按引用传递的。如:

public class TestValueAndRef {
/**
* 方法参数有三个
* @param pa 基本类型 值类型
* @param pstu 自定义类 引用类型
* @param parr 数组 引用类型
*/
void change(int pa, Student pstu, int[] parr) {

//方法体中改变值类型pa的值
pa = pa + 10;
//方法体中改变引用类型stu,parr的值
pstu.stuAge = pstu.stuAge + 10;
parr[0] = parr[0] + 10;

System.out.println("方法体改变后pa = " + pa);
System.out.println("方法体改变后pstu.stuAge = " + pstu.stuAge);
System.out.println("方法体改变后parr[0] = " + parr[0]);
}

}


运行类:

public class Test {
public static void main(String[] args) {

Student student = new Student();
student.stuAge = 10;
int a = 10;
int arr[] = new int[]{9, 5, 27};

System.out.println("初始值 a = " + a);
System.out.println("初始值 student.stuAge = " +
student.stuAge);
System.out.println("初始值 arr[0] = " + arr[0]);

TestValueAndRef testValueAndRef = new TestValueAndRef();
testValueAndRef.change(a, student, arr);

System.out.println("调用函数后 a = " + a);
System.out.println("调用函数后 student.stuAge = " +
student.stuAge);
System.out.println("调用函数后 arr[0] = " + arr[0]);

}
}


运行结果:
初始值 a = 10
初始值 student.stuAge = 10
初始值 arr[0] = 9
方法体改变后pa = 20
方法体改变后pstu.stuAge = 20
方法体改变后parr[0] = 19
调用函数后 a = 10
调用函数后 student.stuAge = 20
调用函数后 arr[0] = 19


[color=red]我们看到,基本数据类型int 变量a 虽然在方法体中改变了数值,但方法调用完后其原始数值并没有改变。而引用数据类型Student 在方法体中改变年龄的数值,方法执行完其数值发生了改变,数组也是引用类型,所以其值也发生了改变。也就是说:值类型按值传递参数,方法调用结束后,原始数值不变,引用类型按引用传递参数方法调用结束后,原始数值改变。

不管是按值传递还是按引用传递,都是把栈中的数据备份了一份给参数变量,只不过值类型备份的是具体的数值,而引用类型备份的是内存地址,所以从这个角度看,我们也可以说Java 全部是按值传参的,因为它都是把数据备份了一份。
[/color]
7、String类
[color=blue]特例String 类型,虽然它也是引用类型,但用引用传参后,原始数值却不会改变
[/color]

public class TestString {
public static void main(String[] args) {

String name = "zhangsan";
TestString testString = new TestString();

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

void change(String str) {
str = "张三";
System.out.println("方法体内修改值后:" + str);
}
}

运行结果:
方法调用前:zhangsan
方法体内修改值后:张三
方法调用后:zhangsan

[color=red][size=large]虽然参数String 是引用数据类型,但其值没有发生改变,这是因为String 类是final 的,它是定长。方法体内改变的仅仅是改变了str的引用!对方法体外的name并没有任何影响!
[/size][/color]
以后遇到类似而的问题要“多思考,多画图,多实践!”
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值