1.引言
在Java中,它的内存管理包括两方面:内存分配(创建Java对象的时候)和内存回收,这两方面工作都是由JVM自动完成的,降低了Java程序员的学习难度,避免了像C/C++直接操作内存的危险。但是,也正因为内存管理完全由JVM负责,所以也使Java很多程序员不再关心内存分配,导致很多程序低效,耗内存。因此就有了Java程序员到最后应该去了解JVM,才能写出更高效,充分利用有限的内存的程序。
2. 正题
内存泄露导致的原因:对象已经没有被应用程序使用,但是垃圾回收器没办法移除它们,因为还在被引用着。 还有一种说法是:没有直接或间接被Gcroot集合引用 的对象都被称作垃圾内存。其中能作为Gcroot对象的有:
(1). 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
(2). 方法区中的类静态属性引用的对象。
(3). 方法区中常量引用的对象。
(4). 本地方法栈中JNI(Native方法)引用的对象。
2.1 算法分析-引用计数法
堆中每个对象都有一块内存用来保存该对象被引用的次数。当任何其他变量赋值为这个对象的引用时,计数加1 ,(a=b ,则b引用的对象实例计数器+1)但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减1,任何引用计数器为0 的对象实例可以当做垃圾收集。
优点:
1)实时性 无需等到内存不够的时候,才开始回收,运行时根据对象的计数器是否为0,就可以直接回收。
2)在垃圾回收过程中,应用无需挂起。如果申请内存时,内存不足,则立刻报outofmember 错误.
3)更新对象的计数器时,只是影响到该对象,不会扫描全部对象
缺点:
1)每次对象被引用时,都需要去更新计数器,有一点时间开销。另外无法解决循环引用问题。例如:
要想清除:必须先调用a.b=null;b.a=null;
2.2标记-清除(Mark-Sweep)算法
首先标记出所有等待被回收的对象。然后统一清除。
缺点:产生的内存碎片太多,效率低下。
2.3 复制(Copying)算法
1)新生对象被分配到A块中未使用的内存当中。当A块的内存用完了, 把A块的存活对象复制到B块。
2)清理A块所有对象。
4)新生对象被分配到B块中未使用的内存当中。当B块的内存用完了, 把B块的存活对象复制到A块。
4)清理B块所有对象。
循环1。
缺点:内存缩小为了原来的一半,这样代价太高了
2.4 优化的复制算法
1)Eden+S0可分配新生对象;
2)对Eden+S0进行垃圾收集,存活对象复制到S1。清理Eden+S0。一次新生代GC结束。
3)Eden+S1可分配新生对象;
4)对Eden+S1进行垃圾收集,存活对象复制到S0。清理Eden+S1。二次新生代GC结束。
循环1。
2.5 标记-整理(Mark-Compact)算法
复制算法在对象存活率较高的场景下要进行大量的复制操作,效率很低。万一对象100%存活,那么需要有额外的空间进行分配担保。老年代都是不易被回收的对象,对象存活率高,因此一般不能直接选用复制算法。根据老年代的特点,有人提出了另外一种标记-整理算法,过程与标记-清除算法一样,不过不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉边界以外的内存。标记-整理算法的工作过程如图:
2.6 分代回收算法
分别是:年轻代,年老代,和永久代。
年轻代:“标记-复制算法”,eden:survivo:survivo=8:1:1
老年代:"标记-整理(Mark-Compact)算法",将大部分的依旧在引用的对象,整理,减少代码碎片。
永久代:
用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或
者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新
增的类。持久代大小通过-XX:MaxPermSize=进行设置。