1 概述
在 java中,对象实例都是在堆上创建。
方法区,又叫静态成员区,所有的 1 类(class),2 静态变量(static变量),3 静态方法,4 常量,5 成员方法
都存储在方法区
方法区和栈区,被所有线程共享,是不安全的
- gc机制由 jvm 提供,用来清理需要清除的对象,回收
堆内存
- gc由
垃圾回收器守护线程
执行 - 从内存回收一个对象之前,会调用对象的
finalize()方法
- 开发者不能强制 jvm 执行 gc,gc的触发机制由 jvm 依据堆内存的大小来决定;但是可以通过不同的引用类来辅助垃圾回收器工作(弱引用或软引用)
- system.gc() 和 runtime.gc() 会向 jvm 发送执行 gc的请求,但是 jvm 不保证一定会执行 gc
2 总结
- 为了分代垃圾回收,java堆内存分为3代:新生代,老年代,永久代
- 新的对象实例会优先分配在新生代,在经历几次 minor gc后(默认15次),还存活的会被迁移至老年代(某些大对象会直接在老年代分配)
- 永久代是否执行gc,取决于采用的 jvm
- minor gc 发生在新生代,当 eden区没有足够空间时,会发起一次 minor gc,将 eden区中的存活对象迁移至 survivor区,major gc 发生在老年代,当 升到老年代的对象,大于老年代剩余空间时,会发生 major gc
- 发生 major gc时,用户线程会暂停,会降低系统性能和吞吐量
- jvm的参数-xmx和-xms用来设置java堆内存的初始大小和最大值。依据个人经验这个值的比例最好是1:1或者1:1.5。比如,你可以将-xmx和-xms都设为1gb,或者-xmx和-xms设为1.2gb和1.8gb
3 堆内存的划分
java中对象都在堆上创建。为了gc,堆内存分为三个部分,也可以说三代,分别称为新生代,老年代和永久代。其中新生代又进一步分为eden区,survivor 1区和survivor 2区(如下图)。新创建的对象会分配在eden区,在经历一次minor gc后会被移到survivor 1区,再经历一次minor gc后会被移到survivor 2区,直到升至老年代,需要注意的是,一些大对象(长字符串或数组)可能会直接存放到老年代
eden与两个survivor的内存大小比例大概是 8:1:1
永久代有一些特殊,它用来存储类的元信息。对于gc是否发生在永久代有许多不同的看法,在我看来这取决于采用的jvm。大家可以通过创建大量的字符串来观察是发生了gc还是抛出了outofmemoryerror
4 gc算法
4.1 标记清除算法
分为
标记
清除
两个阶段
首先标记出所有需要回收的对象,在标记完成后,统一回收所有被标记的对象
该算法的缺点是:效率不高,并且会产生不连续的碎片
4.2 复制算法
把内存空间划为两个区域,每次只使用其中一个区域
垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。
算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去后还能进行相应的内存整理,不会出现"碎片"问题
优点:实现简单,运行高效。 缺点:会浪费一定的内存
一般新生代才用这种算法
4.3 标记整理算法
标记阶段与标记清除算法一样,但后续并不是直接对可回收的对象进行清理,而是
让所有存活对象都向一端移动,然后清理
优点:不会造成内存碎片