JVM 最佳学习笔记<二>--- 垃圾收集器与内存分配策略
前言
本笔记参照了周志明`《深入理解Java虚拟机:JVM高级特性与最佳实践》
`第三版,读完之后受益匪浅,让我对Java虚拟机有了一个深刻的认识,这也是Jvm书籍中最好的读物之一。
1. 判断对象是否已死
引用计数算法
给对象中添加一个引用计数器,每当一个地方引用它时,计数器就+1;当引用失效时,计数器就-1;任何时刻计数器为0的对象就是不可能再被使用的。
引用计数算法的缺陷:它很难解决对象之间相互循环引用的问题。案例代码如下:
运行结果:
public class Test {
public static void main(String[] args) {
Test a = new Test();
a = null;
}
}
public class Test {
public static Test s;
public static void main(String[] args) {
Test a = new Test();
a.s = new Test();
a = null;
}
}
public class Test {
public static final Test s = new Test();
public static void main(String[] args) {
Test a = new Test();
a = null;
}
}
JNIEXPORT void JNICALL JavacompecuyujnirefdemoMainActivity_newStringNative(JNIEnv *env, jobject instance,jstring jmsg) {
...
// 缓存String的class
jclass jc = (*env)->FindClass(env, STRING_PATH);
}
$ ps -ef|grep java
501(UID) 26699(PID) 25587(PPID) 0(CPU) 7:19PM ??
jmap -heap 26699
$ java -XX:+PrintCommandLineFlags -version
//5g-8g
-XX:InitialHeapSize=536870912 -XX:MaxHeapSize=8589934592 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
java version "1.8.0_172"
Java(TM) SE Runtime Environment (build 1.8.0_172-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.172-b11, mixed mode)
```
GC 调优策略
降低 Minor GC 频率
降低 Full GC 的频率
减少创建大对象:在平常的业务场景中,我们习惯一次性从数据库中查询出一个大对象用于 web 端显示。例如,我之前碰到过一个一次性查询出 60 个字段的业务操作,这种大对象如果超过年轻代最大对象阈值,会被直接创建在老年代;即使被创建在了年轻代,由于年轻代的内存空间有限,通过`
Minor GC
之后也会进入到老年代。这种大对象很容易产生较多的
Full GC
`。增大堆内存空间:在堆内存不足的情况下,增大堆内存空间,且设置初始化堆内存为最大堆内存,也可以降低`
Full GC
`的频率。
选择合适的 GC 回收器
理解GC日志
通过两张图非常明显看出GC日志(YoungGC和FullGC)构成:
Young GC日志:
Full GC日志:
"33.125"和"100.667":代表了GC发生的时间,这个数字的含义是从Java虚拟机启动以来经过的秒数。这部分时间未作显示
`
[GC
和
[Full GC
说明了这次垃圾收集停顿类型,并不是来区分**新生代GC**还是**老年代GC**。如果是
Full
,说明这次GC是发生了
Stop-The-World
。如果是调用
System.gc()方法
所触发的收集,那么这里将显示
[Full GC(System)
``
[DefNew
、
[Tenured
和
[Perm
` 表示所发生GC的区域,与GC收集器相关。
* Serial收集器,新生代名称为`Default New Generation(DefNew)
`。
* ParNew收集器,新生代名称为`Parallel New Generation(ParNew)
`。
* Parallel Scavenge收集器,新生代名称为`PSYoungGen
`
334480K->4736K(334480K) 含义是 GC前该内存区域已使用容量->GC后该内存区域已使用容量(该内存区域总容量)
0.0209890 secs 表示该区域GC所占用的时间,单位是秒。有的收集器会给出更具体的时间数据。如"`
"[Times: user=0.01 sys=0.00,real=0.01 secs]"
` 分别表示用户态消耗CPU时间、内核态消耗的CPU事件和操作从开始到结束所经过的墙钟时间新生代总空间为334480K=Eden区+1个Survivor区的总容量
参数设置
`
-XX:+PrintGC
与
-verbose:gc
是一样的,可以认为
-verbose:gc
是
-XX:+PrintGC
`的别名,用于垃圾收集GC时的信息打印。`
-XX:+PrintHeapAtGC
` 打印垃圾收集GC中堆的信息`
-XX:+PrintGCDetails
` 在垃圾收集行为GC时,打印内存回收日志,并且在进程退出的时候输出当前的内存各区域分配情况`
-Xmn
` 分配新生代`
-XX:SurvivorRatio=8
`决定了新生代中Eden区和一个Survivor的空间比例是8:1
新生代GC(Minor GC)和老年代GC(FullGC)区别
新生代(Minor GC):指发生在新生代的垃圾收集动作,因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度较快
老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC,经常会伴随至少一次的Minor GC,且Major GC的速度一般会比Minor GC慢10倍以上
4. 内存分配与回收策略
大对象直接进入老年代
所谓的大对象是指,需要大量连续内存空间的java对象,典型的是那种很长的字符串以及数组。
长期存活的对象将进入老年代
如果对象在Eden出生并经过第一次Minor GC后仍然存活,并且能被Survivor容纳的话,将被移动到Survivor空间中,并且对象年龄设为1。对象在Survivor区中每“熬过”一次Minor GC,年龄就增加1岁,当它的年龄增加到一定程度(默认为15岁),就将会晋升到老年代中。
5. 本章小结
内存回收与垃圾收集器在很多时候都是影响系统性能、并发能力的主要因素之一,虚拟机之所以提供多种不同的收集器以及提供大量的调节参数,是因为只有根据实际应用需求、实现方式选择最优的收集方式才能获取更高的性能。
版权声明: 本文为 InfoQ 作者【Loubobooo】的原创文章。
原文链接:【http://xie.infoq.cn/article/69af3d34a99f1460740af8ff3】。文章转载请联系作者。
评论