概念:html
一:堆(Heap)和非堆(Non-heap)内存java
按照官方的说法:“Java 虚拟机具备一个堆,堆是运行时数据区域,全部类实例和数组的内存均今后处分配。堆是在 Java 虚拟机启动时建立的。”“在JVM中堆以外的内存称为非堆内存(Non-heap memory)”。web
能够看出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
堆内存分配:
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属于堆内存,堆内存会从JVM启动参数(-Xmx:3G)指定的内存中分配,Perm不属于堆内存,有虚拟机直接分配,但能够经过-XX:PermSize -XX:MaxPermSize 等参数调整其大小。
New又分为几个部分:
2.垃圾回收算法
垃圾回收算法能够分为三类,都基于标记-清除(复制)算法:
JVM会根据机器的硬件配置对每一个内存代选择适合的回收算法,好比,若是机器多于1个核,会对年轻代选择并行算法,关于选择细节请参考JVM调优文档。
稍微解释下的是,并行算法是用多线程进行垃圾回收,回收期间会暂停程序的执行,而并发算法,也是多线程回收,但期间不中止应用执行。因此,并发算法适用于交互性高的一些程序。通过观察,并发算法会减小年轻代的大小,其实就是使用了一个大的年老代,这反过来跟并行算法相比吞吐量相对较低。
还有一个问题是,垃圾回收动做什么时候执行?
另外一个问题是,什么时候会抛出OutOfMemoryException,并非内存被耗空的时候才抛出
知足这两个条件将触发OutOfMemoryException,这将会留给系统一个微小的间隙以作一些Down以前的操做,好比手动打印Heap Dump。
(2)内存泄漏及解决方法
1.系统崩溃前的一些现象:
以后系统会没法响应新的请求,逐渐到达OutOfMemoryError的临界值。
2.生成堆的dump文件
经过JMX的MBean生成当前的Heap信息,大小为一个3G(整个堆的大小)的hprof文件,若是没有启动JMX能够经过Java的jmap命令来生成该文件。
3.分析dump文件
下面要考虑的是如何打开这个3G的堆信息文件,显然通常的Window系统没有这么大的内存,必须借助高配置的Linux。固然咱们能够借助X-Window把Linux上的图形导入到Window。咱们考虑用下面几种工具打开该文件:
使 用这些工具时为了确保加载速度,建议设置最大内存为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会工做的很好,但对一些配置很好的Server和具体的应用必须仔细调优才能得到最佳性能。经过设置咱们但愿达到一些目标:
前两个目前是相悖的,要想GC时间小必需要一个更小的堆,要保证GC次数足够少,必须保证一个更大的堆,咱们只能取其平衡。
(1)针对JVM堆的设置,通常能够经过-Xms -Xmx限定其最小、最大值,为了防止垃圾收集器在最小、最大之间收缩堆而产生额外的时间,咱们一般把最大、最小设置为相同的值
(2)年轻代和年老代将根据默认的比例(1:2)分配堆内存, 能够经过调整两者之间的比率NewRadio来调整两者之间的大小,也能够针对回收代,好比年轻代,经过 -XX:newSize -XX:MaxNewSize来设置其绝对大小。一样,为了防止年轻代的堆收缩,咱们一般会把-XX:newSize -XX:MaxNewSize设置为一样大小
(3)年轻代和年老代设置多大才算合理?这个我问题毫无疑问是没有答案的,不然也就不会有调优。咱们观察一下两者大小变化有哪些影响
(4)在配置较好的机器上(好比多核、大内存),能够为年老代选择并行收集算法: -XX:+UseParallelOldGC ,默认为Serial收集
(5)线程堆栈的设置:每一个线程默认会开启1M的堆栈,用于存放栈帧、调用参数、局部变量等,对大多数应用而言这个默认值太了,通常256K就足用。理论上,在内存不变的状况下,减小每一个线程的堆栈,能够产生更多的线程,但这实际上还受限于操做系统。
(4)能够经过下面的参数打Heap Dump信息
经过下面参数能够控制OutOfMemoryError时打印堆的信息
请看一下一个时间的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以清除引用。
调优方法:
一切都是为了这一步,调优,在调优以前,咱们须要记住下面的原则:
一、多数的Java应用不须要在服务器上进行GC优化;
二、多数致使GC问题的Java应用,都不是由于咱们参数设置错误,而是代码问题;
三、在应用上线以前,先考虑将机器的JVM参数设置到最优(最适合);
四、减小建立对象的数量;
五、减小使用全局变量和大对象;
六、GC优化是到最后不得已才采用的手段;
七、在实际使用中,分析GC状况优化代码比优化GC参数要多得多;
GC优化的目的有两个:
一、将转移到老年代的对象数量下降到最小;
二、减小full GC的执行时间;
为了达到上面的目的,通常地,你须要作的事情有:
一、减小使用全局变量和大对象;
二、调整新生代的大小到最合适;
三、设置老年代的大小为最合适;
四、选择合适的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