<JavaSE学习笔记>面向对象(2):Java内存机制

一 Java内存机制概述


作者是先学习C++,后来才学习的Java。在编写程序的时候,感觉二者最不一样的地方就是:在Java中,程序员并不需要对动态开辟的存储空间(new创建的对象或数组)进行回收(delete操作)。这些工作将会由JVM的垃圾自动回收器接手。Java的内存管理分为以下几个部分:栈(Stack),堆(Heap),方法区(Method Area)。下面几节将会分别介绍这几个区域。


二 栈


栈中保存的是Java八种基本数据类型的变量(short, int, long, byte, double, float, char, boolean),对象的引用变量(相当于对象实例或者数组的名字,也就是Java中的指针),方法帧。当程序员在代码中定义了上述类型的变量时,JVM就会在栈内存中为这些变量分配空间。


需要注意的是:1.引用变量保存的是对象的实例或数组在堆内存中的地址。2.栈内存是线程私有的。JVM为每一个线程分配一个栈,这个栈的生命周期与线程相同。3.当调用程序中的一个方法时,JVM就会吧方法帧(方法需要的参数,方法的局部变量,方法的中间结果)压入栈,当使用结束时,把方法帧弹出栈(同时自动释放掉这些变量被分配的空间)。


三 堆


程序员使用new创建对象和数组所分配的内存空间,称为堆内存。换句话来说,堆内存中存放的是对象的实例和数组。在Java中,程序员并不需要像在C++中那样手动回收这些内存。与栈不同,在多线程开发中,堆内的数据是所有线程栈共享的(并非为每一个线程开辟出一块堆内存)。


需要注意的是:当对象或者数组的引用变量在其生命周期结束时被销毁,但存放在堆内存中的对象的实例和数组并不会被立刻销毁,JVM的内存回收器(Garbage Collection)会在某个不确定的时间回收这部分内存。


例子1:对象的创建

class Student {
	String name;
	int grade;
	public Student(String name, int grade){
		this.name = name;
		this.grade = grade;
	}
}
public class HelloWorld{
	public static void main(String[] args){
		Student s1 = new Student("caoxi",4);
		Student s2 = new Student("quyuan",6);
		System.out.println("If s1 equals s2: " + (s1 == s2));
	}
}





程序的输出结果:If S1 equals S2: false

说明S1和S2保存的是不同的堆内存地址,这两个内存地址分别指向的是两个不同的对象实例。


例子2:引用的传递

class Student {
	String name;
	int grade;
	public Student(String name, int grade){
		this.name = name;
		this.grade = grade;
	}
}
public class HelloWorld{
	public static void main(String[] args){
		Student s1 = new Student("caoxi",4);
		Student s2 = new Student("quyuan",6);
		s2 = s1;
		System.out.println("If s1 equals s2: " + (s1 == s2));
	}
}



由上面这个例子可以看到。引用变量与对象并非是必须是一一对应的。引用传递就是讲一个堆内存空间的使用权分配给多个栈内存空间,也就是说可以有多个引用变量指向同一个对象的实例(此时这两个引用变量存储的堆内存地址是相同的)。例子2中,由于引用变量S2之前已经指向了一个堆内存空间,在引用传递时,它将切断之前的链接而指向对一个对象实例(删去第二个对象实例的地址,存储第一个对象实例的地址),此时第二个对象实例没有任何引用,变成了垃圾,它将等待JVM的垃圾回收器在某个不确定的时间回收掉。


在软件设计中,我们将栈内存中的存储内容看作是程序的逻辑,而把堆内存中存储的内容看作是程序的数据。关于栈和堆的访问速度,访问栈的速度要略快于访问堆的速度,尽管二者都是内存,在物理上的访问机制相同,但访问方式并不相同。

1. 栈内存相较于堆内存来说通常要小很多(这也是满足了为每个线程开辟一个栈的条件),在建立了cache和内存映射的条件下(上述二者只能在有限内存块大小内进行),根据局部性的关系,命中率高,所以访问速度要快很多。

2.访问堆内存中的内容通常需要两次访问:第一次取得指针(引用变量),第二次获取堆内存的内容。而取得栈内存中的内容只需要一次访问。


四 方法区


方法区和堆内存是一样是各个线程共享的,它存储的是被加载的类信息,常量池,方法列表,静态变量等。方法区的大小并非固定的,可以根据需要进行动态调整。方法区的内存地址空间也不必须是连续的,JVM允许用户和程序制定方法区的初始大小,最大和最小尺寸。

其中,类信息包含了类和接口的名称。在方法区中,每一个类都维护着一个被称作常量池的一块内存区,存储的是String, Integer, floating point等类型的常量。其中String类的常量在常量池中是通过表的方式存储对,,有一张固定长度的CONSTANT_String_info表存储字符串值。这些数据和堆内存中的对象实例与数组一样,是通过索引来访问的。方法列表中保存着方法的名称已经它的描述符。

例子3:字符常量

public class HeapAndStackDemo01 {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String a = "haha";
		String b = "haha";
		System.out.println(a==b);
		String c = new String("haha");
		String d = new String("haha");
		System.out.println(c==d);
		System.out.println(a==d);
	}
}

上面这个例子可以看出,c,d这两个引用变量指向了两块不同的堆内存,而a,b这两个指向了常量池中的同一个的地址。



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值