码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2015-08-19 16:51:40      阅读:167      评论:0      收藏:0      [点我收藏+]

标签:java内存回收   根搜索法   jvm内存模型   java内存分配   新生代老生代   

参考:《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区内存大小是有限的,JAVA程序员不会去显示释放无用的对象,那么JVM一定要对无用的对象进行释放和回收。

1.标记无效对象

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

计数算法和根搜索算法

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

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

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

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

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

1.JAVA方法栈中对象的引用

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

3.常量池中对象的引用

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

5.类对应的Class对象

技术分享



2.回收方案

参考http://blog.csdn.net/initphp/article/details/30487407

有三类回收的算法

(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空间互换位置

.................循环.............

版权声明:本文为博主原创文章,未经博主允许不得转载。

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

标签:java内存回收   根搜索法   jvm内存模型   java内存分配   新生代老生代   

原文地址:http://blog.csdn.net/silviakafka/article/details/47260479

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!