本文内容来自《Java编程思想(第四版)》第二章《一切都是对象》和第五章《初始化与清理》。做为一个使用了好几年的Javaer,再次看编程思想的前面章节(不要问我为何用再,尽管我第一遍看的啥,一点都不记得了。)编程
-----------------正文分割线---------------------微信
一个程序须要在计算机中运行,其本质是CPU操做内存中[1]的数据,进行某些运算的过程。因此这个问题是,计算机是如何操做这些数据的。网络
要解答这个问题,必须知道1.这些数据指的是什么?2.这些数据是如何存储在内存中的?数据结构
a) 数据能够存在哪里?this
l 寄存器。存在这里的读取和写入速度都是最快的,由于寄存器是CPU的一部分,而CPU是干活的所有力量,因此CPU和寄存器的交互速度很是快,比内存快上百来倍(未考证)。至于为何寄存器更快呢?由于寄存器用的是SRAM,而内存是DRAM,SRAM贵多了。至于为何SRAM比DRAM快,这个我也不知道(喂,在这么下去,要跑题了)。spa
l 堆栈:堆栈是存在内存的。用的是栈的数据结构,它快的缘由是栈的空间分配速度很快,只须要将指针上移下移就好了。(这种上下其手的事情慢了怎么行。)指针
l 堆:堆也是放在内存的,并且只比堆栈少一个字!不过它的速度慢多了,这是由于它是堆的数据结构,堆天生就比栈慢。可是堆栈太大了效率就吃不消,你去试试对乐山大佛·上上下下啊!而堆就不同了,能存的东西就多多了。对象
l 其余:包括常量堆,流、持久化对象(这个是存在磁盘和网络上的)。内存
b) 而后呢?(如何操做存在内存的数据)资源
对内存中数据的操做,汇编是直接操做的,所以你能够在组成原理中看到,内存寻址是一块很重要的内容。C/C++是采用“指针”来间接操做的,它要负责内存的分配和回收,例如malloc就是要内存的节奏。
在Java中,一切都是对象。对象有可能很庞大也可能很小。Java将对象的引用存储在栈上,这样能够快速找到定位,将实际的对象存储在堆中。引用的本质是内存地址,它直接指向堆里相应的对象。因此你在Java中输出this的时候,其实输出的是Java对象的内存地址。
前面说了,堆的操做比较慢。可是实际上,Java的堆并不慢,根据编程之美的说法,Java堆的效率能够媲美某些语言(我也不知道某些是哪些)的栈。这主要得益于JVM良好的垃圾回收机制。
c) JVM垃圾回收机制
程序在运行过程当中,须要不断的申请新的内存空间,而后释放掉不用的对象。程序删除不用的内容后,原来这些内容占着的坑(内存空间)就会空出来。如此便会形成大量的内存碎片存在。内存碎片会下降内存使用率,并致使一些大内存对象的分配显得困难。整合这些内存碎片,是很是消耗资源的行为(可是又不得不整合)。JVM的内存回收机制(垃圾回收机制)干的就是这个活。
那么,JVM是如何进行垃圾回收的呢?
简言之,就是根据某些策略,找到那些不用的对象,并将它们释放掉。固然若是能顺便解决内存碎片就更好了,么么哒。
d) 如何找到失活对象?(根据什么策略呢)
有个理论上很是简单的方式就是,若是一个对象被引用了,那么给它的计数增长1,对象被释放掉了,就减去1,那么当一个对象的引用是0的时候,他就绝不留情的被垃圾回收机制回收了。这个方式叫作“引用计数”。可是这个方式有个致命的问题:当两个对象相互引用的时候,计数就永远不为0,即使这些对象是须要被释放掉的。
因此,JVM就不用这个方式啦。
JVM的作法是:遍历栈里的全部引用,有引用的对象即是活的对象,没引用的对象,就是被当作孤魂野鬼来处理。为了解决内存碎片的问题,JVM使用了一种叫作“中止-复制(stop-and-copy)”的作法,即暂停当前程序,而后将活的对象复制到另外一片内存区域;如此,既解决了内存碎片(新复制的对象在内存上是连续的),有解决了垃圾回收(没有引用的对象就被抛弃了)。可是这样子,效率上并不过高,另外,还须要一大片内存来作备胎。另外,程序稳定运行后,内存碎片可能并很少。
这时候,JVM的另外一个套机制,叫作标记-清扫(mark-and-sweep)的方式。这种方式也是从栈出发,遍历全部引用,为有引用的对象标记存活标记;标记以后,再开始清扫,即清理没被标记的对象。这时候获得的结果,其内存是不连续的。
JVM会监视内存使用状态,在程序稳定的时候,启动标记-清扫方式,当堆碎片过多的时候,启动中止-复制方式。JVM管这个过程叫作自适应的垃圾回收机制。
在上面所述的过程当中,内存是以块为单位进行分配的。较大的对象会占据一整个块。每一个块都有一个参数叫作代数(generation count),标记其是否存活。对于大型对象,在中止-复制过程当中,也不会被再复制一遍,只是代数增长。(这个是为了减小复制的内容,从而减小内存占用。)