ref: http://www.ccs.neu.edu/home/cbw/5600/slides/8_Free_Space_and_GC.pptx
这篇文章主要是复习Java虚拟机(JVM)内存的简要介绍。在 JVM中,内存分别供程序代码(Program Code),栈(Stack),未使用的内存(Free Memory)及堆(Heap)使用,如下图。所以接下来,我们会依序介绍 JVM在进行内存管理时,需要使用的几个数据结构。
Stacks
在JVM中的 Stack,有 Java 方法栈(method stack)及运算栈(Operand stack),分别用来管理程序执行顺序、管理变量还有执行运算的结果。比如说当执行下面代码的时候:
public static void main(String[] args) {在 Java 内存中其实是这样的:
int i = 15;
foo(i); // 假设这里 PC(Program Counter) = 14
}
private static void foo(int i) {
int k = 6;
bbb(k); // PC = 216
}
private static void bbb(int m) { // PC = 320
k++;
}
一个方法在Method Stack需要 Frame来保存方法的Program Counter(PC) -保存著执行的内存位置及本地变量(Local Variables),而遇到程序内有子程序时,便另外产生一个Frame,并「压进(Push)」 Method Stack 中。直到需要执行时 PC自动加一,执行该内存地址的程序。另外,在这里要顺便介绍一下 Call-By-Value,它指的是目前的变量被当作方法参数传进被呼叫的方法里。简单的说,当使用 Call-By-Value的时候,如果参数是 primitive variable,那方法执行后,父方法的中的变量值依然不变;如果传进去的是一个对象,则对象内的值可以改变,所指向的内存位置则不能改变。例子如下:
public static void main(String[] args) {
int i = 15;
foo(i);
int[] j = new int[2];
j[0] = 15;
System.out.println("Before calling foo: " + Arrays.toString(j));
foo(j);
System.out.println("After calling foo: " + Arrays.toString(j));
System.out.println("===========================================");
System.out.println("Before calling foo2: " + Arrays.toString(j));
foo2(j);
System.out.println("After calling foo2: " + Arrays.toString(j));
}
private static void foo2(int[] array) { // 此方法试图改变所指向的内存位置
array = new int[3];
array[0] = 1;
array[1] = 2;
array[2] = 3;
}
private static void foo(int[] array) { // 此方法改变数组对象的值
array[0] = 0;
}
运算桟(Operand Stack)负责的是一些数学运算功能,在这里用一简图代过:
Heap 内存管理
Java 程序里用 new 宣告产生的对象所使用的内存,便是从 Heap 中取得
int[] lists = new int[] // 所产生的对象在 Heap 内存区中在JVM规范中要求 Heap 应要能快速的分配内存给新的对象,但没有明确规范要使用何种数据结构。free list 是维持可用内存常见的数据结构,它是链表(Linked List),分配、释放内存时,分别如下简图所示。
这种方式,便会产生内存碎片(Fragmentation)-即未被使用的内存片段分散在内存中,这样会导致使用内存时,计算机需要花费时间找到大小合适的区块或是一堆小块可用内存,但是却没一个符合我们需求的情况。
内存碎片(Fragmentation)
内存碎片有两种,一种是Internal Fragmentation指的是程序申请的内存大於实际的需求,比如说申请了大小为1000的数组,但是只使用了1000里面的其中5个。另一种为 External Fragmentation 指的是可用的内存散布在已使用的分存之间。
Heap的内存调用算法
best-fit algorithm:遍历找到大小最合适的可用内存块,理论上这算法可以帮我们减少碎片化,但是实际上却产生了更严重的碎片化问题, 因为碎片变得更细小了。
first-fit algorithm:使用 Free List 中第一个可用的内存块。优点是快,但是容易在 free list 前方产生许多内存碎片,未来在搜寻的时候便会降低速度。
next-fit algorithm:和 first-fit 差不多,但它不是从头开始找,它是从上次找到可用内存的地方开始往下一个找。next-fit 使碎片分布均匀,这样反而降低了调用大区块内存的效率。
worst-fit algorithm:使用最大的可用内存块。worst-fit 主要是用来解决 next-fit不容易找到大块内存的问题。