内存优化篇
- memory、GC、Performance
GC(Garbage Collection):垃圾回收是jvm提供的一种垃圾回收机制,回收的是无任何对象引用指向的内存空间。垃圾回收释放的是对象占据的内存(一般为堆内存);一般在程序空闲时间不定时回收。
java中的对象引用:
-
强引用(Strong Reference):如Object obj = new Object(),只要对象还存在,对象引用的地址就永远不会被回收。
-
软引用(Soft Reference):定义的对象为非必需的。在系统内存不够用时,软引用关联的对象被垃圾收集器回收。JDK1.2之后提供SoftReference类来实现软引用。
-
弱引用(Weak Reference):定义的对象是非必需的,强度比软引用弱,弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收弱引用对象引用的内存。在JDK1.2之后,提供WeakReference类来实现弱引用。
-
虚引用(Phantom Reference):最弱的一种引用关系,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的是希望能在这个对象被收集器回收时收到一个系统通知。JDK1.2之后提供PhantomReference类来实现虚引用。
-
检索回收的对象
jvm会查询所有未销毁的对象->回收垃圾对象占用的内存。 -
垃圾对象的确认(引用计数)
其实不管是Object-C还是java,都是通过一个引用计数的原理来跟踪一个对象的使用情况,每个对象从被创建那一时刻起就会有一个引用计数器伴其一生。
- 对象被初始化时计数器值为1 -> 被另一个对象引用时加1 -> 引用的对象为null时,减1 -> 计数为0时jvm视其为垃圾。
-
优点:引用计数收集器执行简单,判定效率高,交织在程序运行中。对程序不被长时间打断的实时环境比较有利(OC的内存管理使用该算法)。
-
缺点: 难以检测出对象之间的循环引用。同时,引用计数器增加了程序执行的开销。所以Java语言并没有选择这种算法进行垃圾回收。
-
早期的JVM使用引用计数,现在大多数JVM采用对象引用遍历(根搜索算法)。
-
Java的堆内存(Java Heap Memory)
Java的堆内存基于Generation算法(Generational Collector)划分为新生代、年老代和持久代。新生代又被进一步划分为Eden和Survivor区,最后Survivor由FromSpace(Survivor0)和ToSpace(Survivor1)组成。所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。
- 堆内存分配区域:
1.年轻代(Young Generation)
几乎所有新生成的对象首先都是放在年轻代的。新生代内存按照8:1:1的比例分为一个Eden区和两个Survivor(Survivor0,Survivor1)区。大部分对象在Eden区中生成。当新对象生成,Eden Space申请失败(因为空间不足等),则会发起一次GC(Scavenge GC)。回收时先将Eden区存活对象复制到一个Survivor0区,然后清空Eden区,当这个Survivor0区也存放满了时,则将Eden区和Survivor0区存活对象复制到另一个Survivor1区,然后清空Eden和这个Survivor0区,此时Survivor0区是空的,然后将Survivor0区和Survivor1区交换,即保持Survivor1区为空, 如此往复。当Survivor1区不足以存放 Eden和Survivor0的存活对象时,就将存活对象直接存放到老年代。当对象在Survivor区躲过一次GC的话,其对象年龄便会加1,默认情况下,如果对象年龄达到15岁,就会移动到老年代中。若是老年代也满了就会触发一次Full GC,也就是新生代、老年代都进行回收。新生代大小可以由-Xmn来控制,也可以用-XX:SurvivorRatio来控制Eden和Survivor的比例。
2.年老代(Old Generation)
在年轻代中经历了N次垃圾回收后仍然存活的对象,就会被放到年老代中。因此,可以认为年老代中存放的都是一些生命周期较长的对象。内存比新生代也大很多(大概比例是1:2),当老年代内存满时触发Major GC即Full GC,Full GC发生频率比较低,老年代对象存活时间比较长,存活率标记高。一般来说,大对象会被直接分配到老年代。所谓的大对象是指需要大量连续存储空间的对象,最常见的一种大对象就是大数组。比如:
byte[] data = new byte[410241024]
这种一般会直接在老年代分配存储空间。
当然分配的规则并不是百分之百固定的,这要取决于当前使用的是哪种垃圾收集器组合和JVM的相关参数。
3.持久代(Permanent Generation)
用于存放静态文件(class类、方法)和常量等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate 等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。对永久代的回收主要回收两部分内容:废弃常量和无用的类。
永久代空间在Java SE8特性中已经被移除。取而代之的是元空间(MetaSpace)。因此不会再出现“java.lang.OutOfMemoryError: PermGen error”错误。
- 堆内存分配策略明确以下三点:
(1)对象优先在Eden分配。
(2)大对象直接进入老年代。
(3)长期存活的对象将进入老年代。
- 对垃圾回收机制说明以下三点:
新生代GC(Minor GC/Scavenge GC):发生在新生代的垃圾收集动作。因为Java对象大多都具有朝生夕灭的特性,因此Minor GC非常频繁(不一定等Eden区满了才触发),一般回收速度也比较快。在新生代中,每次垃圾收集时都会发现有大量对象死去,只有少量存活,因此可选用复制算法来完成收集。
老年代GC(Major GC/Full GC):发生在老年代的垃圾回收动作。Major GC,经常会伴随至少一次Minor GC。由于老年代中的对象生命周期比较长,因此Major GC并不频繁,一般都是等待老年代满了后才进行Full GC,而且其速度一般会比Minor GC慢10倍以上。另外,如果分配了Direct Memory,在老年代中进行Full GC时,会顺便清理掉Direct Memory中的废弃对象。而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记—清除算法或标记—整理算法来进行回收。
新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。当连续分配对象时,对象会逐渐从Eden到Survivor,最后到老年代。
用Java VisualVM来查看,能明显观察到新生代满了后,会把对象转移到旧生代,然后清空继续装载,当老年代也满了后,就会报outofmemory的异常,
网友评论