JVM总结

JVM (Java Virtual Machine),在java生态圈中具有重要地位,不仅支持Java还支持Kotlin、Groovy、Scala、Jython等多种其他语言, 封装各种操作系统和底层硬件的操作为Native代码(大部分为C/C++代码),编程时只需调用Java包中提供的接口即可。从而做到“一次编译,到处运行”(当然得是有JRE环境的机器)。本篇主要总结JVM的内存分配与垃圾回收机制,主要内容如下:

   一、 内存分配回收之历史
   二、 “垃圾”识别算法
   三、 JVM内存模型
   四、 垃圾回收(Garbage Collection)
   五、 JVM调优思路

JVM内存分配与垃圾回收

一、 内存分配回收之历史

在最先的编程语言中,大对象的分配是需要人工指定分配(malloc)回收(free)内存的,如C语言的代码片段:

// Code within C
int send_request() {
    size_t n = read_size();
    int *elements = malloc(n * sizeof(int));

    if(read_elements(n, elements) < n) {
        // elements is not freed!
        return -1;
    }

    //  There are a lot of code here!

    free(elements)
    return 0;
}

上述代码,在read_elements(n, elements) < n条件满足时,直接返回-1,并未回收elements指向的内存,从而造成内存泄漏。事实上,若是代码量足够大,且并非一蹴而就,是很容易出现上述疏忽,从而造成内存泄漏。

为了解决上述问题,C++里引入了智能指针的概念:

int send_request() {
    size_t n = read_size();
    auto elements = make_shared<vector<int>>();

    // read elements

    store_in_cache(elements);

    // process elements further

    return 0;
}

智能指针采用引用计数(稍后介绍)的方式判断内存是否依然还在使用,即每次引用该内存(本例elements所指的内存),引用计数加一,每个引用离开作用区域引用计数则减一。引用计数为0时,内存被自动回收。
智能指针相对于原先的手动分配和回收,先进了很多,但其依赖程序员指明调用,仍不够"智能"。

二、“垃圾”识别算法

为了让程序员更专注于业务逻辑,能不能将内存分配和回收完全交给第三方机制呢?
要解决这个问题,首先要区分哪部分内存还在使用,哪部分内存已经没用了(垃圾)。先驱者们提出了用于识别“垃圾”的引用计数可达性分析方法。

图1-3 引用计数

  • 引用计数:标识每个对象(内存区域)被引用的次数,引用计数为0的对象以及只被其引用的对象,则为垃圾。引用计数简单好用,但是有个循环引用问题,如图1-4所示红色区域的对象,虽理论上为垃圾,但其中每个对象的引用计数均不为0,因而无法被标识。不过不用担心,各个采用引用计数区分垃圾的高级语言,如Python等,使用了如弱引用等方法处理该的问题。(Java并没有采用这种方法,因此不会继续展开说明)

    图1-4 引用计数的循环依赖问题

  • 可达性分析:由GC Root出发,依次扫描并标识引用到的对象,扫描结束后,所有未被标识的对象则为垃圾。Java采用的即使可达性分析方法,可达性分析如图1-5所示。

    图1-5 可达性分析

    GC Root 在不同编程语言中实现不同。在Java中 ,GC Root为:
    - 局部变量(Local variables)
    - 活动线程(Active threads)
    - 静态变量(Static fields)
    - JNI(Java Native Inteface)引用的对象。
    
    三、JVM内存模型

    JVM内存模型见图1-1,其中绿色方框为线程私有,红色部分为线程公用。运行时数据区主要分为:

    1. 程序计数器
    2. 虚拟机栈(VM Stack)
    3. 堆(Heap)
    4. 本地方法栈
    5. 元空间(Metaspace )
图1-1 JVM内存模型

1. 程序计数器
记录当前线程执行的 字节码行号指示器(告诉线程,程序执行到哪了),每个线程独立储存,互不干涉(私有)。
2. 本地方法栈
与虚拟机栈的功能相似,只不过专为Native方法服务。
3. 虚拟机栈
每个线程私有,每个方法执行时,生成一个栈帧,将局部变量表、操作数栈、动态链接、方法返回地址保存,并且入栈,执行完成后,该栈帧返回并出栈。
4. 堆
GC(Garbage Colletion)的主要区域,存放各种对象实例,可分为年轻代(Young)年老代(Tenured/Old),年轻代又分为一个伊甸区(Eden)和两个幸存者区(Survivor),其中Virtual是由JVM控制分配但未使用的内存。堆结构的详细结构如图1-2所示(G1回收器的堆内存分配除外)。

图1-2 JVM堆

5. 元空间(Metaspace)
JDK1.8将之前的方法区(Method Area)移除后,加入了元空间,用以加载的类信息和常量池。元空间并不占用-Xmx设置的虚拟内存,而是占用额外的直接内存,(如:使用-Xmx1G配置了JVM最大内存为1G,最终App可能占用了1.5G内存,而多余部分就是被Metaspace占用了)。

四、垃圾回收(Garbage Collection)

垃圾回收器可以分为三大类:

  • Serial GC
  • Parallel GC
  • Mostly Concurrent GC
1. 单线程(Serial)回收器

在JVM面世之后,Serial回收器就开始服役,回收机制开始工作时,会暂停JVM中APP的工作线程(Stop The World),开始标记 - 清扫 - 压缩的清理过程。在多核处理器盛行的今天,也许Serial回收器看起来需要淘汰了,但该回收器依然适用于客户端内存回收和内存需求小(100M)的应用。

2. 并行(Parallel)回收器

并行回收器,依然会暂停JVM中APP的工作线程,但其利用多核处理器,能够高效的完成回收工作,有着极高的吞吐量表现。一般注重·数据计算以及强调CPU利用率的应用适合使用。
(吞吐量:若工作CPU时钟周期为T,垃圾回收CPU始终周期为t,吞吐量 = T/(T + t)

3. 并发(Mostly Concurrent)回收器

在APP的工作线程运行的同时,占用少量CPU资源并发的回收垃圾。CMS回收器(Concurrent Mark Sweep)和G1回收器(Garbage First)是该类回收器的代表。
为什么是Mostly Concurrent?因为CMS和G1在回收过程中,依然会有2次短暂STW(Stop The World)。CMS和G1主要适用于交互式低延时应用。
2次短暂STW: First Mark(标识出GC Root)和 Remark(重新标记并发标记过程中进入引用链的对象)
CMS在清理Tenured过程中并没有对内存的压缩的操作,而是维护了一张记录空闲内存的表,但这也造成了内存的碎片化,这是CMS一个弊端,我猜测这也是官方后续不推荐使用CMS的一个原因。

对应的新生代(Young)和年老代(Tenured)的回收器使用组合和命令如下表所示:

Young Tenured JVM options
Incremental Incremental -Xincgc
Serial Serial -XX:+UseSerialGC
Parallel Scavenge Serial -XX:+UseParallelGC -XX:-UseParallelOldGC
Parallel New Serial N/A
Serial Parallel Old N/A
Parallel Scavenge Parallel Old -XX:+UseParallelGC -XX:+UseParallelOldGC
Parallel New Parallel Old N/A
Serial CMS -XX:-UseSerialGC -XX:+UseConcMarkSweepGC
Parallel Scavenge CMS N/A
Parallel New CMS -XX:+UseParNewGC -XX:+UseConcMarkSweepGC
G1 G1 -XX:+UseG1GC

乍一看很可怕,这怎么记啊!大多数组合要么无法使用,要么不切合实际(多核处理器环境下,已经有一个回收代用了Parallel GC或者CMS,那另一个也就没必要用Serial GC了),因此只用记住粗体字部分就好,总共4条(分别对应本章开始的3类):

  • Serial GC (-XX:+UseSerialGC)
  • Parallel GC (-XX:+UseParallelGC -XX:+UseParallelOldGC)
  • Mostly Concurrent GC
    • CMS (-XX:+UseParNewGC -XX:+UseConcMarkSweepGC)
    • G1 (-XX:+UseG1GC)
    • ZGC (-XX:+UnlockExperimentalVMOptions -XX:+UseZGC):JDK11加入的新GC,适用场景:超大内存(TB级),极低响应延迟(10ms内),STW时间不随内存增加而增大。JDK12中依然属于实验性质,非常强大的GC,拭目以待吧。

五、 JVM调优思路

JVM三大重要指标:

  • 延时(Latency)
  • 吞吐量(Throughput)
  • 内存容量(Footprint)
延时

针对注重系统响应时间、延迟低的交互式应用,可以考虑使用CMS(JDK1.8以后不推荐使用)或G1(JDK1.9的默认回收器)。

吞吐量

针对注重计算以及CPU利用率的应用,可以考虑使用Parallel GC。

内存容量

若并不清楚应用所需的内存容量,可以将该配置交给JVM的Ergonomics机制自动配置(将在1/64 ~ 1/4 的系统内存之间调控

JDK GC的默认配置(新生代:年老代 = 1:3,Eden : Survivor = 8 : 1),针对的是大量朝生暮死对象场景,若是有大量中期或者长期存活的对象,就不适用了。根据使用场景和对象存活时间调整Young和Tenured的大小比例。

JVM 的GC日志相关配置:

JVM options Description
-Xloggc:${LogFilePath} 重定向GC日志至指定地址
-XX:+PrintGCDetails 打印GC细节
-XX:+PrintGCDateStamps 打印GC时间戳
-XX:+UseGCLogFileRotation GC日志循环打印
-XX:NumberOfGCLogFiles=${Num} 保存GC日志文件数
-XX:GCLogFileSize=${LogFileSize} GC日志文件最大值

----施工中,未完待续
GC Root
- Local variables
- Active threads
- Static fields
- JNI references

- Serial (STW)
- Parallel (PSYoung-Ergonomics、 Parallel Old) - 吞吐量优先
- Mostly Concurrent  (ParNew+CMS、G1) - 延时优先
- Concurrent (ZGC) - 大内存、低延迟
参考资料
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355