JVM

JVM组成

jvm由类加载器+内存+执行引擎

JVM内存区域

  • 堆 线程共享 存储对象
  • 虚拟机栈 线程私有,生命周期跟随线程
  • 栈帧 虚拟机栈中元素;局部变量表,操作数栈(做运算),动态链接(指向常量池中方法引用),方法出口
  • 本地方法栈 线程私有
  • 程序计数器 线程私有,无oom
  • 方法区 1.7之前是永久代、1.8之后是元空间
  • 直接内存

对象内存布局OOP

对象头 markword 8字节;klasspointer 4字节(开启指针压缩)/8字节;数组长度
内部引用
对齐

为什么要对齐

指针对齐指的是,对象的大小要是8字节的整倍数,如果不足会使用空位不足
应该是为了寻址更方便,可能跟指针压缩有关,指针压缩的原理就是将原地址/8,使用更短的地址表示

指针压缩原理

64位地址分为堆的基地址+偏移量,当堆内存<32GB时候,在压缩过程中,把偏移量/8后保存到32位地址。在解压再把32位地址放大8倍,所以启用CompressedOops的条件是堆内存要在4GB*8=32GB以内。
CompressedOops,可以让跑在64位平台下的JVM,不需要因为更宽的寻址,而付出Heap容量损失的代价。 不过它的实现方式是在机器码中植入压缩与解压指令,可能会给JVM增加额外的开销。

java中有哪些类加载器

BootstrapClassLoader启动类加载器(非java实现,无法获取,无法操作),负责加载<Java_Home>/lib下面的核心类库或-Xbootclasspath选项指定的jar包
ExtClassLoader扩展类加载器,负责加载<Java_Home>/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库 Launcher静态加载
AppClassLoader系统类加载器,负责加载系统类路径-classpath或-Djava.class.path变量所指的目录下的类库 Launcher静态加载
URLClassLoader,ext和app是他的子类,他实现了findClass方法,可以根据一组url找到对应的文件
ThreadContextClassLoader线程上下文类加载器,这不是一个真正的类加载器,而是在项目启动阶段设置到Thread中的,子线程会继承父线程线程上下文类加载器

java的类加载机制

全盘负责 谁触发的加载,由谁的类加载器质性加载
双亲委派 向上查找,向下加载;加载类时,先看自己有没有,再看父加载器有没有,都没有,则从父加载器开始向下加载
缓存机制 加载一次之后的类将会被缓存在类加载器中

java类加载过程

装载 将class文件找到,并加载方法区(1.8之后放在元空间中的 klassMetaSpace)
验证 验证class文件的格式、魔数、语义、语法
准备 为class的静态变量分配内存,并附默认值,int=0 boolean=false
解析 替换常量池中的符号引用为直接引用
初始化 为class的静态变量赋值,执行static块方法
使用
销毁

破坏双亲委派机制

指的是ThreadContextClassLoader可以在任何场景下被获取并执行类加载,本质上其实破坏的是全盘负责机制,双亲委派还是完整的

java SPI机制,指的是java定义了一些核心接口比如JDBC,具体实现由各大厂商进行实现,厂商将驱动启动类配置在一个文件中,jvm启动阶段会读取此文件执行加载,而负责加载的类在核心类路径上,根据全盘负责机制,应该有启动类加载器进行加载,那这肯定是加载不到的,所以会使用线程上线文类加载器
spring也使用的是线程上下文类加载器
线程上下文类加载器在启动阶段被设置为appclassloader

双亲委派的好处

1.唯一性,类只会被加载一次
2.安全性,核心类库不会被篡改
3.隔离性,不同自定义类加载器的类不共享,但是可以共享父加载器的,参考tomcat实现隔离不同webwork

如何确定是否应该回收对象

  1. 引用计数法 每个对象维护一个数字代表被多少对象引用,循环引用场景有问题
  2. 可达性分析 从gcroot出发,能访问到的对象代表不是垃圾

java中引用类型

强软弱虚引用,软引用在内存不足时会被回收,弱引用在GC时会被回收,虚引用用来管理对外内存

TLAB

Thread Local Allocation Buffer 防止线程竞争内存地址,所以给每个线程分配一块buffer

PLAB

Promotion Local Allocation Buffers 年轻代对象晋升时,申请的一块老年代地址

GCRoot

虚拟机栈和本地方法栈中的局部变量持有的对象,方法区静态变量和常量持有的对象

GC算法

  1. 标记-复制
  2. 标记-清除
  3. 标记-整理

三色标记法

完全扫描的对象标记为黑色,部分扫描的对象标记为灰色,未扫描的对象为白色;黑色对象在如果发生引用变化,会被标记为灰色

垃圾收集器

  • Serial 年轻代串行收集器 复制
  • SerialOld 老年代的串行收集器 整理
  • PS 年轻代并行收集器 复制 吞吐量优先,可以设置吞吐量、最大停顿时间,开启自适应自动配置分区大小,1.8默认
  • PO 老年代并行收集器 整理
  • PN 年轻代并行收集器 复制 配合CMS
  • CMS 老年代并发收集器 清除 追求最小停顿,可以与用户线程并发执行,cup敏感,浮动垃圾,内存碎片
  • G1 可以回收年轻代和老年代,复制/整理 追求最小停顿和吞吐量的平衡,弱化分代,使用分区,内存划分为2000个区域,每个区域可能是s、e、o、p,可以开启自适应自动配置分区大小,1.9默认
  • ZGC 彻底摒弃分代回收 1.11

对象晋升老年代的几种情况

  1. 分配担保机制
  2. 达到年龄
  3. 大对象直接分配 需要配置 默认不开启
  4. 动态年龄 当s区小于某一个年龄的对象大小超过s区一半,大于等于此年龄的对象直接进入老年代

STW

stop the world在gc过程中,为了保证某些一致性,要强制暂停所有用户线程

安全点和安全区域

安全点和安全区域指代码中一些引用不会发生改变的地方,当线程运行到这类位置时,堆对象状态是确定一致的,JVM可以安全地进行操作,比如return之前、调用方法之后、抛出异常之前、循环的末尾;阻塞中(安全区域);
一些操作会触发线程执行到安全点后停顿,GC,偏向锁接触,JIT优化等

CMS回收过程

1.初始标记 会导致STW,标记直接被GCRoots引用的对象
2.并发标记 与用户线程并发执行,使用三色标记法标记对象
3.并发预清理 清理用户线程并发过程中产生的对象引用变化,对象引用变化会触发写屏障,将对应的cardtable位置标记为dirty区域,
4.可中断的并发预清理 重复执行步骤3,尽可能的减少并发产生的脏页,预期一次YGC,减少年轻代对象,可以通过参数配置,一般会在5s左右
5.重新标记 这个阶段是 STW 的,因为并发阶段引用关系会发生变化,所以要重新遍历一遍新生代对象、Gc Roots、卡表、ModUnionTable等,来修正标记。
6.并发清除 清除垃圾对象
7.线程重置

CMS的问题

  • 浮动垃圾 在并发标记和并发预清理阶段会产生浮动垃圾,就是本身已经被标记为黑色的对象,在并发标记阶段被用户线程修改为没有引用,本质上应该是白色的,但是由于无法触达,所以一直是黑色的,会在下一次CMS被回收,浮动垃圾过多也会触发fullgc,参数配置CMS触发机制目前是75%
  • 内存碎片 CMS使用清除算法,被清除的地方形成一个一个不连续空洞,这就是内存碎片,内存碎片会影响大对象的内存分配,严重时会触发SerialOld,强制进行内存整理,这就是fullgc
  • promotion failed 进行ygc时,年轻代放不下,通过分配担保机制,检查老年代是否有足够空间,如果没有则抛出promotion failed,此时old区还没有开始CMS,降级为Serail OldGC,主要是内存随碎片导致的
  • concurrent mode failure 在CMS执行过程中,出现对象晋升失败,抛出concurrent mode failure 降级为Serail OldGC

CMS中的Incremental update

cms通过incremental update write barrie 在并发标记期间,将发生引用变化的黑色对象编程灰色对象。

CMS中的cardtable

Hospot将老年代内存按512字节划分为一个一个card,而卡表就是一个字节数组,数组中每一个元素对应一个card;CARD_TABLE[address>>9]=DIRTY
YGC需要卡表记录老年代对象对年轻代对象的引用,用以规避全量扫描老年代对象;
CMS用卡表记录老年代对象在并发标记过程中对象引用的变化

CMS中的ModUnionTable

ModUnionTable为了补充CMS和YGC同时发生时对cardtable的操作,YGC利用卡表记录老年代对年轻代对象的引用,YGC扫描到这个card之后会重置,这样CMS标记的dirty会丢失,所以使用ModUnionTable

触发fullgc的情况

  • 担保失败promotion failed
  • CMS期间 concurrent mode failure

G1垃圾收集器

分区又分代,将内存等分成2000多个region,每个region可以是Eden、S、Old、超大对象区(大小超过region的一半)
younggc和mixedgc模式
吞吐量和最小停顿权衡

Cset

一组需要被回收的region集合,YGC只包含e和s,MixedGC包含e、s和回收价值最大的一些o区

Rset

每个region维护一个remmberset,存储引用了本region对象被其他region区对象的引用
points-into 和 points-out ;CMS的卡表是out,G1的rset是in

SATB

标记期间,一旦引用关系发生变化,将此引用记录下来,就认为他是活的,Snapshot-at-beginning,从标记开始,认为可达的对象都是活的,即使并发过程中出现引用变化,产生浮动垃圾;
标记开始时生成两个指针,一个pre一个next,这两个指针区间的对象是标记期间创建的,视为活着的对象

G1回收过程

YGC
stw 从gcroots+rset出发扫描整个年轻代,此处是多线程并行完成,将存活对象copy到其他s区,清除垃圾
存活对象会被复制或移动到一个或者更多的survivor区域。如果这个阈值被满足了,其中的一些对象就会晋升到老年代中。
这里会出现STW的暂停。Eden和survivor的大小会被计算来供下一个年轻代的GC所使用。整体的信息会被保存起来来计算大小。暂停时间目标的这个事就会被考虑进来。
这种方式使得我们很容易的来调整区域的大小,使得它们根据实际需要变得更大或者更小。

MixedGG
global concurrent mark 全局并发标记

  • 初始标记 stw 标记gcroots 使用外部bitmap,不用对象头 这里复用了ygc的stw
  • 并发阶段 从gcroots和rset出发 三色标记,SATB开始工作
  • 最终标记 stw 处理SATB里的引用
  • 清理 stw 清理为空region 置为freelist
  • 复制 stw 将存活对象复制到其他的区域

G1主要运行模式

YGC
MixedGC
fullGC

YGC何时发生

新生代不够用了

MisedGC何时发生

G1HeapWastePercent:在global concurrent marking结束之后,我们可以知道old gen regions中有多少空间要被回收,在每次YGC之后和再次发生Mixed GC之前,会检查垃圾占比是否达到此参数,只有达到了,下次才会发生Mixed GC。
G1MixedGCLiveThresholdPercent:old generation region中的存活对象的占比,只有在此参数之下,才会被选入CSet。
G1MixedGCCountTarget:一次global concurrent marking之后,最多执行Mixed GC的次数。
G1OldCSetRegionThresholdPercent:一次Mixed GC中能被选入CSet的最多old generation region数量。

G1何时发生fullGC

当晋升失败、疏散失败、大对象分配失败、Evac失败时,有可能触发Full GC,在JDK10之前,Full GC是串行的,JEP 307: Parallel Full GC for G1之后引入了并行Full GC。

G1其他参数

设置region大小
设置个
手机目标时间 默认200ms
新生代最小值 5%
新生代最大值 60%
并行GC线程数
并发标记阶段并行线程数

JIT是什么

JIT即使编译,是JVM虚拟机解释引擎的一部分,我们知道java是一种先编译后解释的语言,在运行阶段,由JVM将class文件中的程序解释成机器语言来执行,每次解释还是会影响效率,JIT就是将一些热点代码直接翻译成机器语言后进行缓存,下次执行这个缓存就好了,在翻译和缓存过程中,还可以对代码进行一些动态的优化,比如逃逸分析、标量替换、锁消除、锁粗化、方法内联等

JIT的client和server

jit有client和server模式,client模式启动快,针对少量代码进行即使编译,适合客户端程序;server模式会针对大量大吗进行优化,启动慢,适合做服务器程序

如何确定那些代码需要编译

当 JVM 执行一个 Java 方法,它会检查方法调用计数器和回边计数器的总和,以决定这个方法是否有资格被编译。如果有,则这个方法将排队等待编译。这种编译形式并没有一个官方的名字,但是一般被叫做标准编译。
client模式是1500 server模式是10000

何时编译

异步,允许边执行边编译
栈上替换OSR,应对长循环或者死循环的编译,编译完成之后,下一次循环直接替换为编译好的机器码

JIT还有哪些功能

逃逸分析、标量替换 对象栈上分配
锁消除 对于永远不会并发访问的区域去除synconized
锁粗化 对于循环内部的synconized可能会迁移到外部
方法内联 将方法内调用的其他方法直接纳入本方法中,减少出战入栈
profile优化 如果这这方法一直只进行一个分支的判断,虚拟机就会推断,程序不会走另一个分支,即时编译器就会省略掉一个,只对一个分支进行编译为机器码。

JVM调优经验

  • cv优化过程
    cv项目的oldgc优化,cv的内存使用本身具有一定的特色,在简历查询场景,组装一份简历会产生大量的临时对象,简历对象本身存在一些文本信息,一份简历的平均长度在5000+char左右,在批量查询场景中,一组简历被返回,如果有200份简历那么这个数据大概会有2m左右的一个集合对象,并且接口响应较长,在一个长时间中内存会持有这些简历对象,并且cv的qps很高,峰值会有1w的qps。cv项目尽管已经加到15个实例,仍然每天伴会有几次cms进行执行

第一次 优化cv的jvm的目的,其实就是降低cms执行,cms尽管是低停顿收集器,但是回收过程中不免进行stw导致一些线程超时
针对cv的优化从以下几点入手,
1.使用宽表缓存,减少组装简历过程中产生的临时对象
2.限制接口size数量
3.调整jvm参数,策略是适当调大年轻代,也可以减少ygc的产生,降低因为年龄达到进入老年代,调整s区比例,减少因s区不足进入老年代的场景,这个过程是一个慢慢调试的过程
第一次优化使cv已经cms次数降低到了1周1次

第二次 优化cv是为了进一步降低服务的停顿
1.发现日志中有大量的安全点stw日志,通过日志发现很多偏向锁的释放导致进入的stw,删除代码中的一些无用的synconized,这里其实无伤大雅,大部分都是框架产生的
2.cms的最终标记时间user time过长,了解到公司服务器最多允许使用3核,将cms并发度调整到了3核,之前是15,频繁切换线程确实会影响

第三次 优化cv是为了解决concurrent mode failure问题
配置了每执行5次cms 执行一次整理算法,现阶段的cv已经能做到1周1次cms

第四次 优化cv是为了进一步减少服务压力
将简历缓存封装成轻量级接口,直接织入业务端代码,以前的模式是RPC-》缓存,改成缓存-》RPC,减少服务压力

  • user平台优化
    user平台也十分具有特点,那就是qps超高,峰值能达到2w
    正常情况下,user平台虽然qps很高,但是没有响应时间很长的那种接口,经过调整jvm参数,对象会很快被回收掉,每天仍然会有1次cms
    通过排查内存快照,定位到某个内部定时任务会生成超大对象集合,处理过之后,old区每日几乎不会有什么变化
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容