JVM 虚拟机 精华一页纸

1、内存管理 - 栈 or 堆

无论是java还是C,内存分配,本质上就是 栈和堆两个类型。简单来说,代码逻辑处理在栈上,数据在堆上。

I、JVM内存模型

堆:新生代(Eden,survivor),年老代(Gen) -- 分配对象、数组等

非堆(栈):虚拟机栈,本地方法栈 -- 栈帧 分配局部变量、操作需要的空间比如方法链接

方法区-(永久代) -- 分配代码、全局变量、静态变量

Object o = new Object()

首先代码在 方法区中。

执行时,Object o 会存放在 java栈 的本地变量表中。

new Object 会在 java堆中。

II、JVM 内存分配过程

a、创建的对象都在堆的新生代(Eden)上分配空间;垃圾收集器回收时,把Eden上存活的对象和一个Survivor 的对象拷贝到另一个Survivor

一般是MinorGC

b、大对象直接进入老年代

-- 所以对于大对象比较多的,年老代分配的内存要多一点,年轻代分配的少一点

--可以配置对象大小的门限

c、长期存活的对象进入老年代

--在多次MinorGC时仍然存活的,进入老年代

--可以配置门限MinorGC次数,默认15次

原因很简单,因为 年轻代一般是复制算法,多次复制代价很大

老年代是 Full GC

d、年龄判断,如果 Survivor 的同龄对象占所有对象的一半,大于这个年龄的就直接进入老年代

MinorGC时,检查晋升老年代的对象是否大于 老年代剩余空间,如果大于则进行 Full GC

e、空间分配担保

在发生Minor GC 时,虚拟机检测之前 晋升到老年代的空间平均大小 是否大于老年代剩余空间,如果大于则直接进行 Full GC;如果小于,则查看HandlePromotionFailure 设置是否允许担保失败。如果允许,就进行Minor GC,并把存活的对象移到老年代,如果不允许,则进行Full GC

III、内存溢出

a、OutOfMemoryError

首先,堆内存不够分配、肯定会出现 内存溢出的问题

永久代,加载的类太多,也会有

栈内存申请不到也有

本机native直接内存溢出

内存溢出会出现在各个内存区域

b、StackOverflowError

递归调用(没有关闭条件)

线程太多

c、内存溢出定位过程

使用内存映像分析工具(Eclipse Memory Analyzer),对dump出的文件进行分析

确认内存中的对象是否必要的。 即分清楚出现了内存泄漏(Memory Leak)还是内存溢出(Memory Overflow)

如果是内存泄漏通过工具查看 泄漏对象到 GC root的引用链

如果不是泄漏,则 检查 虚拟机的堆参数(-Xmx -Xms),从代码上检查是否存在某些对象生命周期过长,持有状态时间过长的情况。

2、内存(垃圾)回收

在描述 java 垃圾回收之前,想象一下 C ++ 内存如何内存管理 和 垃圾回收。

通常new 一片内存区域,存储一些数据,假设就是 new int[]

频繁的操作删除后,留下了很多内存碎片

然后一般都是 memcpy 把数据转移到内存的一端,一般是都移动到开始端。

事实上,所有的内存回收后的管理,基本都是 拷贝移动已有数据。比如 Redis的 ziplist 就是这么设计的。

垃圾回收两个问题:

I、如何判断 对象不再被使用?

a、首先想到的是记录每一个使用者 - 引用计数器,事实上早期的java垃圾回收就是如此。

引用计数有个很大的困扰,几个对象间的互相循环引用,怎么办?引用计数一直存在。

b、标记引用链 + 从根开始 - 根搜索法

通过引用链可以识别对象引用关系;从根开始,就能识别脱离主链的 循环引用的问题。这样利用有向图,从根开始寻找整个引用链,把不再链上的对象都进行标记。

什么样的对象适合做根对象 GCroot

静态变量 - 程序加载首先进内存的对象,全局根

栈帧的变量 - 程序当前执行到的对象,临时根 (因为执行完毕,栈帧的数据就会回收,执行过程中,作为当前流程开始的对象同样也是根)

II、如何操作回收不用的对象?

前面已经描述过C++ 内存回收方法,java也非常类似

标记清除法 - 前面发现的对象,标记完后,进行删除, 类似 delete,这样会产生很多碎片

复制算法 - 把存活的对象,统一拷贝到 另一块完整内存

标记整理法 - 把存活对象移动到一端,剩下的内存统一清理,类似 memcpy,后delete

适用场景

复制算法,适用存活对象较少的场景,比如 新生代;标记整理算法和清除算法,适用于存活对象较多的场景。

III、垃圾回收器

除了标记清除法外,其他两种需要移动对象,都会造成程序的卡顿(移动过程中,对象不能被改变),这个问题数据库备份过程中也有同样的问题。

a、复制算法收集器 -- 基本都用在新生代

Serial收集器 - 单线程条件下运行 (一般client和默认的)

ParNew收集器 - 多线程条件下运行 (一般server模式适用)

Parallel Scanvenge收集器

ParNew VS Parallel Scanvenge

ParNew 关注卡顿时延; Parallel Scanvenge 关注系统吞吐量

b、标记整理算法收集器 -- 基本用在老年代

Serial Old收集器 - 单线程

Parallel Old收集器 - 多线程 关注吞吐量

c、标记清除算法收集器 -- 用在老年代

CMS(Concurrent Mark Sweep)收集器 关注时延(因为耗时最多的标记和清除不需要影响用户业务)

G1收集器(Garbage First)收集器

时延 or 吞吐量

可以这么理解,时延的目标是单次回收要尽快,减少单次时延,而整体卡顿累计时长可能更多,导致吞吐量下降;吞吐量则关注整体卡顿情况,累计时长要端,吞吐量要高,单次卡顿时延可能会较长。

在这两种策略下,关注时延的 可能是多次频繁小范围的GC、关注吞吐量的可能是 一次就彻底的大范围的GC

组合:

单线程版本 - Serial + Serial Old 用在 Client模式下 (一般很少使用)

吞吐量优先组合 - Parallel Scanvenge + Parallel Old(Serial Old 老版本) 用在 Server模式下

时延优先组合 - ParNew + CMS(Serial Old 备用)用在 Server 模式下

3、JVM 优化

I、JVM crash

JVM 宕机的问题分析,首先 JVM 是一个C++进程,同样可以采用 C++ coredump 的分析思路来分析 JVM (网上描述的,好像用jmap生成的dump不能用GDB调试)

a、定位的文件素材

crash 日志

生成 -XX:ErrorFile=/path/xxx.log;

执行命令-XX:OnError="string"

-XX:+ShowMessageBoxOnError -- 打开实时GDB调试

程序自带日志

coredump文件

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/xx.log

linx: kill -3 | windows : Ctrl + Break

用JDK 自带命令jmap ,或者工具 JConsole和VisualVM

如果不能生成 则检查linux的 ulimit 配置

如果都找不到 到 /var/log/message中 找 cat messages|grep java java线程相关信息

b、分析文件

crash 日志

日志头(概要信息): -- 得到在哪个大的部分出现的问题。粗略信息

SIGSEGV - 执行 JNI 时出现的问题,一般是 编译加载类、执行JVM 外部代码出现的问题

EXCEPTION_ACCESS_VIOLATION - 执行 JVM 自身的代码

EXCEPTION_STACK_OVERFLOW - 堆栈出错

执行代码类型 C J VM 等

线程信息: -- 得到crash时线程的工作情况

线程类型 - Java Thread | VMThread | CompilerThread | GCTaskThread | WatcherThread | ConcurrentMarkSweepThread

线程状态 - _thread_in_native | _thread_uninitialized | _thread_new | _thread_in_vm | _thread_in_Java | _thread_blocked

安全点 safepoint 和锁 Mutex

安全点是标记线程运行到一个区域,JVM将其挂起,以便执行GC 等JVM操作。没有运行到安全点的线程,GC是不能回收其内存的;如果线程一直不到安全点可能会出现假死状态

内存heap情况

各个内存区域使用情况

其他信息

JVM参数,系统环境

分析重点:概要信息里,判断Crash时正在执行什么信息;当前线程状态;还有内存使用情况。

经典问题:内存溢出,一般永久代因为分配较少,出现问题的情况比较多;堆栈溢出,主要是 jni 本地栈溢出的可能较多

threaddump / heapdump 文件

当前线程运行状态、线程堆栈信息

类、对象使用情况

分析重点:基本信息里面,生成堆栈时的 异常线程和异常原因; wait/lock 等信息

经典问题:内存溢出

分析顺序 crash日志 > thread dump > heap dump

II、JVM OOM 问题

分析的文件和 crash 一样的。分析过程也是类似;

另外,可以通过JDK工具和命令实时监控分析。

不过,OOM 不一定会出现crash的情况。一般都是分析 heap dump文件。

a、分析内存 堆、非堆的使用情况; 看看是否是内存分配参数设置不合理。

b、分析 出现OOM的线程,正在操作的情况。找到导致泄露的对象的 GC Root 链

c、分析 类实例数最多、最大的 类的使用情况。(大对象、多对象)

-- 找到上面这两种情况下的 类和对象 是否需要/需要这么多,分清是 泄露还是对象生命周期不合理

d、分析 GC 的情况

-- 看看Full GC的情况

III、性能优化

程序的性能优化,无非就是 CPU、内存、IO 三种资源的占用情况分析。

性能优化的关键在于,分段排查,逐步逼近的方式,确定问题代码所在

先测量

再逐步逼近

找到问题代码

分析原因,并给解决方法

第一步:Linux 命令查看

top -H -p 找到 java进程和线程 中最耗资源的线程

第二步:在各种日志中找到对应的线程进行分析

a、卡顿时间较长 or 处理很慢 - 一般是CPU在高负荷运转,说明线程在高负荷执行;GC 时间较长等

查看GC 时长,GC 频次,各种GC占比 使用的是什么垃圾处理器

(比如 GCViewer 工具),GC的具体情况 -XX:+PrintGCTimeStamps -Xloggc:/tmp/gc.log -XX:+PrintGCDetails

查看各个线程处理情况,lock wait/notify 等情况;长时间运行的线程

查看JNI线程使用情况

连续生成两次的 线程堆栈(core) 文件,对比,查看 对象变化,线程执行变化;如果执行方法没有变化的线程,一般就是有问题的线程

IV、JDK自带工具

a、命令行工具

内存信息 jmap工具

生成dump jmap dump:format=b,file=xxx pid

内存统计 jmap -heap

内存跟踪 jstat

线程堆栈跟踪 jstack

配置信息 jinfo

分析工具 jhat

利用命令行就是 jmap+jstack,然后详细信息通过jhat

b、可视化工具

JConsole

JVisualVM - 其中 BTTrace 可以嵌入到每个方法追踪每个方法的执行(通过类似 asm/CGLib 字节码加载替换)

对比两个 dump 的差异,找出对象的

V、性能分析工具

MAT

IBM Heap - 可以分析出OOM中的 内存占用最大的地方,可以溯源GC root,找到对象树

VI、配置建议

a、内存分配,各个代的分配,32位JVM下 堆分配 1G,年轻代 一半,年老代一半。持久代64M

-Xmx512m

-Xms512m

-- 最大最小保持一致,避免频繁扩容和收缩

-Xmn256m

-- 年轻代一般在 一半左右,官方推荐 3/8

-XX:PermSize=64m

-- 持久代一般固定在 64M左右

-XX:MaxPermSize=128m

b、垃圾收集器选择

-XX:+UseParNewGC

--设置年轻代使用 ParNew收集器 并行

-XX:+UseConcMarkSweepGC

--设置年老代为CMS收集器 并发

c、其他设置项:内存压缩,对象晋级等等。

-XX:+UseCMSCompactAtFullCollection

-XX:CMSFullGCsBeforeCompaction=5

-- Full GC 5次后,进行内存碎片压缩

-XX:+HeapDumpOnOutOfMemoryError

-- 内存溢出时生成dump文件

d、启用运行期编译

Jit即时编译器

C1 – Client (简单优化

C2 – Server(激进优化 运行在Server模式下会更高效)

根据监控,针对热点代码进行优化(比如方法内联)

Jit的缺点是 编译有耗时,另外,对于一些类装载卸载比较多的场景也不适合。

Server模式启动时要慢一点,运行时效率很高

也可以指定,运行模式 解释模式-Xint,编译模式-XComp

比较理想的情况是,编译成Class,运行时可以动态Server模式;兼顾了效率和可移植性。

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

推荐阅读更多精彩内容

  • 1.一些概念 1.1.数据类型 Java虚拟机中,数据类型可以分为两类:基本类型和引用类型。基本类型的变量保存原始...
    落落落落大大方方阅读 4,502评论 4 86
  • 原文阅读 前言 这段时间懈怠了,罪过! 最近看到有同事也开始用上了微信公众号写博客了,挺好的~给他们点赞,这博客我...
    码农戏码阅读 5,927评论 2 31
  • Java 虚拟机有自己完善的硬件架构, 如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM 屏蔽了与具体操作系...
    尹小凯阅读 1,669评论 0 10
  • 一 、java虚拟机底层结构详解 我们知道,一个JVM实例的行为不光是它自己的事,还涉及到它的子系统、存储区域、...
    葡萄喃喃呓语阅读 1,453评论 0 4
  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,488评论 3 83