JVM

jvm内存结构/java运行时数据区


image.png

JVM调优

调优流程:

  1. 压测,可以增加代码或者使用工具,比如LoadRunner、jmeter
  2. 使用工具,监控jvm虚拟机运行,生成堆快照和日志,awr 报告,分析结果
  3. 性能瓶颈定位,比如内存瓶颈、cpu瓶颈、代码缺陷、内存泄漏、JVM频繁gc
  4. 优化代码(线程池、集合对象使用不对、连接池)或者调整内存/gc配置。
GC优化

jvm调优主要是针对垃圾收集器的收集性能优化,令运行在虚拟机上的应用能够使用更少的内存以及延迟获取更大的吞吐量或用户体验。
指标定义:

  1. 吞吐量
  2. 延迟
  3. 内存占用

目的:

  1. MinorGC回收原则: 每次minor GC 都要尽可能多的收集垃圾对象。以减少应用程序发生Full GC的频率。
  2. 减少垃圾回收次数
    2、将转移到老年代的对象数量降低到最小;
    3、减少full GC的执行时间;
一般程序优化

1、减少使用全局变量和大对象;
2、调整新生代的大小到最合适;
3、设置老年代的大小为最合适;
4、选择合适的GC收集器;

参考数字

注:如果满足下面的指标,则一般不需要进行GC:
Minor GC执行时间不到50ms;
Minor GC执行不频繁,约10秒一次;
Full GC执行时间不到1s;
Full GC执行频率不算频繁,不低于10分钟1次;

场景1

现象:系统报错java.lang.OutOfMemoryError:GC overhead limit exceeded异常
原因:堆太小
解决方案:把-Xmx设置大点

场景2

现象:系统经常卡顿
原因:新生代和老生代大小之比为1:9,新生代太小,导致对象提前进入老年代,触发老年代发生Full GC,而且老年代较大,进行Full GC时耗时较大
解决方案:优化的方法是调整NewRatio的值,调整到4,这就是把对象控制在新生代就清理掉,没有进入老年代

场景3

现象:一应用在性能测试过程中,发现内存占用率很高,Full GC频繁
原因:使用jmap生成dump文件,使用mat分析,发现使用了队列LinkedBlockingQueue,所引用的大量对象并未释放,导致整个线程占用内存高达378m
解决方案:代码优化,将相关对象手动释放掉即可。

  1. 垃圾回收器调优
  2. 内存分配参数调优
  3. 内存泄漏解决
  4. 内存溢出解决
  5. 虚拟机监控工具
  6. 程序设计调优
  7. 系统架构调整,负载均衡
  8. 硬件资源升级
垃圾回收

如何查看JVM GC情况

  1. 打印gc日志
-XX:+PrintGCDetails  打印GC信息
-XX:+PrintGCDateStamps 打印GC时间
  1. jstat可以查看本地或远程虚拟机ID对于的gc情况
//jvm gc查看
jstat -gcutil pid interval(ms)
  1. visualVM工具监控
  2. jstack获取线程转储文件
  3. jmap获取堆转储文件

问题

  1. 垃圾回收过于频繁,很多时候是因为堆内存分配不合理导致,比如没有设置-Xmx,每次内存不够,老年代太小引发full gc,进行内存拓展。
  2. 停顿时间过长,假如cpu资源富余,可以使用cms来减低停顿时间,增加用户体验。
直接内存溢出

可以通过-XX:MaxDirectMemorySize指定直接内存大小。
如果不指定,且操作系统内存大小允许,直接内存大小与堆最大内存大小(-Xmx)一样。
一般情况下,如果是直接内存导致的OutOfMemoryError,一个明显的特征是Heap Dump文件很小,看不出异常,这样大致可以推断是直接内存溢出。

方法区溢出

可以通过-XX:PermSize和-XX:MaxPermSize限制方法区大小。

OutOfMemoryError:PermGen space一般有两种情况

  1. 常量池满,比如String.intern()方法可以将大量字符串复制到方法区字符常量池
  2. 加载太多Class,比如很多动态类加载技术,jdk动态代理、CGLib、Spring、Hibernate、JSP文件、字节码生成技术会动态生成class加载到方法区

方法区只会在full gc时才去回收,回收的主要目标是常量池和类型卸载,但是类型卸载条件太苛刻,所以方法区的回收成绩很差。
jdk1.8已经把方法区废掉。

栈溢出两种情况
  1. -Xss参数设置每个栈的内存大小, JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。如果线程请求的栈深度大于虚拟机允许的最大深度,将抛出StackOverflowError异常;
  2. 如果虚拟机在拓展栈时无法申请足够的内存空间,则抛出OutOfMemoryError异常,就针对虚拟机运行而言,栈可申请的内存大小 = 操作系统内存 - 堆 - 方法区 - 直接内存;

在相同物理内存下,减小-Xss这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。

内存溢出
  1. jmap或-XX:+HeapDumpOnOutOfMemoryError在获取内存堆转储快照文

  2. 使用visualVM或者MAT工具分析堆转储快照

  3. 分析GC Roots的引用链,排查弱/软引用,如果存在不需要的对象没有被回收,则定位是内存泄漏

  4. 如果不存在内存泄漏,检查堆参数(-Xmx与-Xms),结合物理内存大小,看是否可以调大堆大小。

  5. 各种内存/堆/栈/方法区溢出案例参考:
    https://www.cnblogs.com/dingyingsi/p/3760447.html

垃圾回收

判断哪些对象需要回收:

  1. 引用计数算法
  2. 可达性分析算法

垃圾回收算法:

  1. 标记和清除(mark-and-sweep)算法:标记阶段的任务是标记出所有需要被回收的对象,清除阶段就是回收被标记的对象所占用的空间,这个算法问题就是容易产生内存碎片。
  2. 复制算法:复制算法的提出是为了克服句柄的开销和解决内存碎片的问题,缺点是浪费了一般的内存空间,还有当存活对象较多时效率低。
  3. 标记-整理(Mark-Compact)算法:标记-整理算法是在标记-清除算法的基础上,又进行了对象的移动,因此成本更高,但是却解决了内存碎片的问题。
  4. 分代收集(Generation)算法:它的核心思想是根据对象存活的生命周期将内存划分为若干个不同的区域,一般情况下将堆区划分为老年代(Tenured Generation)和新生代(Young Generation),老年代的特点是每次垃圾收集时只有少量对象需要被回收,而新生代的特点是每次垃圾回收时都有大量的对象需要被回收,那么就可以根据不同代的特点采取最适合的收集算法。

垃圾回收器:


image.png

JVM参数

  1. 堆内存
//堆内存 = 新生代(eden+survivor1+survivor2)+老年代
//默认 eden : survivor1 : survivor2 = 8 : 1 : 1
//官方推荐 新生代 : 老年代 = 3 :5
-Xms:堆初始化内存,默认是物理内存的1/64,空余堆内存小于40%时扩充到Xmx。
-Xmx:堆最大内存,默认是物理内存的1/4,空余堆内存大于70%时缩小到Xms。
-Xmn:年轻代内存大小
-XX:NewSize:年轻代初始大小
-XX:MaxNewSize:年轻代最大大小
-XX:NewRatio:年轻代与老年代的比值,4代表年轻代与老年代1:4
-XX:SurvivorRatio:年轻代中Eden区与Survivor区的比值,8代表eden和survivor比例8:1。

####疑问解答
-Xmn,-XX:NewSize/-XX:MaxNewSize,-XX:NewRatio 3组参数都可以影响年轻代的大小,混合使用的情况下,优先级是什么?
如下:
高优先级:-XX:NewSize/-XX:MaxNewSize 
中优先级:-Xmn(默认等效 -Xmn=-XX:NewSize=-XX:MaxNewSize=?) 
低优先级:-XX:NewRatio 
推荐使用-Xmn参数,原因是这个参数简洁,相当于一次设定 NewSize/MaxNewSIze,而且两者相等,适用于生产环境。-Xmn配合 -Xms/-Xmx,即可将堆内存布局完成。
-Xmn参数是在JDK 1.4 开始支持。
  1. 虚拟机栈内存
//设置虚拟机栈内存:是每个线程使用栈的内存最大大小
-Xss:设置每个栈的内存大小, 默认JDK5.0以后每个线程堆栈大小为1M,以前每个线程堆栈大小为256K。

在相同物理内存下,减小这个值能生成更多的线程。但是操作系统对一个进程内的线程数还是有限制的,不能无限生成,经验值在3000~5000左右。需要注意的是:当这个值被设置的较大(例如>2MB)时将会在很大程度上降低系统的性能。

  1. 持久代(方法区)
-XX:PermSize:持久代的初始值,默认为物理内存的1/64。
-XX:MaxPermSize:持久代的最大值,默认为物理内存的1/4。
  1. 元空间
    jdk8取消了方法区,使用元空间代替,元空间存储在本地内存。
-XX:MetaspaceSize=128m:元空间的初始内存大小
-XX:MaxMetaspaceSize=512m:元空间最大内存大小
  1. 直接内存
 -XX:MaxDirectMemorySize:设置最大直接内存
  1. GC参数
//串行收集器
-XX:+UseSerialGC:使用Serial + Serial Old,这是Client模式下默认值。
-XX:+UseParNewGC:使用ParNew + Serial Old收集器。

//并行收集器(吞吐量优先)
-XX:+UseParallelGC:使用Parallel Scavenge + Serial Old,这是Server模式下默认值。
-XX:+UseParallelOldGC:使用Parallel Scavenge + Parallel Old收集器。
-XX:ParallelGCThreads=20:并行收集器的线程数,即:同时有多少个线程一起进行垃圾回收。此值建议配置与CPU数目相等。
-XX:MaxGCPauseMillis=100:每次年轻代垃圾回收的最长时间(单位毫秒)。如果无法满足此时间,JVM会自动调整年轻代大小,以满足此时间。
-XX:GCTimeRatio=99:GC时间占总时间的比率,默认值为99,即允许1%的GC时间。
-XX:+UseAdaptiveSizePolicy:设置此选项后,并行收集器会自动调整年轻代Eden区大小和Survivor区大小的比例,以达成目标系统规定的最低响应时间或者收集频率等指标。此参数建议在使用并行收集器时,一直打开。

//并发收集器(响应时间优先)
-XX:+UseConcMarkSweepGC:使用ParNew + CMS + SerialOld收集器。
-XX:+UseCMSCompactAtFullCollection:打开内存空间的压缩和整理,在Full GC后执行。可能会影响性能,但可以消除内存碎片。
-XX:CMSFullGCsBeforeCompaction=0:多少次Full GC后对内存空间进行整理来清除内存碎片,设置为0次,即每次Full GC后立刻开始压缩和整理内存。
-XX:CMSInitiatingOccupancyFraction=70:表示年老代内存空间使用到70%时就开始执行CMS收集,让年老代有空间接纳年轻代的对象,避免full gc。

//G1收集器(取代CMS)
-XX:+UseG1GC:使用Garbage First (G1)收集器

//其它
-XX:+MaxTenuringThreshold=15:代表对象在Survivor区经过15次复制以后才进入老年代。如果设置为0,则年轻代对象不经过Survivor区,直接进入老年代。
-XX:PretenureSizeThreshold=3145728:直接晋升老年代的对象大小阈值,大于这个参数值的对象将直接在老年代分配,这里设置为3MB,默认值是0,意思是不管多大都是先在eden中分配内存。
  1. 日志打印
-XX:-HeapDumpOnOutOfMemoryError:当首次遭遇内存溢出时Dump出此时的堆内存。
-XX:-PrintCompilation:当一个方法被编译时打印相关信息。
-XX:-PrintGC:每次GC时打印相关信息。
-XX:-PrintGCDetails:每次GC时打印详细信息。
-XX:-PrintGCTimeStamps:打印每次GC的时间戳。
-XX:-TraceClassLoading:跟踪类的加载信息。
-XX:-TraceClassLoadingPreorder:跟踪被引用到的所有类的加载信息。
-XX:-TraceClassResolution:跟踪常量池。
-XX:-TraceClassUnloading:跟踪类的卸载信息。
G1和CMS收集器的比较

网上有测试称,在相同的应用程序测试,G1和CMS相比,吞吐量下降了3.5%,停顿时间减少20%。
CMS仍然是最佳的默认选择,G1的吞吐量实在是太差,和它所减少的那点时延相比并不划算。

年轻代和年老代设置多大才合理?
  • 更大的年轻代必然导致更小的年老代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间,小的年老代会导致更频繁的Full GC;
  • 更小的年轻代必然导致更大年老代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短,大的年老代会减少Full GC的频率

如何选择应该依赖应用程序对象生命周期的分布情况:

  • 如果应用存在大量的临时对象,应该选择更大的年轻代;
  • 如果存在相对较多的持久对象,年老代应该适当增大。

但很多应用都没有这样明显的特性,在抉择时应该根据以下两点:
(A)本着Full GC尽量少的原则,让年老代尽量缓存常用对象,JVM的默认比例1:2也是这个道理
(B)通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响Full GC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:1。但应该给年老代至少预留1/3的增长空间

操作系统影响堆大小

jvm内存大小大小受操作系统的限制,32位操作系统,理论上堆最大可用内存为4G,但实际上最多只能达到1.4G至1.6G。
1、在32位solaris的机器上,堆最大可以达到2G
2、在64位的操作系统上,32位的JVM,堆大小可以达到4G

MAT

Memory Analyzer tool(MAT)是eclipse的一个内存分析插件
https://www.cnblogs.com/cnmenglang/p/6261435.html

分析原因

  1. 内存泄漏(Memory Leak),如果是内存泄漏,可进一步通过工具查看泄漏对象到GC Roots 的引用链。于是就能找到泄漏对象是通过怎样的路径与GC Roots 相关联并导致垃圾收集器无法自动回收它们的。掌握了泄漏对象的类型信息,以及GC Roots 引用链的信息,就可以比较准确地定位出泄漏代码的位置。
  2. 内存溢出(Memory Overflow),如果不存在泄漏,换句话说就是内存中的对象确实都还必须存活着,那就应当检查虚拟机的堆参数(-Xmx 与-Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容

  • Java 虚拟机有自己完善的硬件架构, 如处理器、堆栈、寄存器等,还具有相应的指令系统。JVM 屏蔽了与具体操作系...
    尹小凯阅读 1,688评论 0 10
  • 这篇文章是我之前翻阅了不少的书籍以及从网络上收集的一些资料的整理,因此不免有一些不准确的地方,同时不同JDK版本的...
    高广超阅读 15,599评论 3 83
  • 内存溢出和内存泄漏的区别 内存溢出:out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,...
    Aimerwhy阅读 741评论 0 1
  • 原文阅读 前言 这段时间懈怠了,罪过! 最近看到有同事也开始用上了微信公众号写博客了,挺好的~给他们点赞,这博客我...
    码农戏码阅读 5,962评论 2 31
  • JVM架构 当一个程序启动之前,它的class会被类装载器装入方法区(Permanent区),执行引擎读取方法区的...
    cocohaifang阅读 1,664评论 0 7