Java运行时数据区域
在这之前先了解JVM运行时的数据区域,分五块:
程序计数器
用于表示当前代码执行到第几行
虚拟机栈
虚拟机栈描述的是 Java 方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧(Stack Frame,是方法运行时的基础数据结构)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
本地方法栈
本地方法栈与虚拟机栈所发挥的作用是非常相似,虚拟机栈对应描述的是Java方法,而本地方法栈则描述的是Native(C++)方法。
方法区
方法区与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫做 Non-Heap(非堆),目的应该是与 Java 堆区分开来。
JDK8 之前,Hotspot(虚拟机) 中方法区的实现是永久代(Perm),JDK8 开始使用元空间(Metaspace),以前永久代所有内容的字符串常量移至堆内存,其他内容移至元空间,元空间直接在本地内存分配。
堆
对于大多数应用来说,Java 堆是 Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC堆”。从内存回收的角度来看,由于现在收集器基本都采用分代收集算法,所以 Java 堆中还可以细分为:新生代和老年代;再细致一点的有 Eden 空间、From Survivor 空间、To Survivor 空间。
很明显Java垃圾回收主要作用于堆区域,而堆区域又分为新生代与老年代,而网上说的持久代(永久代)其实是存于方法区中,并且于JDK1.8后以转移到元空间中(以前永久代所有内容的字符串常量移至堆内存,其他内容移至元空间,元空间直接在本地内存分配)
再介绍下堆里又划分2块区域,分别是新生代和老年代(对应比例1:2)
新生代
主要存放新生成的对象,新生代的对象一般是朝生夕灭(存活期短),而新生代的区域又划分为三个区域,分别叫Eden, From Survivor,To Survivor(比例对应为8:1:1)
在新生代区域触发的GC叫做Minor GC
老年代
当新生代的对象经过一定次数的回收仍存活(这个次数为一个规定的阈值),那么则会由新生代移动到老年代.老年代则会触发Full GC(同时清除新生代,老年代,永久代)
如何判断对象是否为垃圾
1.引用计数法
给对象加上计数器,引用+1,失效-1。当引用数为0则为内存垃圾
但是引用计数法存在一个漏洞,那就是无法解决循环引用的bug(A持有B的引用,B持有A的引用,运用这个方法则导致两者都无法被回收)因此出现第二种方法
2.可达性分析法
通过GC Roots作为起始点,从这些节点开始向下搜索,搜索走过的路径叫做引用链。当一个对象与任何一个GC Roots没有引用链相连时,就认为该对象是内存垃圾。
同样以上面的案例分析(A,B虽然相互持有引用,但没有被任何一个GC Roots引用,因此仍然被认为是内存垃圾)
而到底什么对象可以作为GC Roots呢,以下对象都可以 :
- 虚拟机栈(栈帧中的本地变量表)中引用的对象。
- 方法区中类静态属性引用的对象。
- 方法区中常量引用的对象。
- 本地方法栈中JNI(即一般说的Native方法)引用的对象。
Java垃圾回收算法
知道如何判定对象是否为垃圾后就可以制定垃圾回收的策略(算法)了,分为4种
1.标记-清除算法
标记-清除算法采用从根集合(GC Roots)进行扫描,对存活的对象进行标记,标记完毕后,再扫描整个空间中未被标记的对象,进行回收
优点: 并没有移动仍存活对象的内存,对于正在运行的应用进程影响较少
缺点:1 每个对象找一遍导致效率不高
2 容易产生大量不连续内存碎片无法利用
2.复制算法
把内存分成两半,对象分配内存时只分配到其中一半的区域,当这一半区域内存满了则把这部分内存整理复制到另一半区域,同时把另一半的区域的内存清空
优点:简单高效(相对于上面的方法只需遍历内存的一半),不会出现内存碎片
缺点:一半的内存空间无法使用,代价太大
3.标记-整理算法
先标记(标记方法跟第一种方法一样),然后把存活的对象移向一端,然后再把其余区域的内存清掉
优点:1 相比标记-清除算法不会产生内存碎片
2.相比复制算法不会产生空间浪费
4.分代收集算法
其实严格来说分代收集算法只是前面3种算法在Java虚拟机中实际的应用:对于新生代内存,采用复制算法,对于老年代内存,采用标记-整理算法
Dalvik与Art虚拟机在垃圾回收算法上的区别
Dalvik:只能固定一种垃圾回收算法,出厂就固定了
Art:能够根据实际情况选择垃圾回收算法(例如对于正在前台运行的app采用标记-清除算法,对于转到后台运行的app则可以采用标记-整理算法)
触发GC的时机
Minor GC触发机制:
当年轻代满时就会触发Minor GC,这里的年轻代满指的是Eden代满,Survivor满不会引发GC。
Full GC触发机制:
(1)调用System.gc时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法区空间不足
(4)通过Minor GC后进入老年代的大小大于老年代的可用内存
(5)由Eden区、survivor space1(From Space)区向survivor space2(To Space)区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
堆的垃圾回收工作流程
新生成的对象会在Eden区中存在,当Eden内存已满把Eden无效的数据清除,存活的数据(没能释放内存的数据)移到From Survivor,假如From Survivor内存满了则把Eden 和From Survivor的内存整理移到To Survivor中,如此反复(假如其中一个Survivor并不足以承载Eden+另一个Survivor的内存时直接转移到老年代区域),在Survivor中只要一次GC没把对象内存清掉给内存计数就会+1,当这个计数达到一定阈值会从新生代区域转移到老年代区域,当老年代区域内存已满或出现其他情况又会触发的的Full GC
网友评论