大话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主要都会讲到多线程里的主内存和工作内存,这两个名词大家肯定没少见,但它俩具体是什么东西,长啥样呢(身高、体重、三围)?因为这篇的主人公不是线程,所以先吊下胃口,先从堆和栈说起。
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扯到一起了,这东拼一块西拼一块的感觉大有《诚哥的逆袭》那杂乱无章的特点。不过希望对一些人会有帮助,如果有说得驴唇不对马嘴的地方,指出来好了,对我也是学习。