一、对象的生命周期
java对象在虚拟机中经过三个过程:
1.首次加载类型 => 初始化类变量
2.创建对象 (有四种方式)
2.1 new 操作
2.2 Class或java.lang.reflect.Constructor.newInstance()
2.3 调用对象的clone()
(类需要实现java.lang.Cloneable接口,才能调用close(),如果是Object.clone(),则返回一个新对象,并新对象的属性由原对象来赋值,
注意,如果对象的属性是对象类型,则新对象的属性也只是赋值对象引用,既是Object.clone()只是浅复制。)
2.4 对象的反序列化 ObjectInutStream.readObject()
(先分配内存,再初始化实例变量的值,
如果没有显式地调用父类的构造方法,初始化对象的构造方法时,先默认调用父类的无参构造方法)
3. 垃圾收集
二、对象的垃圾收集
对象是创建在堆中的,一般堆是分代存储对象,分为三个部分:
1. 年轻代(分为 Eden, From Survivor Space, To Survivor Space)
2. 年老化(步骤:标记(MARK),消除(SWEEP),合并(Compact)
3. 永久代
年经化
新创建对象是,会先放到Eden区,当Eden区的空间满了,会 把Eden区存活的对象搬到From区,
下次Eden区又满了,会把Eden和From区的存活对象搬到To区,
下次Eden区又满了,会把Eden和To区的存活对象搬到To区,以此类推。
当多次(默认15次)垃圾收集之后,对象还在年轻代,则会搬到年老代。
年轻代每次垃圾收集又称Young GC
年老代
当年老代里的空闲空间存在碎片,则使得没有足够空间时去存放新放进来的年老代对象,需要进行一个Full GC,需要垃圾收集所有对象,将存活的对象移动到空间的前部分。标记阶段把所有存活的对象标记出来,清除阶段释放所有死亡的对象,合并阶段 把所有活着的对象合并到年老代的前部分,把空闲的片段都留到后面。设计的选型为合并,减少内存的碎片。
虚拟机参数:
-Xms512m 堆heap初始化值
-Xmx512m 堆heap最大值
-Xmn300m 年轻代的堆heap大小值
-Xss512k 每个线程的Stack大小
-XX:PermSize=64m 永久代的最小值
-XX:MaxPermSize=64m 永久代的最大值
-XX:NewSize=100m 设置年轻代的大小值
-XX:MaxNewSize=100m 设置年轻代的最大值
-XX:NewRatio=4 即 年轻代 : 年老代 = 1:4
-XX:SurvivorRatio=4 即 Eden : From : To Survivor Space = 4 : 1 : 1
-XX:+UseConcMarkSweepGC 开启虚拟机收集器:CMS,默认启动的回收线程数是(CPU数量+3)/ 4,也就是当CPU在4个以上时,并发回收时垃圾收集线程最多占用不超过25%的CPU资源。但是当CPU不足4个时(譬如2个),那么CMS对用户程序的影响就可能变得很大,如果CPU负载本来就比较大的时候,还分出一半的运算能力去执行收集器线程,就可能导致用户程序的执行速度忽然降低了50%,不过一般服务器都有8核或16核或更高。
-XX:CMSInitiatingOccupancyFraction=58 ,默认值是68,即年老代的使用比率达到68%后,执行CMS垃圾收集。
-XX:+PrintGCDetails 垃圾收集打印收集详情
-XX:+PrintGCTimeStamps 垃圾收集打印详细时间,只是打印垃圾收集的持续时间
-XX:+PrintGCDateStamps 垃圾收集打印出收集的某个时间时刻,具体到哪个一分哪一秒发生的垃圾收集。
-Xloggc:${LOGS_DIR}/gc.log 垃圾收集打印的日志路径
为保留内存溢出的场景,可使用以下参数:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:\service\test-service\gc.hprof
当内存溢出时,内存的对象使用情况,会输出到文件 gc.hprof ,我们可以使用ecplise的MAT工具分析内存的对象使用情况。
使用jmap命令导出jvm堆的内存快照,导出结果文件使用MAT工具分析:
jmap -dump:format=b,file=/opt/deploy-service/heap.bin pid
使用-Xloggc:./logs/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps
gc.log的内容:
2017-06-12T11:54:57.955+0800: 11.556: [GC2017-06-12T11:54:57.955+0800: 11.556: [DefNew: 13886K->721K(14656K)
, 0.0080210 secs] 43593K->30940K(47044K), 0.0081070 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
其中
GC,是年经代的垃圾收集;
[DefNew: 13886K->721K(14656K), 0.0080210 secs],DefNew(不同的GC这个年轻代名字会稍有不同)年轻代占内存14656K,已使用13886K->721K,减少了13000K,历时0.0080210秒;
43593K->30940K(47044K),堆占内存47044K, 已使用43593K->30940K,减少了13000K;
从以上可以看出经过年轻代垃圾收集,年轻代的对象是真实收集掉了,而不是转到年老代。
2017-06-12T11:55:00.888+0800: 14.489: [Full GC2017-06-12T11:55:00.888+0800: 14.489: [Tenured: 26390K->25631K(32388K)
, 0.1790040 secs] 29094K->25631K(47044K), [Perm : 32127K->32127K(32128K)], 0.1801110 secs] [Times: user=0.18 sys=0.00
, real=0.18 secs]
其中
Full GC,年老代和永久代的垃圾收集;
[Tenured: 26390K->25631K(32388K), 0.1790040 secs],Tenured年老代(也有叫年终代),其余部分跟年轻代垃圾收集类似;
[Perm : 32127K->32127K(32128K)], 0.1801110 secs],Perm永久代,其余部分跟年轻代垃圾收集类似;
[Times: user=0.18 sys=0.00, real=0.18 secs],user是用户态耗费的时间,sys是内核态耗费的时间,real是整个过程实际花费的时间,user + sys表示一个CPU需要的时间,由于可能有多核CPU的情况,可能user + sys会比real多几倍,不过我这里是单核虚拟机,所以时间是相等的。
垃圾收集的两种方式
虚拟机区分对象和垃圾有两个基本的方式:引用计数和跟踪。
1 引用计数
垃圾收集器为每个对象保存一个计数来区分活动对象和垃圾对象,这个方法可以很快地执行,但无法检测出循环引用 的情况(即两个或多个对象相互引用的情况),所以这个方法已被放弃不用了
2 跟踪收集器
跟踪收集器从根节点开始的对象引用 图,在追踪中遇到的对象以某种方式打上标记。当追踪结束时,没有被标记的对象则是不可触及的,从而可以被收集。
对象的状态
上面说过,对象在垃圾收集需要经过标记,再进行清除,基于这点,在虚拟机垃圾收集看来,对象有3种状态:可触及的,可复活的,不可触及的
1 可触及的
对象被创建后,就处理于可触及状态,因为从根节点扫描该对象,对象是可以被追踪到的,每个对象都是从可触及状态开始它的生命周期。
2 可复活的
一旦程序释放了所有对该对象的引用,经过第一次从根节点的全部扫描,触及不到该对象,垃圾收集器必须检查已检测出不再被引用的对象是否声明了终结方法finalize(),因为有可能执行了所有不再被引用 的对象的终结方法后,该对象有可能“复活”了,被复活不仅是被该对象本身复活,而是指所有不再被引用 的对象的终结方法里,有可能需要用到该对象来释放、处理对象的终结处理而复活了该对象。
在第一次扫描之后,没有被引用 的对象,会被收集器加以标记。
3 不可触及的
经过第一次从根节点扫描,而且执行了不再被引用 的对象的终结方法后,第二次只会扫描第一次扫描出的不再被引用 的对象结果,如果经过第二次追踪,还是不再被引用的对象,则是不可触及状态,已经是可以被垃圾收集器回收内存了。
的1.2之前,对象对于垃圾收集器来说只有以上3种状态。
在1.2以后,扩展了3种状态: 软可触及、弱可触及和虚可触及,则对象是有6种可触及状态的。
1 强可触及
对象可以从根节点不通过任何引用对象搜索到。对象生命周期从强可触及 状态开始,并且只要有根节点或者另外一个强可触及 对象引用它,就保持强可触及状态。垃圾收集器不会回收强可触及状态占据的空间的
2 软可触及的(SoftReference)
对象不是强可触及的,但是可以从根节点开始 通过 一个或多个软引用对象触及。垃圾收集器可能回收软可触及的对象所占据的空间。如果发生了收集,会清除软可触及的软引用对象,并该软引用对象加入队列
3 弱可触及(WeakReference)
对象既不是强可触及,也不是软可触及的,但是可以从根节点开始 通过 一个或多个弱引用对象触及。垃圾收集器必须回收弱可触及的对象所占据的空间。如果发生了收集,会清除弱可触及的弱引用对象,并该弱引用对象加入队列
4 可复活的
对象既不是强可触及的、软可触及的,也不是弱可触及,但是仍然可以通过执行某些终结方法复活到这几种状态之一
5 虚可触及(影子可触及,PhantomReference)
对象既不是强可触及 的、软可触及的,也不是弱可触及,并已经 被断定不会被 任何终结方法复活,可以从根节点开始 通过 一个或多个虚引用对象触及。一旦一个虚可触及引用的对象变成 虚可解及状态,垃圾收集器立即把该引用对象加入队列。垃圾收集器从不会清除一个虚引用,所有的虚引用都必须由程序明确地清除。
6 不可触及
一个对象不是强可触及、软可触及、弱可触及,也不 是虚可触及,并且它不可复活。不可触及 的对象已经 准备好被回收。