jvm运行时内存划分
1.方法区:类的信息(类的全局变量),常量,静态 (1.8前叫永久区)
2.堆内存:new 出来的对象,数组,
3.java栈 :基本数据类型,局部变量(每个线程独立栈),jvm栈帧(栈帧里面的有个局部变量表)
4.本地方法栈:java调用外部语言(c语言),方法使用native(CAS),安卓开发,应用层 java api 底层 C 语言 JNI
5.程序计数器 :是一块较小的内存空间,它可以看作是当前线程所执行的字节码的行号指示器。
(字符串常量池jdk1.8之前放在发放区 之后放在了堆区)
下面来说一下堆内存的划分
堆内存划分为:新生代 和 老年代 默认大小为1:2
新生代又划分为:eden区 和 from区 和 to区 默认大小为2:1:1
如下图所示:
新生代 :刚出生不久的对象 或者 不经常使用的对象
老年代 :存放比较活跃的对象 经常被引用(使用)的对象
什么是垃圾回收机制?
就是不定时去堆内存中清理不可达对象(没有存活的对象)。不可达的对象并不会马上就会直接回收, 垃圾收集器在一个Java程序中的执行是自动的
如何判断对象是否存活?
引用计数法
引用计数法就是如果一个对象没有被任何引用指向,则可视之为垃圾。这种方法的缺点就是不能检测到环的存在。
首先需要声明,至少主流的Java虚拟机里面都没有选用引用计数算法来管理内存。
什么是引用计数算法:给对象中添加一 个引用计数器,每当有一个地方引用它时,计数器值加1;当引用失效时,计数器值减1.任何时刻计数器值为0的对象就是不可能再被使用的。
那为什么主流的Java虚拟机里面都没有选用这种算法呢?
其中最主要的原因是它很难解决对象之间相互循环引用的问题。
根搜索算法(可达性分析算法)
根搜索算法的基本思路就是通过一系列名为”GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
那么问题又来了,如何选取GCRoots对象呢?
在Java语言中,可以作为GCRoots的对象包括下面几种:
(1). 虚拟机栈(栈帧中的局部变量表)中强引用的对象。
(2). 元空间中的类静态属性强引用的对象。
(3). 本地方法栈中JNI(Native方法)强引用的对象。
如下图所示
由上图可以看出 obj8,obj9,obj10 是不可用对象,因为没有任何一个GC Roots与之相连,便是GC Roots不可达的对象,也就是GC需要回收的垃圾对象。
Java 四种引用
强 软 弱 虚
强引用 垃圾回收机制永远不会回收强引用的对象。
软引用:只有当我们堆内存空间不足的时候才会被清理 软引用
弱引用:当我们GC在触发的时候,不管堆内存空间是否足够都会清理弱引用对象。
虚引用:和没有引用是一样,随时都可以回收。
考虑引用避免节约堆内存就可以使用弱引用 比如threadloccal
垃圾回收算法
标记清除算法
该算法有两个阶段。
- 标记阶段:找到所有可访问的对象,做个标记
- 清除阶段:遍历堆,把未被标记的对象回收
缺点 :导致内存空间不连续,空间的利用率不高,容易产生空间碎片化
优点 :效率高
标记复制算法
如果jvm使用了copy算法,一开始就会将可用内存分为两块,a区和b区,
刚开始使用A区,如果A区触发gc了会吧可用的对象放到B区,然后把A的对象给清除,以此类推。
缺点:不过会浪费一半的内存,降低空间的使用率。
优点:可以避免出现空间碎片(内存中不连续的空间)
标记整理算法
标记整理算法在标记清除算法之上解决内存碎片化,因为他会将所有的不可达对象排序到一块,
优点:解决内存碎片化
缺点:它整理的时候,对象对应的内存地址会发生变化,中间会stop-the-world ,暂停所有线程
如下图所示
分代算法
jvm会吧内存分为新生代(eden(2),from(1),to(1))和老年代 ,新对象会放在eden区 ,当Eden区满的时候就触发Minor GC把可用的对象放到from或者to区,然后清空Eden区,并且每次Minor GC(标记复制算法)的时候会来回复制,当对象到达一定的阈值的时候就会放到老年代(大对象也直接放老年代),老年代满了就触发full gc(标记整理算法) 并且也触发 Minor GC
串行 并行 并发
串行gc:单线程gc清理垃圾适合小项目,大项目效率较低()
并行gc:多线程gc清理垃圾适合大项目,小项目反而效率低
并发gc:用户线程和gc线程同时运行 cms/g1
Parnew+cms垃圾收集器和g1垃圾收集器
------------Parnew(新生代)+cms 垃圾处理器(老年代)------------
设置一个阈值堆内存达到一个阈值的时候就清理垃圾
1 初始标记阶段
只标记GC Roots 能直接引用到的对象,速度快stw时间短
2 并发标记阶段
从GC Roots开始找,用户线程和gc线程同时运行,找到它能引用的所有其他对象
3 重新标记阶段
并发标记中,可能会改变GCRoo的引用链,所以需要重新标记一下,会产生stw
4 并发清理
gc线程和用户线程同时清理垃圾,可能会产生浮动垃圾
优点
1.初始化标记与重新标记只需要短暂的stw操作,清理阶段gc线程赫尔用户线程同时运行,不会非常耗时,只会短暂的stw,降低用户线程等待时间
缺点
1.并发清理会导致空间不连续,碎片化问题
2.多线程消耗cpu资源
3.并发清理的时候可能会浮动垃圾
4.大对象无法存放的时候可能引发fullgc
5 CMS收集器只适合于使用老年代
------------g1垃圾收集器------------
整个堆被划分成2048左右个Region
G1中提供了三种模式垃圾回收模式,young gc、mixed gc 和 full gc,在不同的条件下被触发。
young gc(并行多线程,会stw)
发生在年轻代的GC ,就是在eden region区满了的情况下,采用标记复制算法吧对象放到 s区(s0,s1)(会stw),当对象年龄到达一个阈值(默认15)的时候就存放到老年代
mixed gc
当老年代达到一定的阈值的时候,触发mixed gc,回收整个Young Region(新生代区域),部分的老年代区域
full gc
g1收集器回收过程分为三个环节:
1.年轻代GC(Young GC )
如果新生代满了的情况下,先会触发young gc,
2.新生代和并发标记过程( Concurrent Marking)
在新生代进行回收时,进行GCRoot初始化标记与CMS实现原理基本相同,老年代达到堆内存空间阈值时,
3.混合回收(Mixed GC )
如果老年代当老年代达到一定的阈值的时候触发mixed gc,回收整个Young Region(新生代区域),部分的老年代区域
G1收集器Remembered Set*问题(记忆集)
一个region中的对象可能会引入到其他region中的对象,为了避免全局扫描,在每个region中都对应一个Remembered Set(记忆集),使用CarTable记录每个region区相互引用的关系。
G1收集器CSet
筛选出回收收益最高的分区添加到CSet中,回收的时候都是根据CSet进行操作的
G1对比Parnew+cms最大的进步在哪里?
1.G1可以控制stw时间
2.G1基于标记-整理算法, 不会产生空间碎片,分配大对象时不会无法得到连续的空间而提前触发一次FULL GC。
三色标记算法原理
GC如果想查找到存活的对象,根据GCRoot引用链遍历存活对象,GCRoot遍历过程中,分为三种不同颜色。
白色:还没有被访问的对象
灰色:本对象被访问过,属性没有被访问过
黑色:本对象已经被访问多,并且所有属性都被访问过
颜色切换流程
1 初始标记阶段
–只标记GC Roots 引用的对象 变成灰色
2 并发标记阶段
–从GC Roots开始找(灰色),找到它能引用的所有其他对象,都变成黑色
3 重新标记阶段
–解决漏标问题
4 并发清理
–最后把白色的对象都清理
cms 和 g1 区别
区别一: 使用范围不一样
cms只适合老年代还要配合其他收集器一起使用,g1适合老年代和新生代
区别二: STW的时间
CMS收集器stw时间比较小
g1收集器是可预测的停顿时间(建立可预测的停顿时间模型)
区别三: 垃圾碎片
CMS 标记清除(老年代) 容易产生内存碎片
G1 新生代(标记复制),老年代(标记-整理) 不会产生内存碎片,当回增加停顿时间
cms 和 g1 解决漏标问题(最大的区别)
漏标
漏标会导致被引用的对象当成垃圾误删除,这个严重的bug必须解决 一般解决方案有两种:增量更新和原始快照
CMS收集器中处理漏标问题(增量更新):
假如在并发标记阶段如果有 灰色对象与白色对象引用断开,并且有黑色对象白色对象引用,然后在重新标记阶段,它就会把黑色对象变成灰色重新扫描这个黑色对象的引用链。
(这种方案能够确保垃圾都被清理,缺点就是效率非常低)
G1收集器中处理漏标问题(原始快照SATB):
假如有灰色对象与白色对象引用断开,会记录原始快照,如果没有黑色对象去引用这个白色对象也不会被清除,就会产生浮动垃圾,下次在清理
(这种方式效率高,从而人用户线程的停留时间更加短)
jvm 参数性能调优
1 项目堆内存的初始值和最大值保持一致
2 尽量少调用system.gc
3 尽量少放大对象和全局变量,避免触发fullgc