两大主要内存
-
栈内存
栈主要用于存储方法参数、局部变量和对象的引用变量。每个线程都有自己的栈内存,当方法执行完成后,栈内存中的数据会被自动释放。
栈的内存空间在编译时确定,是一段连续的空间,是固定的,在内存随着线程的结束而自动释放。
栈内存归属于单个线程,每个线程都会有一个栈内存。
-
堆内存
堆用于管理对象和数组,堆内存由JVM管理和垃圾回收。
堆内存是动态分配的,不连续的物理空间,线程结束后由GC判断是否进行回收。
堆内存中的对象对所有线程可见。堆内存中的对象可以被所有线程访问。
关系比对表:
特性 | 栈内存(Stack Memory) | 堆内存(Heap Memory) |
---|---|---|
管理方式 | 自动管理(方法结束时自动释放) | 由垃圾回收机制管理 |
存储内容 | 方法调用信息、局部变量、方法参数 | 对象实例和数组 |
访问速度 | 快 | 相对较慢 |
线程安全性 | 线程私有,天然线程安全 | 线程共享,需要同步机制保证线程安全 |
内存分配方式 | LIFO(后进先出) | 动态分配 |
生命周期 | 短,方法执行完毕即释放 | 长,对象不再被引用时才回收 |
垃圾回收机制GC
判断方法
-
引用计数法
通过维护每个对象的引用次数来管理内存,当引用次数为零时回收对象。
引用计数法的工作原理
-
初始化:每个对象都有一个引用计数,当对象被创建时,引用计数初始化为1。
-
增加计数:当一个新的引用指向该对象时,引用计数加1。
-
减少计数:当一个引用不再指向该对象时,引用计数减1。
-
回收对象:当对象的引用计数变为零时,系统回收该对象的内存。
引用计数法的优点
-
简单直观:实现和理解都比较简单。
-
即时回收:当对象引用计数变为零时,内存可以立即被回收,而不需要等待GC的周期性运行。
引用计数法的缺点
-
循环引用问题:当两个或多个对象相互引用时,即使它们不再被其他对象引用,它们的引用计数也不会变为零,导致内存泄漏。
-
性能开销:每次增加或减少引用时都需要更新引用计数,增加了系统的开销。
-
-
可达性分析法
可达性分析算法通过一组称为“根集合”(GC Roots)的引用,递归地判断哪些对象是可达的。可达的对象被视为活跃对象,不可达的对象则可以被回收。
GC Roots包括:
-
栈中引用的对象:如局部变量、方法参数等。
-
方法区中的静态字段引用的对象。
-
方法区中的常量引用的对象。
-
本地方法栈中JNI(Java Native Interface)引用的对象。
可达性分析步骤:
-
从GC Roots开始遍历对象图。
-
标记所有可达对象。
-
未被标记的对象即为不可达对象,可以被回收。
回收算法
-
标记-清除算法(Mark and Sweep)
步骤:
-
标记阶段:从GC Roots开始,标记所有可达对象。
-
清除阶段:扫描堆中的所有对象,回收所有未被标记的对象。
-
-
复制算法(Copying)
步骤:
-
将堆内存分为两个等大的区域:From和To。
-
每次只使用一个区域(如From)。
-
当活动区域用满时,标记并复制所有存活对象到另一个区域(如To)。
-
清空旧区域,交换角色(From和To)。
-
-
标记-整理算法(Mark-Compact)
步骤:
-
标记阶段:标记所有可达对象。
-
整理阶段:将所有存活对象向一端移动,清理无效对象,释放内存。
-
-
标记-整理算法(Mark-Compact)
理念:根据对象的存活时间将堆内存分为几代(如新生代和老年代),分别进行垃圾回收。
内存区域:
-
新生代(Young Generation):包括Eden区和两个Survivor区。新创建的对象通常分配在Eden区。当Eden区满时,触发一次Minor GC,将存活对象复制到Survivor区。
-
老年代(Old Generation):存放生命周期较长的对象。当老年代满时,触发Major GC(Full GC),对整个堆进行垃圾回收。
-
永久代(Permanent Generation)(Java 8之前):存放类信息、常量、静态变量等。Java 8之后被元空间(Metaspace)取代。
-