为什么要了解垃圾收集和内存分配?
答:当需要排查各种内存溢出、内存泄漏问题时,当垃圾收集成为系统达到高并发量的瓶颈时,我们必须对这些技术实施必要的监控和调节。
程序计数器、虚拟机栈、本地方法栈3个区域随着线程而生,随着线程而灭,栈中的栈帧随着方法的进入和退出有条不紊的执行着出栈和入栈的操作。每一个栈帧中分配多少内存基本上是在类结构确定下来时就已知的,因此这几个区域的内存分配和回收都具备确定性,在这几个区域就不需要过多的考虑如何回收的问题,当方法结束或者线程结束时,内存自然就跟着回收了。
Java堆和方法区这两个区域则有着很显著的不确定性:一个接口的多个实现类需要的内存可能会不一样,一个方法所执行的不同条件分支所需要的内存也可能不一样,只有处于运行期间,我们才能知道程序究竟回创建哪些对象,创建多少个对象,这部分的内存的分配和回收是动态的。垃圾收集器所关注的正是这部分内存该如何管理。
对象已死
在堆里面存放着Java世界中几乎所有的对象实例。
垃圾收集器在对堆回收首先要确定的便是对象是否存活。
判断对象是否存活的算法:引用计数法、可达性分析算法
引用计数法
在对象中添加一个引用计数器,每当有地方引用它时,计数器就加一,当引用失效时计数器就减一;任何时刻计数器为零的对象就是不可能再被使用的。
缺点:无法解决循环引用的问题
可达性分析算法
通过一系列的称为“GC Roots”的根对象作为起始节点集,从这些节点开始根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”(Reference Chain),如果某个对象到GC Roots 间没有任何引用链相连,或者说从GC Roots 到这个对象不可达,则证明此对象是不可能再被使用的。
GC Roots 对象
虚拟机栈:各线程被调用的方法堆栈中使用到的参数、局部变量、零时变量等。
方法区:类静态属性引用的对象,比如Java类的引用类型静态变量。
Java虚拟机内部的引用,比如基本数据类型对应的class对象,一些常驻的异常对象(比如NullPointException、OutOfMemoryError)等还有系统类加载器。
所有被同步锁(synchronized)持有的对象
引用
场景:我们希望能描述一类对象:当内存空间还足够时,能保留在内存中,如果内存空间在进行垃圾回收后仍然非常紧张,那就可以抛弃这些对象-很多系统的缓存功能都符合这种场景。
引用:强引用(Strongly Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)。
强引用:只要引用关系存在,垃圾收集器就永远不会回收掉被引用的对象
软引用:在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行二次回收。
弱引用:无论当前内存是否足够,垃圾收集器工作都会回收
虚引用:一个对象是否有虚引用的存在完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被垃圾收集器回收时收到一个系统通知。