手动内存管理
手动内存管理(Manual Mememory Management)是一种通过代码显式的分配内存并主动回收的方法,常见于早期的c/c++,通常会因为忘记回收而导致内存泄漏。
例:
int send_request() {
size_t n = read_size();
int *elements = malloc(n * sizeof(int));
if(read_elements(n, elements) < n) {
// elements not freed! return ‐1;
}
// … free(elements) return 0;
}
共享指针
引用计数(Reference Counting):一个在堆上创建的对象,记录有多少个指针指向它。
通过引用计数,针对每一个对象,只需要记住被引用的次数,当引用次数变为0,这个对象就可以被回收。c++的共享指针就是一个使用了引用计数的例子。
例:
int send_request() {
size_t n = read_size(); shared_ptr<vector<int>> elements = make_shared<vector<int>>();
if(read_elements(n, elements) < n) {
return ‐1;
}
return 0;
}
shared_pre 被用来追踪引用的数量。作为参数传递时这个数字加1,在离开作用域时这个数字减1,当引用计数为0时,shared_pre 自动删除底层的vector。
自动管理内存
上面举的两个例子都是我们程序员主动执行代码回收的,都已经过时了,接下来将引入垃圾收集器的概念,自动进行收集垃圾。
早期的垃圾收集器使用了两种方法:引用计数(Reference Counting)和标记-清除(Mark and Sweep)。但是它们都有各自的缺陷。
先说引用计数,它会导致一种叫循环引用的现象。
图例:
图中绿色的云(GC ROOTS)表示程序正在使用的对象,蓝色的圈是引用到的对象,灰色的对象就是要回收的垃圾,因为从技术上讲被标记为蓝色是因为这些可能是当前正在执行的方法中的局部变量,或者是静态变量,被标记为灰色是因为各个作用域都不在引用,但是这些垃圾却各自引用彼此,形成了引用闭环,导致引用计数一直大于零(如红色部分),导致无法回收,引起内存泄漏。
再说标记-清除,在垃圾收集过程中,它需要暂停应用的所有线程,如果不暂停就会使对象的引用关系不断变化导致无法统计对象是否零引用。这种情况叫STW(Stop The World pause,全线暂停)。而且清除后会导致内存碎片而导致内存利用率变低(因为分配的内存是要连续的)。
垃圾收集根元素:GC ROOTS(上面那个绿色的云)在jvm的具体化,包括但不限于 局部变量、活动线程、静态域、JNI引用(JNI是Java Native Interface的简写)。
JVM使用标记-清除算法来跟踪所有的可达对象(就是存活对象,被垃圾收集根引用即视为存活),确保所有不可达对象占用的内存能够释放。
标记(marking):遍历所有的可达对象,并在本地内存(native)中分门别类记下.
清除(sweeping):将视为不可达的对象清除内存。