一文理清JVM和GC(上)

本文主要介绍JVM和GC解析
本文较长,分为上下篇(可收藏,勿吃尘)
如有需要,可以参考
如有帮助,不忘 点赞

一、前期预热

1)JVM内存体系

其中方法区被JVM中多个线程共享,比如类的静态常量就被存放在方法区,供类对象之间共享,虚拟机栈本地方法栈程序计数器是每个线程独立拥有的,不会与其他线程共享。所以Java在通过new创建一个类对象实例的时候,一方面会在虚拟机栈中创建一个对该对象的引用,另一方面会在堆上创建类对象的实例,然后将对象引用指向该对象的实例。对象引用存放在每一个方法对应的栈帧中。

图片来源于网上
  • 虚拟机栈:虚拟机栈中执行每个方法的时候,都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。
  • 本地方法栈:与虚拟机栈发挥的作用相似,相比于虚拟机栈为Java方法服务,本地方法栈为虚拟机使用的Native方法服务,执行每个本地方法的时候,都会创建一个栈帧用于存储局部变量表,操作数栈,动态链接,方法出口等信息。
  • 方法区:它用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据,方法区在JDK1.7版本及之前称为永久代,从JDK1.8之后永久代被移除。
  • 堆:堆是Java对象的存储区域,任何new字段分配的Java对象实例和数组,都被分配在了堆上,Java堆可使用 - Xms / - Xmx 进行内存控制,从JDK1.7版本之后,运行时常量池从方法区移到了堆上。
  • 程序计数器:指示Java虚拟机下一条需要执行的字节码指令。

2)JAVA8之后的JVM

从图中我们可以看出JAVA8的JVM 用元空间取代了永久代


---

---
---

3)GC作用域

---

4)常见垃圾回收算法

  • 引用计数法:

JVM的实现一般不采用这种方式

---

缺点:
1. 每次对对象赋值时均要维护引用计数器,且计数器本身也有一定的消耗;
2. 较难处理循环引用;

  • 复制算法:

Java 堆从GC的角度可以细分为:新生代(Eden区、From Survivor区 和 To Survivor区)和 老年代。
特点:
复制算法不会产生内存碎片,但会占用空间。用于新生代。

---

MinorGC的过程(复制 --> 清空 --> 互换)

  1. 复制: (Eden、SurvivorFrom 复制到 SurvivorTo,年龄加1)
    首先,当Eden区满的时候会触发第一次GC,把还活着的对象拷贝到SurvivorFrom区,当Eden区再次触发GC的时候会扫描Eden区域和From区域,对这两个区域进行垃圾回收,经过这次回收后还存活的对象,则直接复制到To区域(如果有对象的年龄已经到达了老年的标准,则复制到老年代区),同时把这些对象的年龄加1。
  2. 清空:(清空Eden、SurvivorFrom)
    清空Eden和SurvivorFrom中的对象,也即复制之后有交换,谁空谁是to。
  3. 互换:(SurvivorTo和SurvivorFrom 互换)
    最后,SurvivorTo和SurvivorFrom 互换,原SurvivorTo成为下一次GC是的SurvivorFrom区。
  • 标记清除法

算法分成标记和清除两个阶段,先标记出要回收的对象,然后统一回收这些。
特点:
不会占用额外空间,但会扫描两次,耗时,容易产生碎片,用于老年代

---
  • 标记压缩法

优点:
没有内存碎片,可以利用bump
缺点:
需要移动对象的成本,用于老年代
原理:

  1. 标记:与标记清除一样


    ---

    2.压缩:再次扫描,并往一段滑动存活对象


    ---

二、正文接入

1)判断对象是否可回收

  • 引用计数法

Java中,引用和对象是有关联的。如果要操作对象则必须用引用进行。
因此,很显然的一个方法就是通过引用计数来判断一个对象是否可以回收。简单来说就是给对象添加一个引用计数器。每当有一个地方引用它,计数器的值加1,每当有一个引用失效时,计数器的值减1。
任何时刻计数器值为0的对象就是不可能再被使用的,那么这个对象就是可回收对象。
缺点: 很难解决对象之间相互循环引用的问题

---
  • 枚举根节点做可达性分析(根搜索路径)

所谓“GC roots” 或者说tracing GC 的 "根集合" 就是一组必须活跃的引用。
基本思路就是通过一系列名为“GC Roots” 的对象作为起始点,从这个被称为GC Roots的对象开始向下搜索,如GC Roots没有任何引用链相连是,则说明此对象不可用。也即给定一个集合的引用作为根出发,通过引用关系

图片来源于网上

2)哪些可以做GCRoots对象

  • 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)
  • 方法区中的类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中N(Native方法)引用的对象

3)JVM的参数类型

  • 标配参数

    • java -version
    • java -help


      ---
  • X参数

    • java -Xint -version :解释执行
    • java -Xcomp -version :第一次使用就编译成本地代码
    • java -Xmixed :混合模式


      ---
  • XX参数

    • Boolean类型

    -XX:+ 或者 - 某个属性值
    +:表示开启
    -:表示关闭
    例子:
    -XX: +PrintGCDetails: 开启打印GC收集细节
    -XX: -PrintGCDetails: 关闭打印GC收集细节
    -XX: +UseSerialGC: 开启串行垃圾收集器
    -XX: -UseSerialGC: 关闭串行垃圾收集器

    • KV设置类型

    -XX: 属性key = 属性value
    例子:
    -XX: MetaspaceSize = 128m:设置元空间大小为128m
    -XX:MaxTenuringThreshold = 15: 控制新生代需要经历多少次GC晋升到老年代中的最大阈值

    • jinfo-查看当前运行程序的配置

    公式: jinfo -flag 配置项 进程编号
    例子:

    1. 查看初始堆大小:


      ---
    2. 查看其他参数


      ---
    3. 查看使用哪种垃圾回收器


      ---
  • 两个经典参数

    • -Xms 等价于 -XX: InitialHeapSize
    • -Xmx 等价于 -XX: MaxHeapSize

4)查看JVM默认值

  • -XX:+PrintFlagsInitial 查看默认初始值
  • java -XX: +PrintFlagsInitial -version
  • java -XX: +PrintFlagsInitial


    ---
  • -XX:+PrintFlagsFinal 查看修改更新
    • java -XX:+PrintFlagsFinal
    • java -XX:+PrintFlagsFinal -version
    • java -XX:+PrintCommandedLineFlags


      ---

5)常用的配置参数

经典案例设置:
-Xms128m -Xmx4096m -Xss1024k -XX:Metaspacesize=512m -XX:+PrintCommandLineFlags -XX:PrintGCDetails -XX:UseSerialGC

  • -Xms

初始化大小内存,默认为物理内存1/64
等价于 -XX:InitialHeapSize

  • -Xmx

最大分配内存,默认为物理内存1/4
等价于 -XX:MaxHeapSize

  • -Xss

设置单个线程的大小,一般默认为5112K~1024K
等价于 -XX:ThreadStackSize

  • -Xmn

设置年轻代大小

  • -XX:MetaspaceSize

设置元空间大小


---
  • -XX:+PrintGCdetails

输出详细的GC收集日志信息


---

---
  • -XX:SurvivorRatio

设置新生代中eden和S0/S1空间的比例
默认:
-XX:SurvivorRatio=8 --> Eden:S0:S1=8:1:1
修改:
-XX:SurvivorRatio=4 --> Eden:S0:S1=4:1:1
SurvivorRatio值就是设置eden区的比例占多少,S0/S1相同

  • -XX:NewRatio

设置年轻代与老年代在堆结构的占比
默认
-XX:NewRatio=2 新生代占1,老年代占2,年轻代占整个堆的1/3
修改
-XX:NewRatio=4 新生代占1,老年代占4,年轻代占整个堆的1/5
NewRatio值就是设置老年代的占比,剩下的1给新生代

  • -XX:MaxTenuringThreshold

设置垃圾最大年龄
-XX:MaxTenuringThreshold=0:设置垃圾最大年龄。如果设置为0的话,则年轻代对象不经过Survivor区,直接进入老年代。对于老年代比较多的应用,可以提高效率。如果将此值设置为一个较大值,则年轻代对象会在Survivor区进行多次复制,这样可以增加对象在年轻代的存活时间,增加年轻代被回收的概论。

6)强软弱虚

图片来源于网络

图片来源于网络
  • 强引用
    • 当内存不足,JVM开始垃圾回收,对于强引用的对象,就算出现了OOM也不会对该对象进行回收,死都不收
    • 强引用是我们最常见的普通对象引用,只要还有强引用指向一个对象,就能表名对象还“活着”,垃圾收集器不会碰这种对象。在Java中最常见的就是强引用,把一个对象赋给一个引用变量,这个引用变量就是一个强引用。当一个对象被强引用变量引用时,它处于可达状态,它是不可能被垃圾回收机制回收的。即使该对象以后永远都不会被用到,JVM也不会回收。 因此强引用是造成Java内存泄漏的主要原因之一。
    • 对于一个普通的对象,如果没有其他的引用关系,只要超过了引用的作用域或者显式地将相应(强)引用赋值为null,一般就是认为可以被垃圾收集(具体看垃圾收集策略)
public static void main(String[] args) {
        Object o1 = new Object();   //默认为强引用
        Object o2 = o1;     //引用赋值
        o1 = null;          //置空 让垃圾收集
        System.gc();
        System.out.println(o1);     // null
        System.out.println(o2);     // java.lang.Object@1540e19d
    }
  • 软引用
    • 软引用就是一种相对强引用弱化了一些的引用。需要用java.lang.ref.SoftReference类来实现,可以让对象豁免一些垃圾收集。
    • 系统内存充足 -> 不会回收
    • 系统内存不足 -> 会回收
    • 软引用通常用在对内存敏感的程序中,比如高速缓存就有用到软引用,内存够用的时候就保留,不够用就回收
    public static void main(String[] args) {
        Object o1 = new Object();
        SoftReference softReference = new SoftReference(o1);
        o1 = null;
        System.gc();
        System.out.println(o1);
        System.out.println(softReference.get());
    }
  • 弱引用
    • 弱引用需要用java.lang.ref.WeakReference类来实现,它比软引用的生存期更短
    • 对于弱引用的对象,只要垃圾回收机制一运行,不管JVM的内存空间是否足够,都会回收该对象占用的内存。
    public static void main(String[] args) {
        Object o1 = new Object();
        WeakReference weakReference = new WeakReference(o1);
        o1 = null;
        System.gc();
        System.out.println(o1);                     //null
        System.out.println(weakReference.get());    //null
    }
  • 虚引用
    • 虚引用需要java.lang.ref.PhantomReference类来实现。
    • 形如虚设,它不会决定对象的生命周期。
    • 如果一个对象持有虚引用,那么它就和没有任何一样,在任何时候都可能被垃圾回收器回收, 它不能单独使用也不能通过它来访问对象,虚引用必须和引用队列(ReferenceQueue)联合使用。
    • 虚引用的主要作用是跟踪对象被垃圾回收的状态,仅仅是提供了一种确保对象被finalize以后,做某些事情的机制。PhantomReference的get方法总是返回null,因此无法访问对应的引用对象。其意义在于说明一个对象已经进入finalization阶段,可以被gc回收,用来实现比finalization机制更灵活的回收操作。
public static void main(String[] args) {
        Object o1 = new Object();
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
        PhantomReference<Object> phantomReference = new PhantomReference<>(o1,referenceQueue);
        System.out.println(o1);                         //java.lang.Object@1540e19d
        System.out.println(phantomReference.get());     //null
        System.out.println(referenceQueue.poll());      //null
    }
  • 扩展
    • 软弱引用适用场景

    假如有一个引用需要读取大量的本地图片
    存在问题:
    1. 如果每次读取图片都从硬盘读取则会严重影响性能。
    2. 如果一次性全部加载到内存中有可能造成内存溢出。
    >解决思路:
    > 用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题。
    Map<String,SoftReference> imgMap = new HashMap<String,SoftReference>();
    - WeakHashMap

public static void main(String[] args) {
        WeakHashMap<Integer,String> weakHashMap = new WeakHashMap<>();
        Integer key = new Integer(1);
        weakHashMap.put(key,"测试1");
        System.out.println(weakHashMap);    //{1=测试1}
        key=null;
        System.out.println(weakHashMap);    //{1=测试1}
        System.gc();
        System.out.println(weakHashMap+"\t"+weakHashMap.size());    //{} 0
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • JVM架构 当一个程序启动之前,它的class会被类装载器装入方法区(Permanent区),执行引擎读取方法区的...
    cocohaifang阅读 1,646评论 0 7
  • 内存溢出和内存泄漏的区别 内存溢出:out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,...
    Aimerwhy阅读 730评论 0 1
  • Java 虚拟机有自己完善的硬件架构, 如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM 屏蔽了与具体操作系...
    尹小凯阅读 1,678评论 0 10
  • 作者:一字马胡 转载标志 【2017-11-12】 更新日志 日期更新内容备注 2017-11-12新建文章初版 ...
    beneke阅读 2,184评论 0 7
  • 转载blog.csdn.net/ning109314/article/details/10411495/ JVM工...
    forever_smile阅读 5,350评论 1 56