一、前言
在前面的章节了解了class的文件结构和类加载机制,所有的这些数据都是运行在内存中,因此接下来需要了解JVM的内存管理机制。
二、运行时内存区域划分
JVM在运行的时候,把自己所管理的内存分为若干区域,每个区域有各自的功能。从线程的角度出发,分为共享内存区域和私有内存区域
- 线程私有内存区域,包括以下三块
- 程序计数器
- java虚拟机栈
- 本地方法栈
- 共享内存区域
- 堆:对象实例(GC新生代、老年代)
- 方法区(类信息、常量、静态变量、及时编译后的代码等)
- 运行时常量池(字面量、符号引用)
三、对象探秘
程序在JVM运行中,会需要很多的对象,那么我们需要了解对象是如何创建、布局以及访问的。
3.1 对象的创建
当遇到new关键字时,从常量池中定位到对应类的符号引用,并检查该符号引用所代表的类是否被加载、解析和初始化。如果没有则执行相关类的初始化过程。类加载检查通过,为对象分配内存,对对象进行必要设置,对象头:对象哈希吗、GC分代信息、哪个类实例.最后执行方法,对相关的字段进行赋值。
3.2 对象布局
对象头,实例数据、对其填充
3.3 对象访问
创建了一个对象目的就是为了使用,java程序通过栈上的refrence的数据来操作对上的具体对象。目前使用两种方式来访问对象的具体位置:
3.3.1 句柄
在堆中划分一块内存来作为句柄池:refrence中存储的就是对象的句柄地址,句柄中包含了对象的实例数据和类型数据各自的具体地址信息。
3.3.2 直接指针
对象实例中需要存储类型数据的指针。
四、GC和内存分配策略
在了解完对象的创建,访问以及在对象JVM的内存分配情况后,我还需要了解对象的回收机制。
4.1 对象已死吗?
一个对象是否可以被回收或者已经死掉,可以有以下两种判断方法:
4.1.1 引用计数算法
使用加1,没有使用减1,为0代表可以回收,但存在相互引用问题,导致无法判断
4.1.2 可达性分析算法
主流实现方式,通过GC Roots的对象作为起点,从这些节点开始向下搜索,搜索所走过的引用链,当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的,可判断为可以回收的对象。
可以GC Root对象的包括以下几种:
- 虚拟机栈(栈帧中本地变了表)所引用的对象
- 本地方法栈中所引用的对象
- 方法区的静态属性引用的对象
- 方法去中常量引用的对象
4.2 再谈引用
引用分为以下几种类型:
强引用(Strong refrence)
软引用(SoftWeakRefrece)内存溢出之前进行回收
弱引用(WeakRefrence) 下一次GC之前进行回收
虚引用 回收的到一个通知
这里的引用的软引用和弱引用在实际开发中用得比较多,比如一些缓存框架,则使用了软引用,用空间换时间的方式提升整体的运行体验;弱引用则在一些生命周期较长的对象中使用,比如Handler,弱引用Activity,从而防止因Handler生命周期过长导致Activity的内存泄漏。
4.3 GC算法
下面介绍几种常用的收集算法以及优缺点以及演变过程,最后说一下现代GC的算法。
- 标记清除算法:效率不高、大量碎片化
- 复制算法:将内存1:1分配,回收的时候将存活的对象复制到另外一半,然后对另外一半整体回收。
- 标记整理算法:标记后将可用对象向一边移动,然后回收临界点后面的区域
- 分代收集算法:将内存分为新生代和老年代:新生代的对象大多为朝生夕死,98%概率,因此适用复制算法,这里将内存一个Eden(80%)+两个Survivor(10)空间,回收时将Eden空间和一个Survivor空间可用的复制到另外一个Survivor空间,然后对Eden空间和对应的一个Survivor进行回收,如果其中一个Survivor空间不够,则由老年代进行担保,直接进入老年代。因为老年代的对象存活率高,没有额外的空间对他进行分配担保,因此必须要使用标记清楚或者标记整理算法对其进行回收。
参考文献
深入理解Java虚拟机+JVM高级特性与最佳实践(第二版)