JVM好像很难鸭,难也学吖

261 阅读6分钟

写在前面

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的内存分布如下:

两大块体积差异挺大.jpg

下面的图更详细一些。

整体上这么分:

  • 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: 什么时候进入老年代?

  • 存放新生代中经过多次垃圾回收仍然存活的对象,也有可能是新生代分配不了内存的大对象会直接进入老年代。

一般有两种情况:

  1. 当年轻代的对象年龄到达阀值(15),换句话说Minor GC发生太多次了,年轻代的对象们不年轻了……
  2. 另一种情况是,幸存区域没有足够空间再容纳新生的小鬼们了。

三、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的时候,试着把它们缓存在数组中实现复用。

  • 对于能够复用的对象,同理可以使用对象池将它们缓存起来。

最后

有些个人理解,如有不准确期待指出~

参考: