1. 1-内存管理
JVM内存管理,平时大家估计都不是很在意,一直到快跳槽了,快面试了,大家可能会看看,也可能直接忽略不计了。JVM内存管理是虚拟机的事情,和开发人员有什么关系。这种想法一直在我们开发人员的潜意识中存在:“内存管理,与我无关,只要规范地写好Java代码就好喽,虚拟机那是Oracle或者IBM的事。”。我们暂时先放下这个想法啊,一起来看看Java虚拟机到底如何对内存进行操作的。对内存操作无非就是对内存进行分配和对分配的内存进行回收这2方面。可能有些比喻有些牵强,但是笔者还是将内存分配比作公司员工办理入职手续,内存回收嘛,就比作公司裁员吧。
现在回答一下那个原始的问题,为什么开发人员要搞懂JVM对内存的管理。
1):如果程序运行了几天出现了内存相关错误error或者异常Exception,可以根据相关错误定位程序到底在空间效率上哪方面出现了问题,是不必要的新生对象太多?还是静态变量太多?还是不应该创建的大对象太多,完全可以分担到许多个小对象?还是……
2):知道相关的JVM内存管理细节和配置选项(比如:Eclipse的那个启动文件、Tomcat的那些配置启动文件),可以更好地利用本机器资源,有助于优化您的系统性能
3):还是之前说的那句话,了解一下细节,我们的大脑写程序的时候无异于又过了一层:“哦。我这么写的话,它在运行时应该这么分配,恩,换种写法,是不是对它分配来说更好一些呢?”
4):了解JVM细节,看看它是怎么做“事”,没准儿对于咱们开发中间件产品来说是一个思想上的启发。了解细节,视野也得到了扩展,设计一些东西的时候,这些已有的东西可以为我们带来启发。
当然了,有些公司(现在无论好坏吧)就喜欢在面试的时候难为一下面试者。不可否认的是,如果经常不接触底层的研发人员,这些细节不太可能经常接触到。很多公司的面试者就用这些来压低工资、还有就是作为面试者一种高高在上的姿态来鄙视那些肯花时间,肯花精力,诚恳来应试的人。各位,你们在面试中不可否认确实有这种现象吧。言归正传,咱们继续看JVM和内存管理,之前笔者写过2篇文章,一篇是类加载(http://suhuanzheng7784877.iteye.com/blog/964784)、一篇是对象形式与垃圾回收(http://suhuanzheng7784877.iteye.com/blog/1000646),虽然多多少少提及了内存管理方面的信息,但是总是不太连贯,这次我们做个全面的比喻,将JVM和内存这些事情别藏着掖着,都抖落出来。
2. 公司制度
国有国法,家有家规,JVM对于内存的管理也是一样,就像公司的制度一样,有入职的,就有离职的。
咱们先介绍一下公司的组织机构啊,您可以将组织机构看成JVM内存,大家先看下图
方法区:方法区记录了类的一些元数据信息,比如类的属性变量(类型,访问限制)、类的方法信息(返回值,访问权限,参数)、类自身的性质(接口还是抽象类)、常量池(就是这个类型用到的常量的一个有序集合)、静态的类变量(这些变量只与类相关,所以在方法区中,它们成为类数据在逻辑上的一部分)。方法区在一定条件下会被回收器回收,回收后依然不够空间的话一样会报出OutOfMemory。如果用公司入职作比喻的话,方法区更像是该公司HR手中的招聘需求,一家公司要引进新鲜血液(创建对象)肯定是要有一个需求吧:有手(属性)、有脚(属性)、会编程(方法),如此看需求好比Java类这个模板,很多对象都是根据这个定义好的模板进行真正的实例化,真正为咱们做事的不是要求、不是人才需求模板,而是一个个对象实例。所以类仅仅是个招聘需求,对象才是真正为公司做事情的人才。所以笔者这里使用了《三国志11》的崔琰作为方法区的代表,崔琰是选拔人才的,所有招聘需求先到他那里,就像Java的类加载器一样,先将类加载后将类的元数据信息放到方法区中。方法区也叫做持久带。
栈区:这个估计大家都不会陌生,老说堆栈堆栈的。基础类型的值、类的方法的形参、变量也需要在栈申请空间,至于变量指向的是堆还是栈自身的一个地方就得看变量是否是基础类型了,如果是基础类型,则还是指向栈本身。方法的嵌套调用,也需要栈来存储,比如方法A调用了方法B,肯定是先进入方法A,A入栈,之后调用方法B,B入栈,B方法执行结束后B出栈,之后A出栈。这里也是要栈区域给予支持,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收。所以为什么在方法中局部变量在方法末尾不必手工赋值为null,原因就在这里。至于栈吗,效率比较高,而且坚守后进先出的原则,我们就把它看做是辅助办公的行政吧。员工申请笔记本电脑、办公用品都得通过行政才能通过,员工才能正常办公,所以栈更像一个“打杂的”,虽然这个词不太好听啊,但是作用确实不能忽略的。没有笔记本电脑、没有开会用的投影仪、没有记录的笔、本、探讨用的画板、板擦,这项目根本没法做!
堆区:这个是大家听的最多的,所谓内存分配与垃圾回收大多数都是在堆区进行的,咱们主要探讨的分配与回收细节也是主要在堆区域上进行的。堆区就是咱们创建的实实在在的对象实例空间。就像公司根据职位需求(类)找来的人才一样,真正能干事情的人。而堆区就相当于办公室——office,人来了,得有地方办公啊,堆区就是容纳这些人才的地方。能耐大的人才一般占用一间办公室(大对象,属性多,方法多,一般都是比较重量级的)能耐小一点的人才呢,大家将就一下,分配的地方就小了一些。
本地栈区域:这个就是负责调用本地方法JNI的,存储了调用本地方法栈的状态。这个不是内存管理的重点。
咱先看一个极其简单的JavaBean程序
import java.util.Date; /** * * @author liuyan * <p> * User的类相关的原始信息会在JVM的方法区中进行存储 */ public class User { // 静态变量sb放在方法区中 // 指向堆中建立的一个StringBuffer对象 public static StringBuffer sb = new StringBuffer(); // id的类型、访问权限信息在方法区中 // 因为int是原型这个值都在栈中分配 private int id = 1; // name的类型、访问权限在方法区中 // 因为name是特殊的引用类型,这个值也在栈中分配 // 有个好听名字叫做常量池 private String name = "素还真"; // 这个date变量在栈中,=(指针)也在栈中 // 但是指向的Date实际的对象则放在了堆中 private Date date = new Date(); // 以下方法信息(方法名、形参、返回值、访问权限)都是在方法区中存储相关信息 // 方法内部的局部变量在栈中分配 public int getId() { return id; } //……………………节省……………… }
1. 堆管理
好了,知道了JVM的原则,咱们的对象该进入公司办理入职手续了,新人能干什么,具备哪些属性,在方法区中已经描述得比较清楚了。之后更多的对象操作都是在JVM堆区进行的,堆的管理不给力,直接影响整个系统的性能。而垃圾回收,内存溢出更多的也是在堆上产生的。堆这个玩意是什么结构呢,来看看咱们的office是怎么样的。
Office主要分为2大部门,一个是新员工办公的部门、一个是老员工办公的部门,分别对应着JVM的Young带和JVM的Old带。JVM带着有色眼镜看人,在一般情况下啊(有特殊的,一会儿再说),新来的对象没有资格与老员工一起工作,先去Young办公区工作一段时间,经过一些考验之后才能有资格进入资深员工办公区——Old带。Old带是一些资格比较老的员工了,也就是说久经考验,经过公司几次大起大落,依然还有竞争力的那些员工没有被淘汰,赶都赶不走。当然了由Young办公区到Old办公区还有一个缓冲办公区叫做Survivor Space。
以下是咱们堆区内存的一个图形
Eden(伊甸园?)带:在一般情况下,新创建的对象都会分配在这个区域——Eden伊甸园,记住是一般情况下,至于什么是“一般情况”,等咱们说完了From、To、Old后再来讨论一般情况、特殊情况1、特殊情况时、特殊情况N……
From、To空间:这2个区域同意可以叫做幸存区域(Survivor Space),一般情况下,在伊甸园的对象经过垃圾回收后还能依然幸存的人才,恭喜你,你没有被淘汰,但是呢,不代表你就是久经考验的资深员工了,你还得在这个区域呆上一段时间。一般情况下,经过多次的垃圾回收后,依然坚挺的对象,才有资格进入自身的办公区域——Old区。为何又叫做From和To,这个和垃圾回收算法有关,这个咱们之后提及回收算法的时候再说啊。
Old带:在一般情况下,久经考验的对象还活着,没被回收调。那么很荣幸的进入了Old区域。需要注意的一点就是就算进入了Old带,别以为就意味着永远就在这里办公,还是要有危机意识,在Old带只不过意味着回收算法上与Young带不一样罢了,所以一代在Old带一旦被垃圾回收器算法发现你没用了,人家也依然会将你无情的赶走!真到了那时候人家垃圾回收器会无情的说一句:“get out here!”
这三个区域就是咱们的JVM承载对象的地方,对象的分配、对象的回收主要还是面对堆中的这三个区域。在这三个区域,对象分配和对象回收在不同的情况下进行着互相沟通与博弈。
对象申请一个内存堆区域,一般会出现以下情况,申请对象空间就像公司的入职手续,有时候麻烦啊~~~
1.最普通的流程
根据新对象的大小先尝试着像Eden伊甸园要空间,如果伊甸园此时空间足够,好的,到此为止,新对象成功创建
如下图所示
Eden区域此时并不拥挤,还能容纳得下新员工。此刻这就算是正常在Young带建立了新对象的空间。
2.伊甸园空间不够用了
如果此时Eden区域的空间不够了,那么比较残忍的一幕就发生了。HR一看:“咦?新人区伊甸园不够用了,怎么办?裁员!在伊甸园里面肯定有浑水摸鱼的,吃饱了混天黑的!”,于是因为一个新人的入职、因为伊甸园办公区不能容纳新员工,来了一拨裁员风波,我们管这种风波叫做Minor GC,意思就是说让那些占着XX不XX的人从这一次的裁员中不要再占用公司的资源了,get out here,给能干活儿的新人腾地方,JVM残忍吧。Minor GC就是对Eden进行对象回收,在这一次回收过程结束后将没用的对象(不可达)进行回收,释放空间,至于回收哪些对象,和对象的引用类型(强引用、软引用、弱引用)以及对象的可达状态有关(详细请参考笔者以前的博客文章:http://suhuanzheng7784877.iteye.com/blog/1000646)。在这一轮幸存的对象有可能会将部分对象(和对象在内存中的位置有关,和标记对象算法有关)放到幸存区域(Survivor Space),成为幸存员工。
3.幸存员工升级为资格老员工
经过几次人员入职-公司裁员的折腾,依然还没有被裁掉的员工,可以从幸存区域升级成为资格比较老的老员工,对象由Survivor Space挪到了Old带。这是一件好事,久经考验依然存活,证明此人才很重要,确实是人才中的人才,很有用。如下图所示,经历过公司大起大落的员工——黄忠,终于在多次GC后没有倒下,伤痕累累,头发都熬白了,终于顺利打入公司内部,成为了Old带的一名员工,当然了,如果对公司没有利用价值了,现实也是十分残酷的。之前说过了,不是意味着对象到了Old办公区,就能养老善终了,和年轻时候一样同样面临着一个大问题,无用的时候依然被咔嚓的危险啊!当然了,挪到Old区域的前提也是要先保证Old区域要有足够的空间容纳老资格的“新”(相对来说)人。
4.老员工区不够用了
和伊甸园一样,如果老员工区域不够地方了,这个时候又确实有对象需要放到Old区,怎么办?和Eden办法一样,咱们不是说了吗,Old带也不是无忧无虑的,也要面临着裁员的可能。这个时候在Old区的裁员运动也有个好听的名字就是——Full collection。这个时间相对来说比较长一些,有的资料也称之为Major gc,现在一般的说法叫做Full GC。在一般情况下尽量减少这种Full GC的频率,耗时比较长。
5.新对象直接进入老年区
有种情况可以让新对象直接进入到老年区,这种情况比较特殊。先看下图
这个新对象在进入Eden之前先进行判断,如果该对象大小大于配置的PretenureSizeThreshold,说明这个新对象来头不小,属于猎头从别的地方挖过来的人才,这个现象就比较特殊了,这样的对象不用像以前那些新创建的对象在Eden拼杀,而是轻轻松松地直接进入Old带。恩,重量级人才就是不一样,躲过了几次筛选。但是从这一点可以看出一个现实,大对象(相对来说)被回收的几率比较小。也符合客观规律,一个大对象开辟内存的时候从唯物的角度讲其实是比小对象要费一些时间和资源的。产生的时候不易,所以从理论上讲也不愿意将这些创建不太容易的大对象们用完就抛弃了。但是要说明的就是当这个对象毫无利用价值的时候,用Java的话讲就是对象不可达的时候,无论它为我们的软件系统功能做了多少贡献。飞鸟尽,良弓藏,狡兔死,走狗烹。
1. GC方式与公司裁员的方式
公司裁员一般由HR进行工作疏导,如果看过电影《在云端》的朋友应该知道,裁员也要讲究方式方法,如何沟通,如何善后,如何安抚,都是裁员的技巧。不讲究方式方法,愣头青似地去和员工说:“You were fired”,这个后果……
回到正题,JVM的GC方式也讲究技巧面对堆中不同的带,GC的方式是不一样的,即便是面对相同的带(比如Eden),在不同环境下也是有差异的。
面对Eden区域,一般会有以下3种GC策略
1):串行GC,学名叫做Serial GC,它是一个单线程,正常程序停止运行的回收策略。也就是说HR拿到了一个裁员名单,之后在广播中(记得小学时候那个做眼保健操的广播吧)大声对Eden区域的人们说:“都放下手中的工作,现在有重要申请宣布,因为公司发展,需要腾出空间给今天刚入职的员工一些空间,但是没有了,公司临时决定裁员,名单我已经统计上来了,我一会儿过去一个一个通知,这段期间谁都不许工作。”之后HR根据裁员名单(不可达对象)真的一个个通知到个人,对象就这么回收了,资源就这么腾出来了。这个策略的特点就是单线程,暂停程序(我们自己的)。使用场景大多数是在客户端(JVM是Client模式)程序中,尤其是单核CPU的机器,比如Eclipse、Swing客户端等等这类程序都比较适合。
2):并行GC,学名叫做Parallel Scavenge,并行GC策略在串行GC的基础上做了多线程的处理,比如扫描对象是否可达、复制其至幸存区域等等操作都是多线程的,其他的像中断程序运行和串行GC是一样的。好比说此时HR是3个人,一起到了Eden区域,其中一个人HR-A负责检查哪些人才可送至(其实是复制)幸存区域,另一个HR-B负责将已经标记好的人才送往(copy)幸存区域,HR-C就比较狠了,直接对那些没做特殊标记的说了一句:“I am Sorry……”。这种回收策略比较适合于多核CPU,一般情况下JVM模式也是Server模式的适用于此GC策略。单核CPU不能突出这种并行GC的优势,相反,面对多线程之间的资源竞争,这种算法需要的时间会更长一点。需要说明的一点就是这种策略会智能的根据幸存区域与Eden区域的回收状况进行动态调节。
3):多线程串行,学名ParNew,在Eden区域、幸存区域和串行GC并没区别,相当于串行GC的多线程版本,和并行GC的区别就是在分配Eden区域、幸存区域相对来说没那么动态,这个就不必举现实的例子了。这个回收策略还有一个用途就是配个Old带的一个回收策略,稍后再说它。
面对伊甸园这三种GC策略,大家可能发现一个特点,就是在这个区域一旦发生GC(之前说过的,这个区域GC总名称叫Minor),那么系统进入“时间静止状态”,再重要的程序也要停下来。还好在大多数情况下,这个时间并不会太长久。
面对Old区域,一般会有以下3种GC策略
1):资深串行GC,学名是Serial Old。这个和Eden带的串行回收意思差不多,也是基于单线程的,停止运行程序的。无非在回收算法的细节上进行了改动。Eden区域是采用的复制算法,而在此Old空间,因为Old空间的特性,其空间相对来说比较大。所以再回收的时候采用了标记-压缩的方式。读者可以联想一下咱们的Windows的磁盘整理,硬盘使用了一段时间后进行磁盘扫描是不是会出现很多不连续的空余空间,定期做一次磁盘扫描将已经使用的空间连续起来,不使用的空间腾出来,这样对于CPU去硬盘上读取文件,空间利用率上更加方便和快捷,在这里Old串行GC采用这种算法,也是这个道理也是这个道理。不过要说明一点就是采用此算法花费的时间要长一些。所以这也是为什么Full GC比较耗时的原因之一。
2):资深并行GC,这个和Eden的并行GC也没太大区别,除了在具体细节算法上和一般并行有区别外,其他并无太大变化,相当于多线程的资深串行GC。
3):资深并发GC,学名是Concurrent Mark Sweep GC(简称CMS GC),从名字中大家可能猜到了,这是在Old区域采用并发的方式进行回收,在整个CMS GC过程中,要经过多个阶段扫描并标记识别对象是否真正要回收,在整个扫描,标记的阶段过程中,只有极小一段时间处于应用程序中断的状态,其他标记阶段都是同时和应用程序一同工作着的。在某点上(比如压缩时)一般都不得不停止其他操作以完成特定的任务,但是因为其他应用程序可进行其他的后台操作,所以中断其他处理的实际时间大大降低。在Old带采用此策略GC的时候,关系到整个堆的命运,在Eden带必须使用ParNew的方式进行GC,否则Old带的GC会受到Eden的GC影响,导致整个堆区域的GC效率下降。
各位看到了,公司面对不同时期的员工,有着不同的策略收拾这些“人才”,确确实实揭露了一个事实,当你对公司没有用处的时候,他总会有机会,有办法,有理由,有借口,有手段让你离开的。毕竟长江后浪推前浪嘛,前浪早晚得死在沙滩上。
对于Full GC的概念其实是这样的,Full GC干了什么事呢?它将Eden区、幸存区、Old区,乃至非对区域的方法区(持久带),不同区域采取不同策略,都进行了相关的GC。所以咱们微观地看来,面对这么多区域,又有标记啦、扫描啦、停止运行程序啦等等一些列的动作都是需要很长时间的这也是为什么说Full GC在大多数情况下最好少触发。哪个公司也不愿意过个一两天就进行人事大换血,大调用,都愿意稳定。
还要交代一句的就是Full GC的一些条件,上面只是说了一种情况下引发了Full GC,其实呢还有另外几种情况,就是当classloader加载完类信息后,方法区持久带的空间不足以容纳类信息了,那么也会触发一次Full GC;还有就是咱们之前提到的CMS GC回收策略,当Old带正在进行CMS GC的同时来了新的对象了,而此时此刻呢,很不巧,Old带有容纳不了这个新的大对象,此时就得审时度势了,得了,Full一把吧;还有就是经过几次Eden区、幸存区域、Old带的回收下来发现Minor GC后放到Old带的对象平均大小要大于Old的剩余空间,JVM认为这个不太合理,就Full GC了,这算是一种JVM自我优化和保护的一种策略吧。
1. 总结
其实JVM管理内存的方式概念比较多,比如还可以深入到回收算法:拷贝、标记、压缩等等细节。这次咱暂时没有详细提起,仅仅是浅尝辄止,意在用通俗的语言让大家看看JVM到底如何进行分配资源和回收资源的。其实从中不难看出,一个JVM真的为咱们做了不少事情,就是为了不让开发人员手工回收自己开辟的资源,原来他在底层为我们默默的做了这么多事情。从中也能看出一些残酷的事实,还是时刻提醒我们,居安思危,无论身在多么好的企业、公司,最好有一种危机意识。效益在好的公司也有经历严酷寒冬的时候,说不定哪天公司的HR真的就像垃圾回收器(垃圾这个词~可能有点残酷,有点不近人情)一样,将他们认为无用的人员就给咔嚓了。无论你曾经有什么重大的贡献,请记住,那是曾经,不代表将来。这次笔者感觉JVM对于内存的管理比作公司,其实稍微有那么一点点勉强,毕竟现实生活中真正运作的公司谁也不敢那么做(有劳动法,公司再怎么牛也不敢像JVM这么绝决)。此时此刻倒是想起一段新闻,某某网去年一直高调招揽人才,很多人才都是通过猎头给出了比较吸引人的待遇,挖过去的,现在的形式大家都知道是团购网的寒冬时期,那个某某网又低调大手笔裁员。这个难道就是现实生活中JVM的垃圾回收的写照?
以下是一些笔者查看的资料连接,里面对于算法有更详细的描述,希望也能对大家有帮助,当然JVM的话题永远不会就此结束,因为它涉及到的东西确实太多了。
http://blog.csdn.net/lyerliu/article/details/6311709
http://www.7dtest.com/site/html/74/t-4574.html
http://www.mapfilm.com/articles/2011/06/13/1307954329185.html
http://weich-javadeveloper.iteye.com/blog/548253
http://www.wangchao.net.cn/bbsdetail_1758292.html
http://www.ibm.com/developerworks/cn/java/j-nativememory-linux/
http://info.53dns.com/html/wlbc/JAVAbc_1223_6807.html
http://ayufox.iteye.com/blog/214411
http://hi.baidu.com/xuwanbest/blog/item/0587d82f2c44a73d1e30892e.html
http://suhuanzheng7784877.iteye.com/blog/1000646
http://suhuanzheng7784877.iteye.com/blog/1000635
http://suhuanzheng7784877.iteye.com/blog/964784
林昊先生的《Java分布式应用与实践》
李刚老师的《Java程序员基础16课》
周志明《Java虚拟机》
相关推荐
Sun JVM原理与内存管理
JVM 内存管理之道 JVM垃圾回收机制 JVM GC组合 JVM 内存监控工具
详细介绍了JVM 内存管理相关知识 内存空间( VM运行时数据区域) ◦ 内存结构 ◦ 内存空间 内存分配 内存回收(GC) 内存分析工具
主要是JVM内存分配及简单的JVM性能调优
java虚拟机jvm及Tomcat中的jvm有关内存的设置与调优
sun公司出版的jvm运行机制管理丛书,需要深入jvm的同学可以下载来看看
JVM内存调优,java内存管理总结。包含新生代、老年代等详解。还有垃圾回收收集器详解。
(二)MATJVM 内存分析工具.MAT JVM 内存分析工具.MAT JVM 内存分析工具.(二)MATJVM 内存分析工具.MAT JVM 内存分析工具.MAT JVM 内存分析工具.
1.jvm内存结构及功能概述 2.Jvm Heap 内存结构 3.Jvm 的内存分配
性能测试,线程的 dump 看到线程的 死锁,等待 运行状态
jvm内存反洗工具:
程序运行要用到的内存大于虚拟机能提供的最大内存就发生内存溢出了, 内存溢出的问题要看业务和系统大小而定,对于某些系统可能内存溢出不常见,但某些系统还是很常见的解决的方法
对于深入掌握的java的人士,对于内存管理是不得不看的东西
idea插件JVM内存工具JProfiler11,下载完,即可导入idea,可idea快捷打开使用。
jvm内存模型,jvm脑图,jvm调优,jvm垃圾回收算法,jvm垃圾回收器,逃逸算法等总结。
mat用于分析JVM的内存dump信息,是在JVM内存异常时进行内存分析的好工具
JVM内存模型深度剖析与优化
jvm 内存监控
JVM内存管理和垃圾回收 JVM内存管理和垃圾回收 JVM内存管理和垃圾回收