写在前面
JVM的内容好像一直很像天书,啃过好几次都放弃了,这次再来试试。
一、JVM Vs DVM
JVM是Java虚拟机,DVM是Android系统的。
为啥会有DVM?因为Android觉得JVM用着不得劲(内存和性能有更高要求),就开发了一个Dalvik 虚拟机。
执行文件 | 编译技术 | 效率 | 类加载系统 | 存储 | |
---|---|---|---|---|---|
JVM | class文件(运行时,dex字节码 -> 本地机器码 -> 执行) | JIT(Just In Time,即时编译技术) | 效率低 | 1个 | 基于栈(内存) |
DVM | dex字节码 | AOT (Ahead Of Time,预编译技术) | 执行效率更快 | 多个 | 基于寄存器 |
ART(5.0开始) | 本地机器码 (安装应用时,dex字节码 -> 本地机器码) | 可能增加10%-20% |
二、JVM内存区域
JVM的内存可分为三个区:堆(heap)、栈(stack)和方法区(method)。
其中Heap区是GC管理的区域。
1. Heap区
Heap的内存分布如下:
下面的图更详细一些。
整体上这么分:
-
Young:
年轻代,包括Eden 和 Survivor两块小区域(S0和S1,也有成为from和to的)。
-
Old:
老年代,年纪大的对象,或者块头大的(所以不能太胖呀,太胖直接被扔到老年区了)……
1.1 Young 年轻时代
1)Eden - 新生的伊甸园
名字叫伊甸园,新new出的Object对象会在这个区域得到一块自己的空间,大概只有在这个区域它们才最快乐吧。
2)Survivor Space - 幸存者空间
能幸存不易,幸存者才会待到的区域。 到不了这个区域的是只活了一集的。
-
当Eden区域满了,就会触发Minor GC(垃圾桶满啦)
在这过程中被清理了的对象小鬼,也就不再幸存了。 不能被回收的小鬼们会被转移到两块幸存者区域中空白的一块。(还有要用的东西,还不能扔呢)
-
为什么会有两块幸存区域呢?
这是为了避免产生内存碎片。 如果只有一块幸存区,当GC发生时,从young区域被转移过来的对象,放到幸存区域的内存可能不是连续的,造成内存有空白不连续区域。
为了避免这个问题,HotSpot JVM把一块幸存区域的所有对象拷贝到另一块空白的幸存区域,这样就内存里就没有空白空间里。
1.2 Old 老年啦
Q: 什么时候进入老年代?
- 存放新生代中经过多次垃圾回收仍然存活的对象,也有可能是新生代分配不了内存的大对象会直接进入老年代。
一般有两种情况:
- 当年轻代的对象年龄到达阀值(15),换句话说Minor GC发生太多次了,年轻代的对象们不年轻了……
- 另一种情况是,幸存区域没有足够空间再容纳新生的小鬼们了。
三、GC 回收垃圾啦
1. 引用
Java对象是通过引用使用的,所以GC清理的“垃圾”是Heap中不再使用的对象。
类型 | 描述对象 | 使用情况 |
---|---|---|
强引用 StrongReference | 类似"Object obj = new Object()"这类的引用 | 如果一个对象具有强引用,那垃圾回收器绝不会回收它。 |
软引用 SoftReference | 用来描述还有用但并非必需的对象 | 内存空间不够时(抛出OutOfMemoryError之前),才会被垃圾回收 |
弱引用 WeakReference | 非必需对象 | 只能生存到下一次垃圾回收之前,无论内存是否足够 (只能活到下一集) |
虚引用 PhantomReference | 幽灵引用或幻影引用 | 唯一目的就是能在这个对象被回收时收到一个系统通知 |
- 几种类型的继承关系:
个人理解:
type | 公司裁员时 |
---|---|
强引用对象 | 各部门的leader们。 |
软引用对象 | 那些手上还有项目要他们做,但也可以被替代的家伙。呵呵 |
弱引用对象 | 手上已经没事情要做的家伙。 |
虚引用对象 | 大概是刚转到某个部门的,或者入职没多久的,或者实习生。 |
2. 回收算法
1) 标记清除法
这是GC算法的思想基础。 将垃圾分为两个阶段:
-
标记阶段。
通过根节点,标记所有从根节点开始的可达对象,未标记过的对象就是未被引用的垃圾对象。
-
清除阶段。
清除所有未被标记的对象。
2) 复制算法
为了解决效率问题而出现的。
将原有的内存空间分为两块,每次只使用其中一块。
在垃圾回收时,将正在使用的内存中存活对象复制到未使用的内存块,然后清除使用的内存块中所有的对象。
3) 标记整理算法
是前两种GC方法的综合体。
过程与标记-清除算法一样,不过不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉边界以外的内存。
算法 | 优点 | 缺点 |
---|---|---|
标记清除法 | 思路简单且实现方便 | 1.效率不太高。 |
2. 回收后的状态里,可用内存不连续。当后续需要分配大对象而无法找到连续足够的空间,会提前触发下一次GC | ||
复制算法 | 回收和分配时不用考虑碎片问题,提升效率 | 代价是永远只能使用一半的内存 |
标记整理算法 | 解决了内存碎片问题,又提高了内存空间的使用率 | 牺牲了性能,需要遍历heap至少两次。 |
4) 分代收集算法
现代商用虚拟机基本都采用分代收集算法来进行垃圾回收。其实是上面几种算法的结合。
垃圾回收主要是在Young(年轻代)和 Old(老年代)工作.
分代回收的过程是这样滴:
四、内存泄漏
简单理解:应该被回收的内存没有被及时回收(无用对象,还可达)。 就像某些那啥的人应该退休了,还不肯让年轻人。
借个图:
积累久了会导致Out of memory!
看一堆文字容易看花,整个图。
五、内存抖动
指内存频繁地分配和回收,而频繁的gc会导致卡顿,严重时和内存泄漏一样会导致OOM。
1. 检查工具
- Alloctions Tracker
- Memory Monitor
2. 如何避免发生内存抖动?
-
尽量避免在循环体或者频繁调用的函数内创建对象,应该把对象创建移到循环体外。
-
注意自定义View的onDraw()方法会被频繁调用,所以在这里面不应该频繁的创建对象。
-
当需要大量使用Bitmap的时候,试着把它们缓存在数组中实现复用。
-
对于能够复用的对象,同理可以使用对象池将它们缓存起来。
最后
有些个人理解,如有不准确期待指出~