内存对象模型
每个对象存储在内存中一段连续的空间中,如果是引用,则存储它所指向的对象的内存地址。
对象在堆(heap)中分配内存,对象引用指向被引用对象在堆中的起始地址。
非基本数据类型的变量等价于对象引用。
每个对象可包含一组变量,每个变量可指向其他对象的引用。
对象引用只指向一个其他对象,而一个对象可以被多个其他对象引用。
三种内存分配方式
内存分配分为静态内存分配和动态内存分配。静态内存分配在编译阶段就已经确定好了内存分配;动态内存分配在运行时动态分配内存,建立新的内存对象。动态又分为基于堆和基于栈的分配。
这三种分配方式的差异在于如何与何时在程序对象与内存对象之间建立联系。
静态内存分配 Static mode
在将程序load进内存的时候或开始执行的时候,确定所有对象的分配。
不支持递归,不支持动态创建的可变长的复杂数据类型。
基于栈的动态内存分配 Stack-based mode
栈:用来存储方法调用以及方法执行中的局部数据。遵循后进先出原则。
无法支持复杂数据类型。
基于堆的动态内存分配 Heap-based mode
堆:自由模式的内存管理,动态分配,可管理复杂的动态数据结构。
在一块内存里分为多个小块,每块包含一个对象,或者未被占用。
代码中的一个变量可以在不同时间被关联到不同的内存对象上,无法在编译阶段确定。内存对象也可以进一步指向其他对象。
Java内存模型
Java内存管理模型:JVM如何管理内存。关注JVM如何进行基于“堆”和“栈”的内存管理。
栈模型:线程栈 thread stack
每个线程有自己的栈,管理其局部数据,各栈之间彼此不可见。所有局部的基本数据类型都在栈上创建。
多线程之间传递数据,是通过复制而非引用。
堆模型
所有对象都在堆上创建,即使是局部变量的 object,也是在堆上创建。
堆上创建的对象可被所有线程共享引用。
可访问对象,就可以访问对象内的成员变量。
如果两个线程调用同一个对象上的某个方法,它们分别保留该方法的局部变量的拷贝。
垃圾回收 GC
在静态内存分配模式下,无需进行内存回收,所有都是已确定的。
在栈上进行内存空间回收:按block(某个方法)整体进行。
在堆上进行内存空间回收最复杂,因为无法提前预知某个object是否已经变得无用。
内存回收的首要问题是:如何把可达(reachable)对象与不可达(unreachable)对象分离开来?
活(live)对象是从root可达的对象,死(dead)对象是从root不可达的对象。死对象就是需要回收的“垃圾”。
垃圾回收器根据对象的“活性”(从root的可达性)来决定是否回收该对象的内存。
垃圾回收GC:识别“垃圾”对象,把其占用的内存加以回收。
让程序自己进行垃圾回收,程序员没必要管理内存回收。
GC的四种基本算法
引用计数(Reference counting)
为每个object存储一个计数RC,当有其他reference指向它时,RC++;当其他reference与其断开时,RC--;如果RC==0,则回收它。
优点:简单、计算代价分散,“幽灵时间”短→0。
缺点:不全面(容易漏掉循环引用的对象)、并发支持较弱、占用额外内存空间等。
标记-清除(Mark-Sweep)
为每个object设定状态位(live/dead)并记录,即mark阶段;将标记为dead的对象进行清理,即sweep阶段。
优点:循环垃圾自然收集、在指针操作上没有运行时开销、低耦合等。
缺点:破坏性停顿、耗时长、碎片化等。
标记-整理(Mark-Compact)
为你需要的object添加note;将所有拥有note的object移到garage后面;清除所有garage前面的对象。
优点:解决了Mark-Sweep碎片化的问题、对象在内存中的相对位置保持不变。
缺点:耗时比Mark-Sweep更长。
复制(Copying)
将活对象copy到一个“新鲜”堆中。
与mark-compact的区别在于:不是在同一个区域内进行整理,而是将live对象全部复制到另一个区。
优点:分配代价小、只处理live数据(通常占堆的一小部分)。
缺点:可能有破坏性、需要更大空间、寿命长的数据可能被多次copy。