参考:《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空间互换位置
.................循环.............
版权声明:本文为博主原创文章,未经博主允许不得转载。