您的位置:首页 > 其它

大话JMM

2014-04-10 10:35 417 查看
为啥题目叫《大话JMM》?因为JMM优秀的文章实在太多了,很多讲得灰常专业。对于我这个连本科毕业证都没拿到的家伙,就在自己的理解上整合一下自己的理解,可能讲得会比较通俗吧,还是原话,扯得离谱的地方尽管喷我,就是学习了。

其实说到JMM主要都会讲到多线程里的主内存和工作内存,这两个名词大家肯定没少见,但它俩具体是什么东西,长啥样呢(身高、体重、三围)?因为这篇的主人公不是线程,所以先吊下胃口,先从堆和栈说起。

Heap:

简单说,heap(堆),是所有的线程共享的空间,这就一下子和上面的主内存联系起来了。当然了,它和《数据结构》里面的大顶堆小顶堆里面的堆可不是一个东西,数据结构的堆是二叉树,而这里是一个连续的空间。我大二的时候就被这两个概念弄晕了很久,当然,栈(堆栈)在数据结构还是内存模型里都是一个德行的,这个放心。

heap存的都是new出来的东西,可能有人会认为凡是java的对象都在堆里面,其实不是的。如果看过《java23种设计模式》,肯定知道什么是享元模式。说白了就是一个老被人用的东西就扔到容器里,然后谁用谁就引用它,免得new一堆一模一样的东西占空间(够精简吧)。String和基本类型就是用到的享元模式,比如int a=5;int b=5;这个5是存放在栈里面的,a和b都在引用,所以就省空间啦!同理,String a="5";String b="5";也是一样,因为不是new 出来的String,所以也是存放在栈中。只有String a=new String("5");才会放在堆里面,但这样它就失去了享元模式的特性了。

heap虽然只放new的对象,但它也是蛮复杂的:



这个被我涂鸦得很严重的图是http://my.oschina.net/u/436879/blog/85478上面的,不过说明基本上都标在图里面了,我也简单的说一下:

heap有年轻代、年老代和持久代三个部分。

年轻代:Eden+俩一模一样的Survivor,刚new的都是扔到Eden里面,Eden满了就会扔到一个Survivor里面,那个Survivor满就会扔到另一个里面。这两个Survivor没先后顺序,所以可能某个Survivor会同时存在刚扔进去和第二次被扔进去的对象。如果某个对象被进两个survivor过还没有被gc干掉,那就是它太尿性了,直接丢到年老代里。因为这说明这家伙后面要经常会被用到,为了不折腾,就扔到不容易被回收的年老代好了。

年老代:没啥说的,不容易被回收。

持久代:它对垃圾回收影响不大,放的也都是些static的和class的信息。正如图中涂鸦举的例子,如果会动态生成调用大量class时把它设置得大一点是比较明智的,大小通过-XX:MaxPermSize=<N>进行设置。

堆的知识我知道的也就这点,后面就说栈了。

stack:

多线程里每个线程有个缓存有个栈,栈是它自己才能用的,所以把IO流这些对象设成方法内的局部变量就没有线程不安全这个概念了,因为方法内的局部变量就是在线程的栈里面存着。

栈里面存放的信息有:基本类型(局部变量)、引用(4字节的Heap内存地址)、代码、方法返回值、方法上下文。这里再“穿越”一下,从对象的存储形式说起。

图片出自http://m.blog.csdn.net/blog/tracker_w/12867977



这个就是一个对象在堆中的存储形式



如果带有继承,就会变成这样



如果带有方法,那么每个类都会有一张虚表,它是用来存放类的所有方法的,这样只用存数据,方法查虚表,就不用每个对象都存放自己的方法了。

之所以如此脱线的出了这3张图,其实就是为了具体的说明stack是用来存放代码和引用的。这里虚表指的方法就是存放在stack里面,而对象是在heap里,stack保留它的引用。

stack中对对象的引用很重要,因为一旦被赋值为null,那么heap中的对象就是不可达的了。gc运行时采用有向图的方式遍历,发现不可达,那么这个东西就会被光荣的撵出内存。gc就是根据stack中的4字节地址扫描heap的。

此外,再说一下stack的递归:其实递归是在栈中产生栈帧,每次递归都会放入一个栈帧,栈帧里的数据都是独立的,所以递归次数多了或者死递归就会出现overflow错误。当然,方法退出,那么这个栈帧就会销毁。

这样,就算是把JMM和堆栈和GC扯到一起了,这东拼一块西拼一块的感觉大有《诚哥的逆袭》那杂乱无章的特点。不过希望对一些人会有帮助,如果有说得驴唇不对马嘴的地方,指出来好了,对我也是学习。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  JMM GC 内存模型