java虚拟机运行时数据区分为五个部分:1.虚拟机栈 2.程序计数器  3.本地方法区  4.堆  5.方法区

这里前三个是线程独享的后两个是线程共享的。

1.虚拟机栈主要存放的是栈帧,而栈帧是我们方法运行时产生的数据,里面存放着方法的局部变量表,操作数栈,返回出口等数据,是后进先出的顺序,

执行方法的时候会入栈,执行完后出栈。所以方法死循环的时候,栈内不断添加方法的配置信息,会导致栈溢出报错。

2.程序计数器主要记录了程序运行的位置,在java虚拟机中多线程之间的切换,代码并不是一次运行完成中间会发生中断,这时候就由计数器记录运行位置。也是唯一不会oom的地方。

3.本地方法区主要存放的是navite方法,Android很多功能是C实现的这边数据加载的都是在本地方法区。

4.堆 存放着代码运行创建对象的实列和数组数据,也是gc主要回收处理的地方。

5.方法区存放类信息,常量,静态变量等数据。

说我各个组成的作用后讲下GC垃圾回收先说下如何判断哪些资源是垃圾

常用的方法有两种:引用计数法和可达性分析

引用计数法是指对对象创建时进行一个计数,对象每存在一个引用就进行加一计数,减少一个对象引用就减一这样计数为0的时候说明该对象不存在引用需要销毁。存在问题就是ab两资源互相引用,

而其他资源又未对ab资源进行引用的时候,不能判断ab资源能进行销毁。

可达性分析(java采用这种)则是对gc root资源进行分析(gc root 一般包括静态变量,常量,栈帧中的局部变量表,native方法引用),对存在引用关系的资源进行标记,没有标记到的说明是垃圾可以进行回收。

接下来说下内存的分配结构,

1.新生代  2.老年代  分配的空间大小 1:2

新生代还分为Eden,from和to这三块并且大小是8:1:1,这里结合清除算法来看,新生代采用的是复制清除算法,就是每次gc时候,去把没有标记为垃圾的数据进行复制到一片新的区域,

然后再把老的区域整体做一个清空,这种算法效率和空间碎片整理上都比较不错,但是存在一个问题是需要预留出一个空的空间来存放存活的数据导致空间利用率不高。

结合实际来看我们内存新建的对象一般活不过一次gc,大部分对象一般用完都是可以回收的,所以这边对新生代做了一个分区,分成三部分。新对象进来时候存放到Eden区,然后再经历一次

gc后把剩余的对象放到from区或者to区 这是要看这次gc那块区域当预留区域,而from和to区采用复制清除算法,两块的区域互相复制张贴,这样预留区才占10分之1,极大提高了内存利用率。

 

老年代这块清除算法有两种,标记清除,标记整理,标记清除就是把标记为垃圾的进行一个删除但是导致的结果是内存碎片化十分严重,几次gc后内存中连续的大片空间没有,当一个大对象进入

内存的时候可能就导致内存溢出,而标记整理则没有这样问题,它会对删除后的数据进行整理,把有用的数据放到一起这样内存的碎片化就不严重了。

为什么复制清除算法优于标记算法,(网上的说法比较认同但不确定)复制清除算法可以发生在标记过程中,当一个对象标记为活的对象时候就可以直接进行复制,等标记完后就可以进行清除了,

而标记算法只能发生在标记完成之后才能进行所以效率比较低。

java虚拟机中已经有垃圾回收器的实现了,新生代分别是serial,Parallel,老年代分别是CMS,serial old,parallel old  最后还有一个G1

核心要记住垃圾回收器的演变都是为了减少stw的时间,(stw stop the word  工作暂停时间,垃圾回收时候如果创建对象不暂停,肯定会出现标记一个资源需要回收,但是在标记过程中新建对象引用该资源 gc后这个资源为空那肯定出现bug了) ,最早的serial是单线程串行处理机制,Parallel是多线程串行处理机制,CMS则是多线程并发处理机制,主要讲下这个CMS并发如何实现的。

CMS把标记分为三个部分,

第一部分初始标记与gc root存在直接引用的数据,这部分标记简单速度就会很快,这个时候是会出现stw时间很少。

第二部分并发标记,这时候是不会出现stw,并发运行因此需要占用一个cup运行。

第三部分重新标记,这时候主要修正并发标时出现的新对象。

最后进行删除,虽然没有避免stp的出现但是极大缩小了时间。采用的是标记清除算法。

最后G1直接打破了原来的分代模型,对内存空间进行了等量区域划分,但是还是存在新生代老年代的概念,垃圾回收的标记的流程与CMS类似,最后多了一步筛选回收,因为内存区域划分成小块了,因此回收的时间将变的可控,对比CMS如果给我回收时间10ms,一个区域块回收1ms  那我就先回收10个区域块,而cms不行需要整体流程走完才能算回收完中间不可断。