1、JAVA的内存管理
在JAVA中我将内存简单分为了以下4类:
(1)栈内存空间 用来存储所有对象(或者是引用的堆的内存空间的地址)
(2)堆内存空间 存储每个对象的属性
(3)全局数据区 存储static属性的变量或者方法
(4)全局代码区 存储所有的方法
也有人将内存区域这样划分:栈区、堆区、方法区、本地方法栈、程序计数器
我们举个例子,比如有下面这样一段代码
class People{
String name;
}
public class Test {
public static void main(String[] args) {
People people=new People();
}
}
有这样的对应关系:people(栈内存) -----> name = "null"(堆内存)
这是一个对象的,如果有两个对象的话呢?
public class Test {
public static void main(String[] args) {
People people1=new People();
People people2=new People();
people1.name = "wang";
People2.name = "zhang";
}
}
那么就对应关系就应该是:
people1(栈内存) -----> name = "wang"(堆内存)
people2(栈内存) -----> name = "zhang"(堆内存)
那么我们再来看一个复杂一点的,对象引用传递
public class Test {
public static void main(String[] args) {
People people1 = new People();
People people2 = new People();
people1.name = "wang";
people2.name = "zhang";
people2 = people1;
}
}
在people2 = people1之前,栈内存和堆内存的关系依然是
people1(栈内存) -----> name = "wang"(堆内存)
people2(栈内存) -----> name = "zhang"(堆内存)
执行了people2 = people1之后,则
people2(栈内存) -----> name = "zhang"(堆内存)断开
重新指向的对应空间是
people2(栈内存) -----> name = "wang"(堆内存)
那么最后的内存空间的情况为
people1(栈内存)
-----> name = "wang"(堆内存)
那么name = "zhang"(堆内存)就没有用了,这就产生了垃圾空间,等待垃圾回收机制进行回收
2、GC机制
GC(Garbage Collection 垃圾回收),JAVA中实现了垃圾内存的自动回收,就不需要像C++那样让程序员用delete方法来手动释放内存空间,我们先从finalize函数说起。
finalize用于回收非new申请的内存区域,系统会自动调用这个函数,每当GC准备释放资源的时候,都会调用finalize,只有当下一次GC发生时,才会真正的释放资源。
垃圾回收的两种方法:停止-拷贝与标记-清除
停止-拷贝:系统中分配了两个堆,对老堆中的对象进行一次扫描,当老堆中的对象处于活动状态时,将会被拷贝到新堆中,并且紧凑排列,让其空间是连续的。不活跃的对象(垃圾)则被留在老堆中,被清除掉。
标记-清除:将整个空间扫描一遍,当找到活动的对象的时候,就进行标记,扫描完后清除没有被标记的对象。
两者各有优劣,停止-拷贝方法当大部分对象都是活跃状态的时候,需要拷贝的东西就比较多,效率不高,但因为每次拷贝到新堆的时候都会对内存块进行一个紧密排列的处理,合理利用了内存空间。标记-清除方法在清除不需要的对象时,会留出一些琐碎的内存,由于这些内存没有被利用到,所以可能就会被浪费。
基于以上情况,垃圾回收机制在这两种方法之间进行相互转换,构造了一个自适应的回收机制。当大多数对象处于活跃状态而导致停止-拷贝变得低效的时候,JVM就自动转换到标记-清除方式。相反,当JVM在执行标记-清除的时候,发现内存中琐碎空间较多,又会自动转换到停止-拷贝方式。这种自适应方式一方面既能有效的清除垃圾,又能合理的分配利用内存空间。
3、分代回收
对象的整个历程会有年轻代和年老代的划分,对象刚被创建时,就属于年轻代,年轻代有三个区域:eden区,两个存活区(Survivor0、Survivor1),内存的分配过程如下:
(1)大部分对象刚创建的时候,就被分配到eden区,当eden区满的时候,就执行垃圾清除,存活的对象将被拷贝到Survivor0区域
(2)每当对象被创建,就执行(1)的操作,将对象拷贝到Survivor0
(3)直到当Survivor0满的时候,将仍然存活的对象拷贝到Survivor1,这时就清空了Survivor0,以后每次eden满的时候就直接将对象拷贝到Survivor1,而不是Survivor0
(4)当Survivor1也满的时候,就拷贝到Survivor0,如此在Survivor0和Survivor1之间反复(必须保证两者中有一个是空的)
(5)在两者之间反复几次后,仍然存活的对象就被拷贝到年老代,当年老代的区域也满的时候,就执行上面所说的标记-清除方式,当然,年轻代的方式自然就是停止-复制
以上就是通用的GC分代算法。