您的位置:首页 > 其它

JVM内存概况与垃圾回收机制详解

2015-08-19 15:40 537 查看
参考:《Java虚拟机精讲》

一、JVM虚拟机内部的内存分布的概况



其中方法区我在博文 java虚拟机类加载过程内存情况底层源码分析及ClassLoader讲解中详细讲解过,可参考那篇文章。它里面主要保存:运行时常量池、字段和方法数据、构造函数、普通方法的字节码等。


PC寄存器会存储正在执行的字节码指令地址,线程私有

Java栈也为线程私有,生命周期与线程的生命周期一致

二、内存分配

1、分配步骤

当我们创建一个对象时,会经历如下步骤:



根据上面的描述得到下面的图



所以对象是分配在堆的Eden区的,(部分分配在线程私有的TLAB区域(Thread Local Allocation本地线程分配缓冲区))

因为这块区域是线程共享的,从堆中划分内存空间是非线程安全的,所以必须保证数据操作的原子性

为提高效率,有一种方法,让每一个线程在堆中先预分配一小块内存(TLAB本地线程分配缓冲),每个线程只在自己的内存中分配内存。

(可以回顾下,类的加载也是必需保证操作的原子性

java虚拟机类加载过程内存情况底层源码分析及ClassLoader讲解

虚拟机会保证一个类的<clinit>()方法在多线程环境被正确的加锁、同步。

)

2、分配初始化

(另注意:3,4步之间,当为对象成功分配内存空间后,JVM会首先对分配后的内存空间进行零值初始化。确保对象的实例字段在Java代码中可以不用赋值就可以直接使用

java虚拟机类加载过程内存情况底层源码分析及ClassLoader讲解 类似于第一张图的加载过程的第三步,是JVM自己进行的赋初值的操作,不是我们显示的指定的部分

关于Java变量、数组、对象的声明、初始化与访问方式-----《疯狂Java突破程序员基本功的16课》读书笔记----第一章

可以对比下

实例成员变量 类成员变量 局部变量

可以知道前两个JVM都会为我们赋初值的,而局部变量得我们自己进行覆初值,否则不能使用



3、分配内存采取的算法和存放的区域

那么在Eden区域中怎么分配对象:

如果Eden区域内存空间以规整有序的方式分布,则使用指针碰撞方式

否则使用空闲列表的方法分配内存



但并不是所有对象都是分配到Eden区域的。

比如:逃逸分析,选择未逃逸的对象,直接分配在栈帧空间(见上图3)

逃逸分析:对象定义在方法体内部,访问与使用作用于只在方法内,则没发生逃逸

但是一旦对象引用被外部成员引用后,这个对象则发生了逃逸,从方法体内逃了出去

栈帧会伴随方法的调用而创建,随方法执行结束而销毁

对于没有逃逸出去的对象可以直接将其分配在栈帧空间,它会随着栈帧出栈而释放

这样的好处:降低GC的回收率并提升GC回收效率,(个人认为同时也避免了多线程在堆中分配内存的问题)

JDK6u23版本后,HotSpot默认已经开启逃逸分析

三、内存回收

Eden区内存大小是有限的,J***A程序员不会去显示释放无用的对象,那么JVM一定要对无用的对象进行释放和回收。

1.标记无效对象

那么这么多对象JVM如何认定这个对象就是无效对象呢:

计数算法和根搜索算法

计数算法:对象被其他存活对象引用时,它私有的计数器加1,不再引用则减1。当其计数器为0时,被标记为垃圾对象。

---------------->此算法简单效率高,但存在循环引用的问题,可能引发内存泄漏。

根搜索算法:以跟对象集合作为起点,按照从上至下的方式搜索,能够直接或间接连接到的就是可达,不可达的对象被标记为垃圾对象

个人认为就是数据结构中图的深度优先遍历或者广度优先遍历,寻找从根对象出发的连通的其他节点

根对象集合包含如下:(见图1)

1.J***A方法栈中对象的引用

2.本地方法栈中对象的引用

3.常量池中对象的引用

4.方法区静态属性的对象引用

5.类对应的Class对象



2.回收方案

参考/article/1332961.html

有三类回收的算法

(1)标记清除

(时间效率高,但回收后不连续,大对象无法分配)

(2)复制

(整块内存只能用其中一半)

(3)标记整理

(解决了不连续 与 内存利用率低的问题,但效率比较差)

JVM中堆区分为新生代和老生代(见图1)

由于老生代回收频率低 并且 需要存放大对象 因此使用标记整理的方案

新生代:频率高 死亡率高(IBM的研究表明,98%的对象都是很快消亡的 因此使用复制算法

这里详细介绍:

由于对象死亡率高,所以没必要按照上述的复制算法一半使用一半空闲,这样太浪费

Eden区:From Survivor:To Survivor=8:1:1

现在的回收过程如下:



1.初始状态:对象都分配在Eden区,第一次MinorGC发生,将存活的对象复制到To的空间。然后Eden区对这些死亡的对象执行GC(比如finalize),释放掉其所占用的空间。最后清空Eden空间。

2.To和From空间互换位置 (不用再复制,只用换个名字就好,这个在于它的逻辑意义,说明在新的一轮里面From区域就存放了上一轮活下来的对象了)

3.之后的对象还是存放在Eden区域中,然后当MinorGC又被触发时,将Eden中存活的对象复制到To空间中(同第一步)。From空间的存活的(逻辑上也就是之前大浪淘沙坚挺没被回收的对象)也被复制到To中。(有两种情况不会复制到To的空间:a.存活的对象分代年龄超过指定值,b.To空间到达阈值。这两种情况这些对象将晋升到老年代中(终于熬出了头))

4.然后对From和Eden区死亡的对象进行GC,最后清空。

5.To和From空间互换位置

.................循环.............
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: