概念:

一:堆(Heap)和非堆(Non-heap)内存

  按照官方的说法:“Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配。堆是在 Java 虚拟机启动时创建的。”“在JVM中堆之外的内存称为非堆内存(Non-heap memory)”。

    可以看出JVM主要管理两种类型的内存:堆和非堆。简单来说堆就是Java代码可及的内存,是留给开发人员使用的;非堆就是JVM留给自己用的。
     所以方法区、JVM内部处理或优化所需的内存(如JIT编译后的代码缓存)、每个类结构(如运行时常数池、字段和方法数据)以及方法和构造方法的代码都在非堆内存中。

二:jvm参数的含义

 例:

  -Xms128m   JVM初始分配的堆内存
  -Xmx512m   JVM最大允许分配的堆内存,按需分配
  -XX:PermSize=64M   JVM初始分配的非堆内存
  -XX:MaxPermSize=128M   JVM最大允许分配的非堆内存,按需分配

  Xmx(memory max)代表程序最大可以从操作系统中获取的内存数量,Xms(memory start )代表程序启动的时候从操作系统中获取的内存数量。

  比如java -cp . -Xms80m -Xmx256m 说明这个程序启动的时候使用80m的内存,最多可以从操作系统中获取256m的内存。

三:堆内存分配和非堆内存分配

  堆内存分配:

     JVM初始分配的堆内存由-Xms指定,默认是物理内存的1/64;JVM最大分配的堆内存由-Xmx指定,默认是物理内存的1/4。默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制;
 空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。因此服务器一般设置-Xms、-Xmx 相等以避免在每次GC 后调整堆的大小。
   说明:如果-Xmx 不指定或者指定偏小,应用可能会导致java.lang.OutOfMemory错误,此错误来自JVM,不是Throwable的,无法用try...catch捕捉。

  非堆内存分配:

     JVM使用-XX:PermSize设置非堆内存初始值,默认是物理内存的1/64;由XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的1/4。(还有一说:MaxPermSize缺省值和-server -client选项相关,-server选项下默认MaxPermSize为64m,-client选项下默认MaxPermSize为32m。这个我没有实验。)
   上面错误信息中的PermGen space的全称是Permanent Generation space,是指内存的永久保存区域。还没有弄明白PermGen space是属于非堆内存,还是就是非堆内存,但至少是属于了。
   XX:MaxPermSize设置过小会导致java.lang.OutOfMemoryError: PermGen space 就是内存益出。 
   说说为什么会内存益出: 
    (1)这一部分内存用于存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域,它和存放Instance的Heap区域不同。 
    (2)GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS 的话,就很可能出现PermGen space错误。
      这种错误常见在web服务器对JSP进行pre compile的时候。


(1)JVM内存模型及垃圾收集算法

  1.根据Java虚拟机规范,JVM将内存划分为:  

    • New(年轻代)
    • Tenured(年老代)
    • 永久代(Perm)

  其中New和Tenured属于堆内存,堆内存会从JVM启动参数(-Xmx:3G)指定的内存中分配,Perm不属于堆内存,有虚拟机直接分配,但可以通过-XX:PermSize -XX:MaxPermSize 等参数调整其大小。

  • 年轻代(New):年轻代用来存放JVM刚分配的Java对象
  • 年老代(Tenured):年轻代中经过垃圾回收没有回收掉的对象将被Copy到年老代
  • 永久代(Perm):永久代存放Class、Method元信息,其大小跟项目的规模、类、方法的量有关,一般设置为128M就足够,设置原则是预留30%的空间。  

  New又分为几个部分:

  • Eden:Eden用来存放JVM刚分配的对象
  • Survivor1
  • Survivro2:两个Survivor空间一样大,当Eden中的对象经过垃圾回收没有被回收掉时,会在两个Survivor之间来回Copy,当满足某个条件,比如Copy次数,就会被Copy到Tenured。显然,Survivor只是增加了对象在年轻代中的逗留时间,增加了被垃圾回收的可能性。 

   2.垃圾回收算法

  垃圾回收算法可以分为三类,都基于标记-清除(复制)算法:

    • Serial算法(单线程)
    • 并行算法
    • 并发算法

  JVM会根据机器的硬件配置对每个内存代选择适合的回收算法,比如,如果机器多于1个核,会对年轻代选择并行算法,关于选择细节请参考JVM调优文档。

  稍微解释下的是,并行算法是用多线程进行垃圾回收,回收期间会暂停程序的执行,而并发算法,也是多线程回收,但期间不停止应用执行。所以,并发算法适用于交互性高的一些程序。经过观察,并发算法会减少年轻代的大小,其实就是使用了一个大的年老代,这反过来跟并行算法相比吞吐量相对较低。

  还有一个问题是,垃圾回收动作何时执行?

  • 当年轻代内存满时,会引发一次普通GC,该GC仅回收年轻代。需要强调的时,年轻代满是指Eden代满,Survivor满不会引发GC
  • 当年老代满时会引发Full GC,Full GC将会同时回收年轻代、年老代
  • 当永久代满时也会引发Full GC,会导致Class、Method元信息的卸载

另一个问题是,何时会抛出OutOfMemoryException,并不是内存被耗空的时候才抛出

  • JVM98%的时间都花费在内存回收
  • 每次回收的内存小于2%

满足这两个条件将触发OutOfMemoryException,这将会留给系统一个微小的间隙以做一些Down之前的操作,比如手动打印Heap Dump。


(2)内存泄漏及解决方法  

1.系统崩溃前的一些现象:

  • 每次垃圾回收的时间越来越长,由之前的10ms延长到50ms左右,FullGC的时间也有之前的0.5s延长到4、5s
  • FullGC的次数越来越多,最频繁时隔不到1分钟就进行一次FullGC
  • 年老代的内存越来越大并且每次FullGC后年老代没有内存被释放

  之后系统会无法响应新的请求,逐渐到达OutOfMemoryError的临界值。

2.生成堆的dump文件

  通过JMX的MBean生成当前的Heap信息,大小为一个3G(整个堆的大小)的hprof文件,如果没有启动JMX可以通过Java的jmap命令来生成该文件。

3.分析dump文件

  下面要考虑的是如何打开这个3G的堆信息文件,显然一般的Window系统没有这么大的内存,必须借助高配置的Linux。当然我们可以借助X-Window把Linux上的图形导入到Window。我们考虑用下面几种工具打开该文件:

  1. Visual VM
  2. IBM HeapAnalyzer
  3. JDK 自带的Hprof工具

  使 用这些工具时为了确保加载速度,建议设置最大内存为6G。使用后发现,这些工具都无法直观地观察到内存泄漏,Visual VM虽能观察到对象大小,但看不到调用堆栈;HeapAnalyzer虽然能看到调用堆栈,却无法正确打开一个3G的文件。因此,我们又选用了 Eclipse专门的静态内存分析工具:Mat。

4.分析内存泄漏

   通过Mat我们能清楚地看到,哪些对象被怀疑为内存泄漏,哪些对象占的空间最大及对象的调用关系。针对本案,在ThreadLocal中有很多的JbpmContext实例,经过调查是JBPM的Context没有关闭所致。

另,通过Mat或JMX我们还可以分析线程状态,可以观察到线程被阻塞在哪个对象上,从而判断系统的瓶颈。

5.回归问题

Q:为什么崩溃前垃圾回收的时间越来越长?

A:根据内存模型和垃圾回收算法,垃圾回收分两部分:内存标记、清除(复制),标记部分只要内存大小固定时间是不变的,变的是复制部分,因为每次垃圾回收都有一些回收不掉的内存,所以增加了复制量,导致时间延长。所以,垃圾回收的时间也可以作为判断内存泄漏的依据

Q:为什么Full GC的次数越来越多?

A:因此内存的积累,逐渐耗尽了年老代的内存,导致新对象分配没有更多的空间,从而导致频繁的垃圾回收

Q:为什么年老代占用的内存越来越大?

A:因为年轻代的内存无法被回收,越来越多地被Copy到年老代


三:性能调优

  从四个方便入手:  

    • 线程池:解决用户响应时间长的问题
    • 连接池
    • JVM启动参数:调整各代的内存比例和垃圾回收算法,提高吞吐量
    • 程序算法:改进程序逻辑算法提高性能

  下面只介绍JVM的启动参数优化

   在JVM启动参数中,可以设置跟内存、垃圾回收相关的一些参数设置,默认情况不做任何设置JVM会工作的很好,但对一些配置很好的Server和具体的应用必须仔细调优才能获得最佳性能。通过设置我们希望达到一些目标:

  • GC的时间足够的小
  • GC的次数足够的少
  • 发生Full GC的周期足够的长

  前两个目前是相悖的,要想GC时间小必须要一个更小的堆,要保证GC次数足够少,必须保证一个更大的堆,我们只能取其平衡。

(1)针对JVM堆的设置,一般可以通过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,我们通常把最大、最小设置为相同的值
   (2)年轻代和年老代将根据默认的比例(1:2)分配堆内存, 可以通过调整二者之间的比率NewRadio来调整二者之间的大小,也可以针对回收代,比如年轻代,通过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。同样,为了防止年轻代的堆收缩,我们通常会把-XX:newSize -XX:MaxNewSize设置为同样大小

(3)年轻代和年老代设置多大才算合理?这个我问题毫无疑问是没有答案的,否则也就不会有调优。我们观察一下二者大小变化有哪些影响

  • 更大的年轻代必然导致更小的年老代,大的年轻代会延长普通GC的周期,但会增加每次GC的时间;小的年老代会导致更频繁的Full GC
  • 更小的年轻代必然导致更大年老代,小的年轻代会导致普通GC很频繁,但每次的GC时间会更短;大的年老代会减少Full GC的频率
  • 如何选择应该依赖应用程序对象生命周期的分布情况: 如果应用存在大量的临时对象,应该选择更大的年轻代;如果存在相对较多的持久对象,年老代应该适当增大。但很多应用都没有这样明显的特性,在抉择时应该根 据以下两点:(A)本着Full GC尽量少的原则,让年老代尽量缓存常用对象,JVM的默认比例1:2也是这个道理 (B)通过观察应用一段时间,看其他在峰值时年老代会占多少内存,在不影响Full GC的前提下,根据实际情况加大年轻代,比如可以把比例控制在1:1。但应该给年老代至少预留1/3的增长空间

(4)在配置较好的机器上(比如多核、大内存),可以为年老代选择并行收集算法: -XX:+UseParallelOldGC ,默认为Serial收集

(5)线程堆栈的设置:每个线程默认会开启1M的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太了,一般256K就足用。理论上,在内存不变的情况下,减少每个线程的堆栈,可以产生更多的线程,但这实际上还受限于操作系统。

(4)可以通过下面的参数打Heap Dump信息

  • -XX:HeapDumpPath
  • -XX:+PrintGCDetails
  • -XX:+PrintGCTimeStamps
  • -Xloggc:/usr/aaa/dump/heap_trace.txt

通过下面参数可以控制OutOfMemoryError时打印堆的信息

  • -XX:+HeapDumpOnOutOfMemoryError

 请看一下一个时间的Java参数配置:(服务器:Linux 64Bit,8Core×16G)

JAVA_OPTS="$JAVA_OPTS -server -Xms3G -Xmx3G -Xss256k -XX:PermSize=128m -XX:MaxPermSize=128m -XX:+UseParallelOldGC -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/aaa/dump -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:/usr/aaa/dump/heap_trace.txt -XX:NewSize=1G -XX:MaxNewSize=1G"

  经过观察该配置非常稳定,每次普通GC的时间在10ms左右,Full GC基本不发生,或隔很长很长的时间才发生一次

  通过分析dump文件可以发现,每个1小时都会发生一次Full GC,经过多方求证,只要在JVM中开启了JMX服务,JMX将会1小时执行一次Full GC以清除引用。


调优方法:

  一切都是为了这一步,调优,在调优之前,我们需要记住下面的原则:

    1、多数的Java应用不需要在服务器上进行GC优化;

    2、多数导致GC问题的Java应用,都不是因为我们参数设置错误,而是代码问题;

    3、在应用上线之前,先考虑将机器的JVM参数设置到最优(最适合);

    4、减少创建对象的数量;

    5、减少使用全局变量和大对象;

    6、GC优化是到最后不得已才采用的手段;

    7、在实际使用中,分析GC情况优化代码比优化GC参数要多得多;

  GC优化的目的有两个:   

    1、将转移到老年代的对象数量降低到最小;

    2、减少full GC的执行时间;  

  为了达到上面的目的,一般地,你需要做的事情有:

    1、减少使用全局变量和大对象;

    2、调整新生代的大小到最合适;

    3、设置老年代的大小为最合适;

    4、选择合适的GC收集器;

调优实例:

  实例1:

  发现部分开发测试机器出现异常:java.lang.OutOfMemoryError: GC overhead limit exceeded,这个异常代表:GC为了释放很小的空间却耗费了太多的时间,其原因一般有两个:1,堆太小,2,有死循环或大对象;

  首先排除了第2个原因,因为这个应用同时是在线上运行的,如果有问题,早就挂了。所以怀疑是这台机器中堆设置太小;

  使用ps -ef |grep "java"查看,发现:

   

  该应用的堆区设置只有768m,而机器内存有2g,机器上只跑这一个java应用,没有其他需要占用内存的地方。另外,这个应用比较大,需要占用的内存也比较多;

  通过上面的情况判断,只需要改变堆中各区域的大小设置即可,于是改成下面的情况:

  

  跟踪运行情况发现,相关异常没有再出现

  实例2:

  一个服务系统,经常出现卡顿,分析原因,发现Full GC时间太长

  jstat -gcutil:

    S0     S1    E     O       P        YGC YGCT FGC FGCT  GCT

    12.16 0.00 5.18 63.78 20.32  54   2.047 5     6.946  8.993   

  分析上面的数据,发现Young GC执行了54次,耗时2.047秒,每次Young GC耗时37ms,在正常范围,而Full GC执行了5次,耗时6.946秒,每次平均1.389s,数据显示出来的问题是:Full GC耗时较长,分析该系统的是指发现,NewRatio=9,也就是说,新生代和老生代大小之比为1:9,这就是问题的原因:

  1,新生代太小,导致对象提前进入老年代,触发老年代发生Full GC;

  2,老年代较大,进行Full GC时耗时较大;

  优化的方法是调整NewRatio的值,调整到4,发现Full GC没有再发生,只有Young GC在执行。这就是把对象控制在新生代就清理掉,没有进入老年代(这种做法对一些应用是很有用的,但并不是对所有应用都要这么做)

  实例3:

  一 应用在性能测试过程中,发现内存占用率很高,Full GC频繁,使用sudo -u admin -H  jmap -dump:format=b,file=文件名.hprof pid 来dump内存,生成dump文件,并使用Eclipse下的mat差距进行分析,发现:

  

  从图中可以看出,这个线程存在问题,队列LinkedBlockingQueue所引用的大量对象并未释放,导致整个线程占用内存高达378m,此时通知开发人员进行代码优化,将相关对象释放掉即可。

出处资料:https://www.cnblogs.com/downey/p/5352593.html

JVM — 性能调优的更多相关文章

  1. JVM性能调优监控工具jps、jstack、jmap、jhat、jstat、hprof使用详解

    摘要: JDK本身提供了很多方便的JVM性能调优监控工具,除了集成式的VisualVM和jConsole外,还有jps.jstack.jmap.jhat.jstat.hprof等小巧的工具,本博客希望 ...

  2. JVM性能调优监控工具jps、jstack、jmap、jhat、jstat使用详解(转VIII)

    JVM本身就是一个java进程,一个java程序运行在一个jvm进程中.多个java程序同时运行就会有多个jvm进程.一个jvm进程有多个线程至少有一个gc线程和一个用户线程. JDK本身提供了很多方 ...

  3. Java性能优化权威指南-读书笔记(二)-JVM性能调优-概述

    概述:JVM性能调优没有一个非常固定的设置,比如堆大小设置多少,老年代设置多少.而是要根据实际的应用程序的系统需求,实际的活跃内存等确定.正文: JVM调优工作流程 整个调优过程是不断重复的一个迭代, ...

  4. JVM 性能调优实战之:一次系统性能瓶颈的寻找过程

    玩过性能优化的朋友都清楚,性能优化的关键并不在于怎么进行优化,而在于怎么找到当前系统的性能瓶颈.性能优化分为好几个层次,比如系统层次.算法层次.代码层次…JVM 的性能优化被认为是底层优化,门槛较高, ...

  5. JVM 性能调优实战之:使用阿里开源工具 TProfiler 在海量业务代码中精确定位性能代码

    本文是<JVM 性能调优实战之:一次系统性能瓶颈的寻找过程> 的后续篇,该篇介绍了如何使用 JDK 自身提供的工具进行 JVM 调优将 TPS 由 2.5 提升到 20 (提升了 7 倍) ...

  6. jvm 性能调优

    [转载]:http://blog.csdn.net/chen77716/article/details/5695893 最近因项目存在内存泄漏,故进行大规模的JVM性能调优 , 现把经验做一记录. 一 ...

  7. (转)JVM性能调优之生成堆的dump文件

    转自:http://blog.csdn.net/lifuxiangcaohui/article/details/37992725 最近因项目存在内存泄漏,故进行大规模的JVM性能调优 , 现把经验做一 ...

  8. eclipse JVM 性能调优

    最近因项目存在内存泄漏,故进行大规模的JVM性能调优 , 现把经验做一记录. 一.JVM内存模型及垃圾收集算法 1.根据Java虚拟机规范,JVM将内存划分为: New(年轻代) Tenured(年老 ...

  9. JVM性能调优监控命令jps、jinfo、jstat、jmap+jhat、jstack使用详解

    JDK本身提供了很多方便的JVM性能调优监控工具,除了集成式的VisualVM和jConsole外,还有jps.jinfo.jstat.jmap+jhat.jstack等小巧的工具,本博客希望能起抛砖 ...

  10. 《转》:JVM性能调优监控工具jps、jstack、jmap、jhat、jstat、hprof使用详解

    原链接:https://my.oschina.net/feichexia/blog/196575 现实企业级Java开发中,有时候我们会碰到下面这些问题: OutOfMemoryError,内存不足 ...

随机推荐

  1. java.lang.ClassNotFoundException: org.springframework.web.util.WebAppRootListener

    严重: Error configuring application listener of class org.springframework.web.util.WebAppRootListenerj ...

  2. SQL 使用分区方法

  3. 使用 XSLT 显示 XML

    通过使用 XSLT,您可以向 XML 文档添加显示信息. 使用 XSLT 显示 XML XSLT 是首选的 XML 样式表语言. XSLT (eXtensible Stylesheet Languag ...

  4. Java对数组和列表的排序1.8新特性

    Java对数组列表的排序 数组 Integer[] a = new Integer[] { 1, 2, 3, 4, 5, 6, 9, 8, 7, 4, 5, 5, 6, 6 }; Arrays.sor ...

  5. sweetalert2 全面替代 alert ,从 sweetalert2 弹出 text 到 弹出 Dom 以及模态框和取消 sweetalert2 的 OK 按钮

    1. 简易基本版 sweetalert 涵盖日常基本的弹出及对话框 2. 升级版本 sweetalert2 满足常见开发工作中的各种要求 3 取消 OK 按钮, 只需要设置 showConfirmBu ...

  6. Ajax异步提交的步骤

    1.创建XHR对象 ,XMLHttpRequest(该对象负责悄悄滴与服务器进行交互): 2.设置响应函数/回调函数(响应函数规定对返回自服务器的信息如何进行处理): 3.通过xmlhttp.open ...

  7. Yahoo 军规(部分)

    1.尽量减少HTTP的请求次数  网站中的图片,文字,样式表等内容都是从服务器端请求过来的.如果项目中有多个脚本,多个样式表需要加载,尽量将他们合并在一个CSS.JS文件中. 2.将CSS放在页面最上 ...

  8. HTTP协议初步认识

    1.基本概念: HTML:HyperText Transfer Protocol,中文名:超文本传输协议,基于请求/响应模式,基于TCP/IP协议,是一种,无连接,无状态协议: 2.HTTP传输过程: ...

  9. C#中winform下利用ArcEngine调用ArcGIS Server发布的服务 AE9.3

    主要使用了AE中的IAGSServerOject接口及IMapServer接口.Private void GetServerTest_Click(object sender, EventArgs e) ...

  10. CMakeLists.txt中使用循环

    编译一个安卓下的so,此so依赖其他的库,通过循环简化操作 set(UVC_LIBS UVCCamera uvc usb100 jpeg-turbo1500) FOREACH(UVC_LIB ${UV ...