摘录一段Java5内存管理白皮书中的一段话:
One strength of the Java™ 2 Platform, Standard Edition (J2SE™) is that it performs automatic memory
management, thereby shielding the developer from the complexity of explicit memory management.
由上面我们可以知道,Java平台采用了自动的内存管理方式,那么JVM是如何进行内存管理的呢?
Java的内存管理实际上包含两个方面:
1).Java内存分配
2).Java的内存回收
搞清楚了这两个问题,Java的内存管理就清楚了。本章主要讲解内存的分配。
谈到Java内存管理,就必须要了解Jvm体系结构了。
如下图所示,JVM的体系结构包含几个主要的子系统和内存区:
类装载子系统 :负责把类从文件系统中装入内存
GC子系统 :垃圾收集器的主要工作室自动回收不再运行的程序引用对象所占用的内存,此外,它还可能负责那些还在使用的对象,以减少的堆碎片。
内存区 :用于存储字节码,程序运行时创建的对象,传递给方法的参数,返回值,局部变量和中间计算结果。
程序运行时,首先当然是将类从文件系统中装入内存,并开始解释字节码运行。在运行的过程中伴随着变量的创建、销毁、回收等动作。这就需要考虑在这个过程中内存所发生的变化。
了解Jvm内存区的划分:
方法区:存放JVM加载的类型信息。包括: 类型基本信息,常量池,字段信息,方法信息,类变量,指向ClassLoader的引用,Class类的引用,方法表等。(对应JVM内存配置中的-PermSize等)
java堆:程序中创建的类的实例和数组,包括class对象和exception对象,存放在堆里面。堆中除了存储对象的实例数据外,还要存储该对象指向方法区中类型信息的指针。(JVM中所有的线程共享堆空间,对应JVM内存配置中的-Xms和-Xmx等)
java栈:当JVM创建一个新线程时,都会产生线程计数器(PC Register)和栈。每一次方法调用都会产生栈帧,栈帧中包含局部变量区和操作数栈。(JVM中栈被线程独享,对应JVM内存配置中的-Xss)
线程计数器:每个线程拥有自己的程序计数器,它指向下一条指令。当线程调用本地方法的时候, 它为undefined。
本地方法栈:当JVM线程调用了本地方法, 则会跳入本地方法栈。本地方法返回后可能再次跳回java方法栈。(JVM支持本地方法调用,故JVM占用的OS内存可能会超出JVM堆内存大小设置,甚至会产生本地内存泄漏)
那么内存是如何分配的,首先了解Java数据类型:Java中有两种数据类型:基本数据类型、包装类数据类型。
基本数据类型一般存放在常量池里:如 int a = 4;首先会在常量池中查找是否存在4这个值的字面值,如果无则在常量池中创建,并返回地址,并将a指向这个地址;
包装类数据类型一般通过new来创建,则肯定存在于堆中,并且每次创建都产生新的地址,分配新的内存空间。优点是灵活,缺点是要占用更多的cpu时间。这也是为何基本数据类型一般都存放在常量池中的原因。
String属于包装类,但是一个特殊的包装类。因为其底层是一个数组构成,是一个不可变数组。其特殊在于即可以用String str = new String(“abc”);的形式来创建,也可以用String str = “abc”;的形式来创建。由于String底层是一个不可变数组,所以Jvm把通过String str = “abc”方式创建的“abc”字符串当成一个常量存放在常量池中,若有新的String str2 = “abc“,则返回的是第一次创建该常量字符串的地址。所以若调用System.out.println(str1==str2); //结果是true
关于基本类型变量,Java中变量存放的是地址的引用,这时候你肯定有疑问了,按照上面分析,若我们定义 int a=4;int b = 4;那么a、b地址应该是一样的,确实也是一样的。那么我我们执行b = 5;会不会改变a的值?答案肯定是不会的,因为执行b = 5;的时候,jvm会从常量池中查找5字面值,并返回5所在的地址赋值给b,这时候b的地址已经改变了,就不会影响a地址的值。若是包装类的变量呢?情况是怎样的?
关于包装类变量,由于包装类变量都是通过new来创建的,所以不能按照基本类型变量的创建方式来做比较,我们将变量作为方法体的入参来解释为何Java中的变量是地址的引用。当我们执行MyClass my = new MyClass;首先在堆中分配MyClass所需要的内存空间并返回改地址空间的地址,并在栈中存放该地址指针,my就指向该栈中的地址指针,这时候my的值就是地址的值。当我们将my作为参数时,如例子
//my的值是地址
MyClass my = new MyClass;
my.value = "no chang";
changValue(my)
//输出后,my.value的值将改变成changed
System.out.println(my.value);
public void changValue(MyClass my)
{
//my的值是地址
my.value = "changed";
}
根据例子可以知道,my的值将会改变,这也解释了,为何Java中参数的传递是值的传递,而不是引用传递,因为Java中的变量都是引用就没有引用传递这个概念了。则就解释了Java变量是地址的引用,存的是地址指针的值。
本篇我们了解了Java中内存是如何分配的。下一篇,将分析Java中内存的回收。