JVM速查
常见垃圾回收算法
- 引用计数
- 一般不采用
- 复制
- 年轻代:复制之后有交换,谁空谁是to,优点是没有碎片;缺点是浪费空间
- 标记清除
- 老年代:先标记,后清除。优点是对象没有大面积复制;缺点是内存碎片
- 标记整理
- 老年代:先标记清除,再压缩。耗时间
GCRoots
枚举根节点做可达性分析(根搜索路径)
- 虚拟机栈引用的对象
- 方法区中的类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(Native 方法)引用的对象
常用参数
- -Xms
- 初始大小内存,默认为物理内存1/64
- 等价于 -XX:InitialHeapSize
- -Xmx
- 最大分配内存,默认为物理内存1/4
- 等价于 -XX:MaxHeapSize
- -Xss
- 设置单个线程栈大小,一般默认为512k~1024k
- 等价于 -XX:ThreadStackSize
- -Xmn
- -XX:MetaspaceSize
- 常用参数配置参考
- -Xms128m -Xmx4096m -Xss1024k -XX:MetaspaceSize=512m -XX:+PrintCommandLinceFlags -XX:+PrintGCDetails -XX:+UseSerialGC
- -XX:PrintGCDetails
- jvm结束时打印GC详情
- -XX:SurvivorRatio
- 设置新生代中eden和s0/s1空间的比例,一般默认
- -XX:SurvivorRatio=8, Eden:S0:S1=8:1:1
- -XX:NewRatio
- 配置年轻代与老年代在堆结构的占比
- 默认 -XX:NewRatio=2 2:1,年轻代占整个堆的1/3
- -XX:MaxTenuringThreshold
- 设置垃圾最大年龄
强软弱虚
- 强:死都不收
- 软:内存不够要回收
- 弱:只要gc就回收
- 虚:用get()总是返回null,必须和引用队列 ReferenceQueue 结合使用
使用场景
假如有一个应用需要读取大量的本地图片:
- 如果每次读取图片都从硬盘读取则会严重影响性能,
- 如果一次性全部加载到内存中又可能造成内存溢出。
此时使用软引用可以解决这个问题。
设计思路是:用一个 HashMap 来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM 会自动回收这些缓存图片对象所占用的空间,从而有效地避免了 OOM 的问题。
Map<String, SoftReference<Bitmap>> imageCache = new HashMap<String, SortReference<Bitmap>>();
WeakHashMap案例
代码示例
public class WeakHashMapDemo {
public static void main(String[] args) {
Integer a = new Integer(1);
HashMap<Integer, String> map1 = new HashMap<>();
map1.put(a, "a");
a = null;
System.out.println(map1);
System.gc();
System.out.println(map1);
System.out.println("=============");
Integer b = new Integer(2);
WeakHashMap<Integer, String> weakHashMap = new WeakHashMap<>();
weakHashMap.put(b, "b");
b = null;
System.out.println(weakHashMap);
System.gc();
System.out.println(weakHashMap);
}
}
引用队列 ReferenceQueue
当软弱虚引用 gc 之后,会被放入引用队列
当关联的引用队列中有数据的时候,意味着引用直想的堆内存中的对象被回收。通过这种方式,JVM允许我们在对象被销毁后,做一些自己想做的事情。
代码示例
public class ReferenceQueueDemo {
public static void main(String[] args) {
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>(); //引用队列
Object o = new Object();
WeakReference<Object> objectWeakReference = new WeakReference<>(o, referenceQueue);
//gc之前
System.out.println("o = " + o);
System.out.println("objectWeakReference.get() = " + objectWeakReference.get());
System.out.println("referenceQueue.poll() = " + referenceQueue.poll()); //引用队列为空
System.out.println("=============");
o = null;
System.gc();
System.out.println("o = " + o);
System.out.println("objectWeakReference.get() = " + objectWeakReference.get());
System.out.println("referenceQueue.poll() = " + referenceQueue.poll());
}
}
o = java.lang.Object@2a33fae0
objectWeakReference.get() = java.lang.Object@2a33fae0
referenceQueue.poll() = null
=============
o = null
objectWeakReference.get() = null
referenceQueue.poll() = java.lang.ref.WeakReference@707f7052
虚引用PhantomReference
public class PhantomReferenceDemo {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
PhantomReference<Object> phantomReference = new PhantomReference<>(o, referenceQueue);
System.out.println("o = " + o); //不为null
System.out.println("phantomReference.get() = " + phantomReference.get()); //null
System.out.println("referenceQueue.poll() = " + referenceQueue.poll()); //null
System.out.println("=========");
o = null;
System.gc();
Thread.sleep(500);
System.out.println("o = " + o); //null
System.out.println("phantomReference.get() = " + phantomReference.get()); //null
System.out.println("referenceQueue.poll() = " + referenceQueue.poll()); //不为null
}
}
o = java.lang.Object@2a33fae0
phantomReference.get() = null
referenceQueue.poll() = null
=========
o = null
phantomReference.get() = null
referenceQueue.poll() = java.lang.ref.PhantomReference@707f7052
OOM
- StackOverflowError
- Java heap space
- GC overhead limit execceded
- 超过98%的时间用来做GC并且回收不到2%的堆内存
- Direct buffer memory
代码示例
//-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m
public class DirectBufferMemoryDemo {
public static void main(String[] args) {
System.out.println("maxDirectMemory: " + sun.misc.VM.maxDirectMemory() / 1024 / 1024 + "MB");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ByteBuffer bb = ByteBuffer.allocateDirect(6 * 1024 * 1024);
}
}
- unable to create new native thread
- 应用创建了太多线程(linux默认单个进程可以创建1024个线程)
垃圾回收器
- Serial
- 为单线程环境设计且只使用一个线程进行垃圾回收,会暂停所有的用户线程。所以不适合服务器环境
- Serial GC-Serial Old (标记-压缩算法)
- Parallel
- 多个垃圾收集线程并行工作,此时用户线程是暂停的,适用于科学计算/大数据处理等弱交互场景
- Parallel Scavenge GC-Parallel Old GC
- CMS
- 用户线程和垃圾收集线程同时执行(不一定是并行,可能是交替执行),不需要停顿用户线程互联网公司多用它,适用对响应时间有要求的场景
- ParNew GC(标记-压缩算法) - CMS GC
- G1
- 将堆内存分割成不同得区域然后并发的对其进行垃圾回收
- 整理内存过程的垃圾回收器,不会产生很多内存碎片
- STW 更可控,在停顿时间上添加了预测机制,用户可以指定期望停顿时间
- jdk9成为默认垃圾回收器
组合选择
- 单 CPU 或小内存,单机程序
- -XX:+UseSerialGC
- 多 CPU,需要最大吞吐量,如后台计算型应用
- -XX:+UseParallelGC 或者
- -XX:+UseParallelOldGC
- 多 CPU,追求低停顿时间,需快速响应如互联网应用
- -XX:+useConcMarkSweepGC
- -XX:+ParNewGC
参数 | 新生代垃圾收集器 | 新生代算法 | 老年代垃圾收集器 | 老年代算法 |
---|---|---|---|---|
-XX:+UseSerialGC | SerialGC | 复制 | SerialOldGC | 标整 |
-XX:+UseParNewGC | ParNew | 复制 | SerialOldGC | 标整 |
-XX:+UseParallelGC/-XX:+UseParallelOldGC | Parallel[Scavenge] | 复制 | Parallel Old | 标整 |
-XX:+UseConcMarkSweepGC | ParNew | 复制 | CMS + Serial Old 的收集器组合 (Serial Old 作为 CMS 出错的后备收集器) |
标清 |
-XX:+UseG1GC | G1 整体上采用标记-整理算法 | 局部是通过复制算法,不会产生内存碎片 |