了境如幻,自心所现。 -----佛说
一 Android系统内存管理机制
Java虚拟机是一台抽象的计算机,它拥有自己的处理器,堆栈,寄存器以及相应的指令系统,Java虚拟机屏蔽了与具体操作系统相关的平台信息,使得Java程序只需要生成在该虚拟机上运行的目标代码,就可以在多平台上运行,虽然叫Java虚拟机,但在它之上运行的语言不仅有Java,kotlin,Groovy,Scala等都可以运行。
java虚拟机的执行流程
比如说Activity.java文件 Java编译器会将它编译成Activity.class。编译成Activity.class文件以后传入到运行时环境,让java虚拟机来执行。首先我们每次编写Java代码,写完以后都要通过编译器编译成.class文件最后让Java虚拟机来执行。
Java虚拟机运行时数据区域
首先我们编写的Java代码被编译器编译成.class文件以后,会被加载进入java虚拟机,被加载进java虚拟机以后,就来到了虚拟机运行时数据区域。Java虚拟机运行时数据区域包含方法区,Java虚拟机栈,本地方法栈,java堆,程序计数器。蓝色的表述共享区域,白色表示线程私有的,下面我们对这每一块做详细说明。
方法区域:
存储的时一些加载的类的结构信息,成员函数,成员属性,静态变量,运行时常量池等等。
Java堆:
java堆存放的是对象的实例,java中几乎所有的对象都在这里分配,包括接下来要说到的GC,操作的都是这一块区域,这是GC的主战场。堆里面的对象无法显示的销毁,当这些对象无法销毁又没有空间的话,我们继续创建新对象的时候就会抛出(Out Of Memory)
程序计数器
为了保证程序能够连续的执行,虚拟机必须有方法能够知道当执行完毕当前指令以后它下一次应该执行那个指令,程序计数器就是干这个事情的,告诉虚拟机下一次应该执行那个指令。因为我们要知道java虚拟机里面多线程是轮流切换分配处理器执行的,比如说当前有两个线程A线程和B线程,但是CPU在调度的过程中肯定是一个一个来执行的,加入当前执行的是A线程然后切换到了B线程,再由B线程切换到了A线程,这个时候虚拟机怎么知道A线程我上一次执行的到那个地方了?这就是程序计数器的作用,每一个线程有一个独立的程序计数器。
Java虚拟机栈
每一个java线程它都有一个私有的java虚拟机栈,它的生命周期与线程相同,也就是说我的线程什么时候创建它就什么时候创建,我的线程什么时候销毁它就什么时候销毁。主要用与存储java方法调用的各种状态,虚拟机栈包含了局部变量表,操作数栈,动态链接,方法出口等。每个方法从执行到完成就是一个栈帧入栈到出栈的过程,出栈以后会返回一条字节指令的地址( returnAddress 类型,也就是回到方法刚开始执行的地方,程序计数器记录的位置)。局部变量表存放了基本数据类型,对象引用类型,returnAddress 类型。当执行的方法过多,压入栈的深度超过了规定的最大深度则会出现 StackOverFlowError;当然现在大部分的虚拟机都是可以扩展栈深度,一旦扩展深度的时候分配不了内存了,就会出OutOfMemoryError。
本地方法栈
与虚拟机栈类似。不同点在于本地方法栈执行的是 native 方法,另外在执行本地方法栈的时候returnAddress则为null
强/软/弱/虚引用
强引用
当新建的对象为强引用时,垃圾回收期绝对不会回收它,宁愿抛出Out Of Memory异常,让程序异常终止也不会回收。
软引用
当新建的对象为软引用时,当内存不够时,回收器就会回收这些对象,如果回收后还是没有足够的内存,抛出Out Of Memory异常。
弱引用
当新建的对象为弱引用时,垃圾回收器不管当前内存是否够用,都会回收它的内存。
虚引用
虚引用跟其他引用都不同,如果一个对象仅持有虚引用,在任何时候都可能被GC回收,只是当它被回收时会收到一个系统通知。
强引用就像大老婆,关系很稳固。软引用就像二老婆,随时有失宠的可能,但也有扶正的可能。弱引用就像情人,关系不稳定,可能跟别人跑了。虚引用就是梦中情人,只在梦里出现过
引用计数算法
每个对象都有一个引用计数器,当对象每被应用一次就+1,引用失效就-1,当计数为0时则讲该对象设置为可回收的垃圾对象。
优点:逻辑简单,操作简单
缺点:比如有两个对象A,B A引用了B,B引用了A,那么当这两个对象都没有被使用的时候依然无法被回收。
可达性分析算法
简单来说,将对象及其应用关系看作一个图,选定活动的对象作为GC Roots;
然后跟踪引用链条,如果一个对象和GC Roots之间不可达,也就是不存在引用,那么即可认为时可回收的对象
上图 Object1....4和GC Root之间是可达的,所以不是垃圾,Object5和GC Root之间不可达所以是垃圾会被回收,那么哪些东西才是GC Root呢???
1 虚拟机栈中正在引用的对象
2 本地方法栈中正在引用的对象
3静态属性引用的对象
4方法区常量引用的对象
垃圾标记算法-----根搜索算法
根搜索算法就是先选定一些对象作为GC Root组成DC Root集合,然后根据对象是否可达GC Root来标记那些对象是正在使用的,那些对象是可以标记为可被回收的。
垃圾标记算法-----标记---清除算法
标记-清除算法即使用根搜索算法标记可被回收的对象,之后将被标记的垃圾对象进行清除。
在上面所讲的内容中我们已经有办法来标定当前的对象是不是个垃圾对象(比如说引用算法标定,或者根搜索算法标定这个玩意是个垃圾),那接下来我们对标记的垃圾对象应该怎么处理?这就是GC要做的事情。比如上图第一部分白的是可用的,灰的是可回收的,蓝色是存活的。然后我们将标记为灰色的对象清空演变出如上只有白色和蓝色的第二个图。
缺点:1 需要遍历整个堆。2产生大量的垃圾碎片
优点:没有漏网之鱼,只要被标记肯定被回收。
垃圾标记算法-----复制算法
先把内存一分为二,每次只使用其中一个区域,垃圾收集时,将存活的对象全部拷贝到另一个区域,然后对之前的区域全部回收
优点:效率最高
缺点:空间每次都只利用一半,但是由于要牺牲掉一部分内存,所以导致内存使用率不高
垃圾标记算法-----标记--压缩算法
在标记可回收的对象后,将所有的存活的对象压缩到内存的一端,让他们排在一切,然后对边界外的内存进行回收。
优点:不产生内存碎片,内存利用率高
缺点:效率比不过标记---清除算法
垃圾标记算法-----分代收集算法
Java堆中存在的对象生命周期有较大差别,大部分生命周期很短,有的很长,设置与应用程序或者Java虚拟机生命周期一样,因为分代算法就是根据对象的生命周期长短,将对象放到不同的区域。根据区域的不同采用合适的垃圾收集算法,绝大部分虚拟机采用此种算法。
Eden 区
对象出生的地方,一个对象在被创建之后就会被放入 Eden 区。
survivor 区
幸存者区,survivor 区有两个。当发生一次 Minor GC 的时候,Eden 区和其中一个 survivor 区未被回收的对象将通过复制算法进入另外一个 survivor 区内,然后清除掉 Eden 区和前一个 survivor 区的对象。之后每发生一次 Minor GC ,survivor 区内的剩余的对象就会被复制到另外一个 survivor 区内,然后清理掉前一个 survivor 区的对象,这样可以保证其中一个 survivor 区是空的。每次发生 Minor GC ,剩余存活下来的对象计数会加1,知道计数达到上限的时候会进入老年代。(一般都是15代)
老年代
有两种情况会进入老年代区:一种是在 Minor GC 多次以后依然存活在 survivor 区的对象,当计数达到上限的时候会进入老年代区;另一种则是大对象(特别长的字符串或者是数组)在发生一次 Minor GC 时就会进入老年代区(主要是因为 survivor 区本身分配的内存不多)。而当发生 Major GC 时,老年代区的对象将会被回收。
新生代区域主要采用复制算法,老年代区域主要采用标记,整理算法
总结
本篇主要讲了java虚拟机的运行原理以及垃圾回收的各种算法,下一篇将阐述内存抖动