1、堆
1.1 概述
1.1.1 堆空间结构
1.1.2 堆空间工作机制
新创建的对象会放在Eden区
当Eden区中已使用的空间达到一定比例,会触发Minor GC
每一次在Minor GC中没有被清理掉的对象就成了幸存者。
幸存者对象会被转移到幸存者区
幸存者区分成from区和to区
from区快满的时候,会将仍然在使用的对象转移到to区
然后from和to这两个指针彼此交换位置
口诀:复制必交换,谁空谁为to
如果一个对象,经历15次GC仍然幸存,那么它将会被转移到老年代
如果幸存者区已经满了,即使某个对象尚不到15次,仍然会被移动到老年代
最终效果:
Eden区主要是生命周期很短的对象来来往往
老年代主要是生命周期很长的对象。例如:IOC容器对象、线程池对象、数据库连接池对象等等。
幸存者区作为两者之间的过度地带
关于永久代
从理论上来说属于堆
从具体实现上来说不属于堆
1.1.3堆、栈、方法区之间关系
1.1.4 常驻Web对象存活时间
生产环境下:
ServletContext存活时间:
时间单位:月、年
HttpSession存活时间:
时间单位:分钟、小时
HttpServletRequest存活时间:服务器端接收到请求~服务器提交响应
时间单位:秒或毫秒
HttpServletResponse存活时间:服务器端接收到请求~服务器给客户端返回了响应数据
时间单位:秒或毫秒
2.GC
为什么要有垃圾回收?
- 线程私有空间:无需由系统来执行GC。因为线程结束,释放自己刚才使用的空间即可,不影响其它线程。
- 线程共享空间:任何一个线程结束时,都无法确定刚才使用的空间是不是还有别的线程在使用。所以不能因为线程结束而释放空间,必须在系统层面统一垃圾回收。GC的基本原则:
- 频繁收集新生代
- 较少收集老年代
- 基本不动元空间
2.1 标记垃圾对象
垃圾对象的标准:不再被引用的对象。下面这两种方法都是要把这样的对象找出来。
1、引用计数法(不采用)
(1)本意
- 在对象内部记录被引用次数
- 被引用一次,计数器+1
- 引用解除一个,计数器-1
- 计数器归零则表示该对象变成垃圾
(2)问题
循环引用问题,会导致计数器无法归零。
2、GC Roots可达性分析
核心原理:判断一个对象,是否存在从堆外到堆内的引用
因为我们写Java代码时,是不可能直接访问到堆内对象的,要么是通过方法里面局部变量,要么通过static修饰的常量、类变量。局部变量、类变量、常量这些都在堆外。
3、GC Root对象
GC Root对象:就是作为根节点出发,顺着引用路径一直查找到堆空间内,找到堆空间中的对象。
虚拟机栈(Java Stack栈帧中的局部变量区,也叫局部变量表)中引用的对象
本地方法栈(Native Method Stack)中的局部变量
方法区中的类变量、常量引用的对象(说白了就是用static修饰的成员变量指向的对象)
2.2 垃圾回收算法
1、回收范围
JVM在进行GC时,并非每次都对上面三个内存区域一起回收的,大部分时候回收的都是指新生代。
minor GC:只针对新生代区域的GC,指发生在新生代的垃圾收集动作。
因为大多数Java对象存活率都不高,所以Minor GC非常频繁,一般回收速度也比较快。
major
对老年代的垃圾回收
Major GC的速度一般会比Minor GC慢10倍以上,STW的时间更长
如果Major GC后,内存还不足,就报OOM了。
full GC:清理范围包括新生代、老年代和方法区,非常慢。
2、GC年龄
新生代的对象每经历一次GC,只要它还活着,GC年龄就会+1.当GC年龄达到15的时候,该对象就会转移到老年代。
对象在新生代的最大GC年龄可以设置:
-XX:MaxTenuringThreshold
从JDK8开始,64位虚拟机的最大GC年龄不能超过15。
2.2.1 基本算法
1、引用计数法(不采用)
优点:
实时性较高,不需要等到内存不够时才回收
垃圾回收时不用挂起整个程序,不影响程序正常运行
缺点:
回收时不移动对象,所以会造成内存碎片问题
不能解决对象间的循环引用问题(致命问题,一票否决)
小结:
正是由于引用计数法不能解决对象间的循环引用问题,所以事实上并没有哪一款JVM产品采用这个机制。
2、标记清除法
它的做法是当堆中的有效内存空间被耗尽时,就会暂停、挂起整个程序(也被称为stop the world),然后进行两项工作,第一项则是标记,第二项则是清除。
标记:标记的过程其实就是从根对象开始变量所以得对象,然后将所有存活的对象标记为可达对象。
清除:清除的过程中将遍历堆中的所有对象,将没有标记的对象全部清除掉。
小结:
优点:实现简单
缺点:
效率低,因为标记和清除两个动作都有遍历所有的对象
垃圾收集后可能会造成大量·的内存碎片
垃圾回收时会造成应用程序暂停
3、标记压缩法
既然教标记压缩算法,那么它也分为两个阶段,一个时标记(mark),一个时压缩(compact)。所谓压缩就是把存在碎片的空间连起来。
标记压缩算法是在标记清除算法的基础之上,做了优化改进的算法。和标记清除算法一样,也是从根节点开始,对对象的引用进行标记,在清理阶段,并不是简单的清理未标记的对象,而是将存活的对象移动到内存的一端,然后清理边界以外的垃圾,从而解决了碎片化的问题。
标记:标记的过程其实就是从根对象开始遍历所以对象,然后将所有存活的对象标记为可达的对象。
压缩:移动所以的可达对象到堆内存的同一个区域中,使它们紧凑的排列在一起,从而将所有非可达对象释放出来的空闲内存都集中在一起,通过这样的方式来达到减少内存碎片的目的。
小结
优点:标记压缩算法是对标记清除算法的优化,解决了碎片化的问题
缺点:还是效率问题,在标记清除算法上又多加了一步,效率就更低了