持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第18天,点击查看活动详情
前言 Stop The World
这一小节我们聊一下Java的GC机制,java的GC有一个大前提-Stop The World
当垃圾回收开始清理资源时,其余的所有线程都会被停止.所以,我们要做的就是尽可能地让它执行的时间变短.
如果清理的时间过长,在我们的应用程序中就能感觉到明显的卡顿.
一、垃圾回收
Java语言中引入了垃圾回收机制,使得Java程序员在编写程序时不需要考虑内存管理.垃圾回收可以有效地防止内存泄露,有效地使用空闲的内存.
而垃圾回收算法主要做以下两件事
- 1、发现无用信息对象
- 2、回收被无用对象占用的内存空间,使该空间可被程序再次使用.
二、常用的垃圾回收算法
2.1 标记回收算法
堆中每个对象实例都有一个引用计数。当一个对象被创建时,且将该对象实例分配给一个变量,该变量计数设置为1.
当任何其他变量被赋值为这个对象的引用时,计数加1.
但当一个对象实例的某个引用超过了生命周期或者被设置为一个新值时,对象实例的引用计数器减一。
任何引用计数器为0的对象实例可以被当做垃圾手机.
缺点
无法检测出循环引用
三、GC ROOT
由于这种标记计数法无法正确地标识对象的使用状态,于是有了GC Root根可达性的状态判断
- 1、通过System Class Loader或者Boot Class Loader加载的class对象,通过自定义类加载器加载的class不一定是GC Root
- 2、处于激活状态的线程
- 3、栈中的对象
- 4、JNI栈中的对象
- 5、JNI中的全局对象
- 6、正在被用于同步的各种锁对象
- 7、JVM自身持有的对象,比如系统类加载器等.
如果一个对象对GC roots对象没有任何引用,没有形成引用链,那么该对象等待GC回收.
3.1 标记-回收算法
从GC Root进行遍历,把可达对象进行标记,剩下那些不可达的进行回收.
缺点
这种方式需要中断其他线程,并且可能产生内存碎片.
3.2 复制算法
把内存区域分成两块,每次使用一块.GC的时候把一块中的内容移动到另一块中,原始内存中的对象就可以被回收了.
缺点
浪费了一半的内存,并且对于存活时间长的对象说这种移动整理浪费时间和空间
3.3 标记压缩算法
和标记回收差不多,但是在回收的时候会对可达对象进行整理,将其压缩到内存的一段,避免产生内存碎片
缺点
效率较低,而且多了一个整理工作.
四、 分代算法
将内存区域分代,对不同的代使用不同的回收算法,通常分为新生代、老年代和永久代.
4.1 新生代
新生代一般包含三个区域,Eden区和两个Survivor区,新生代一般采用复制算法
- 1、首先,所有新生成的对象都是放在年轻代的Eden分区的,初始状态下两个Survivor分区都是空的.年轻代的目标是尽可能快速地收集掉那些生命周期短的对象.
- 2、当Eden区满的时候,小垃圾收集就会被触发
- 3、当Eden分区进行清理的时候,会把引用对象移动到第一个Survivor分区,无引用的对象删除.
- 4、在下一个小垃圾收集的时候,在Eden分区中:无引用的对象被删除,引用对象被移动到另外一个Survivor分区(s1).
此外,从上次小垃圾收集过程中第一个Survivor分区(S0)移动过来的对象年龄增加,然后被移动到S1.
当所有的幸存对象移动到S1以后,S0和Eden区都会被清理.
此时S分区存储的对象年龄可以各不相同.
4.2 老年代
当再一轮垃圾收集进行时,如果此时S分区存储的年龄对象达到了一定的阈值(例如:8),jvm就会把它们分配到老年代.
标记清除or标记整理
老年代中因为对象存活率高。没有额外空间对它进行分配担保,所以必须使用标记清除或标记整理算法。
4.3 总结
可以看到,因为新生代中每次new出来的对象特别多,而且只有少量存活.
所以选择复制算法,同时记录存活的代数.当新生代存活代数达到一定阈值后就会被移入老年代.
因为它们的存活时间较长,复制算法会浪费时间和空间,所以使用标记清理or标记整理算法.
当老年代此时都无法清理出空间时,此时就产生了内存爆炸.
五、Java引用类型
当我们在使用对象时,手动将引用对象的reference域置为null,这样可以加速GC的工作
- 1、StrongReference--强引用:正常的对象,一直不会清理,直到爆炸.
- 2、SoftReference--软引用:内存不够的时候,遇到了就清了
- 3、WeakReference--弱引用:只要遇到了就清了(可能清了好几次,都没遇到)
- 4、PhantomReference--虚顾名思义就是没有的意思,建立虚引用之后通过get方法返回结果始终为null。
六、应用内存优化建议
分析了这么多关于对象的回收算法,我们需要知道的是Java为了提升对象的回收效率采用了分代回收算法进行垃圾回收.
而为了提高应用性能,我们在应用程序中也可以形成良好的开发习惯让GC运行地更高效.
- 1、尽早释放无用对象的引用,尽早将不用的引用对象赋为null,加速GC的工作
- 2、少使用finalize函数。finalize函数是Java提供给程序员一个释放对象或资源的机会。但是,它会加大GC的工作量,因此尽量少采用finalize方式回收资源;
- 3、如果需要使用经常使用的图片,可以使用SoftReference类型。它可以尽可能将图片保存在内存中,供程序调用,而不引起OutOfMemory;
- 4、能用基本类型如int,long,就不用Integer,Long对象。基本类型变量占用的内存资源比相应对象占用的少得多;
- 5、尽量少用静态对象变量,静态变量属于全局变量,不会被GC回收,它们会一直占用内存
- 6、注意集合数据类型,包括数组,树,图,链表等数据结构,这些数据结构对GC来说,回收更为复杂,所以使用结束应立即置为null,不要等堆在一起。另外,注意一些全局的变量,以及一些静态变量。这些变量往往容易引起悬挂对象(dangling reference),造成内存浪费;
- 7、尽量使用StringBuffer,而不用String来累加字符串