玩命加载中🤣🤣🤣

快速手册之jvm


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成为默认垃圾回收器

7款经典垃圾回收器

组合选择

  • 单 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 整体上采用标记-整理算法 局部是通过复制算法,不会产生内存碎片

文章作者: 👑Dee👑
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 👑Dee👑 !
  目录