欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JVM的内存模型以及性能调优

程序员文章站 2024-03-15 11:31:35
...

JVM的内存模型

1.五大构建模型

JVM的内存模型以及性能调优


方法区和堆内存是各线程共享的内存区域,而程序计数器、JVM栈和本地方法栈是各线程独享内存。

1)、方法区(Method Area(也称永久代)):

主要用于存放类的信息(版本、字段、方法、接口),常量、静态变量、即时编译器编译后的代码等。

永久代是Hotspot虚拟机特有的概念,是方法区的一种实现,别的JVM都没有这个东西。在Java 8中,永久代被彻底移除,取而代之的是另一块与堆不相连的本地内存——元空间。

JDK 7 以前的版本字符串常量池是放在永久代中的,JDK 7 将字符串常量池移动到了堆中,JDK 8 直接删除了永久代,改用元空间替代永久代。对于不同的虚拟机实现来说,是有一定的*度的。
特点:
(1)线程共享

(2)内存区域可以不连续,可以动态扩展

(3)该区域并不是真正的“永久代”,偶尔也会进行内存回收,包括对常量池的回收和内存数据的卸载,频率比堆内存回收低很多

(4)方法区无法满足内存需求时,会报OutOfMenmeryError异常

垃圾回收器对这块区域的回收主要是针对常量池和类的卸载

2)、 java堆(Java Heap):

用于存放对象实例,垃圾回收器最主要针对的对象,对这部分的回收效率影响了VM的整体性能。

3)、本地方法栈(Native Methiod Stack):

主要用于VM Native方法。这部分是有VM自行管理 。

4)、虚拟机栈(VM Stack):

与本地方法栈是类似的,唯一的区别是它为VM执行Java方法服务。该区域主要维护栈针(每调用一个方法,则VM就会创建一个栈针保护当前方法的状态,并将其压入栈中,当被调用的方法完成后,在将其出栈继续执行未完成的方法),有一定的深度,可能会抛出*Error和OutOfMemoryError。

栈帧是由局部变量表、操作数栈和帧数据区组成:

  1. 局部变量表:用来存放方法中的局部变量(包括基本数据类型和对象引用),通过索引取值。
  2. 操作数栈:可以理解为临时存储计算数据的区域。通过入栈出栈方式取值。
  3. 帧数据区:用来记录方法调用信息,处理方法的正常返回和异常终止。如果方法正常返回,则把当前栈帧从栈中弹出,如果方法有返回值,则把返回值压入到调用方法的操作数栈。也可以支持常量池解析。

5)、 程序记数器

保证在多线程环境中程序可以连续执行。存放当前线程执行字节码的行号,字节码解释器工作时,是通过改变计数器的值来选取下一条字节码指令。JVM管理的内存中最小的一块内存区域

程序计数器不会发生内存溢出(OutOfMemoryError即OOM)问题。

2.堆的内存结构

JVM的内存模型以及性能调优
Eden: 最主要的刚创建的对象的内存分配区域, 绝大多数对象都会被创建到这里(对于一些较大的对象即需要分配一块较大的连续内存空间则是直接进入到老年代),该区域的对象大部分都是短时间都会死亡的,故垃圾回收器针对该部分主要采用标记整理算法了回收该区域。

Surviver:大部分对象在Eden区,但Eden区满了,此区存活的对象会复制到fromspace幸存区,当fromspace幸存区满,Eden和fromspace幸存区存活的对象会被copy到tospace幸存区,然后fromspace和tospace两个指针交换位置,保证在下一次GC之前,to幸存区是空的,当一个对象经过多次(有的垃圾回收器回收策略是15次, 可以通过参数 -XX:MaxTenuringThreshold 来设定,当进行15次后还存活的对象放到老年代)copy,就会晋升到老年代。针对新生代的垃圾回收是MinorGC。采用的是复制算法,每次只使用一块,Eden与Surviver区域的比例是8:1:1

Old: 存放生命周期很长的对象(经过多次新生代GC仍然存活的对象),针对老年代的垃圾回收是FullGC。,该区域主要是采用标记清除算法。

3、 Java垃圾回收机制(GC)

两种经典算法确定对象是否可以被回收:引用计数算法和可达性分析算法。

引用计数算法:

简介:根据对象被引用的数量来判断对象是否可以被回收。

给对象中添加一个引用计数器,每当一个地方引用这个对象时,计数器值+1;当引用失效时,计数器值-1。任何时刻计数值为0的对象就是不可能再被使用的。这种算法使用场景很多,但是,Java中却没有使用这种算法,这种算法很难解决对象之间相互引用的情况。

当两个对象相互引用着,但是虚拟机还是把这两个对象回收掉了,这也说明虚拟机并不是通过引用计数法来判定对象是否存活的。

优点:引用计数收集器执行速度很快,不会长时间打断程序的执行。

缺点:很难解决对象之间相互循环引用问题。

可达性分析算法:

简介:根据对象引用链是否可达来判断对象是否可以被回收。

详细描述:程序把所有的引用关系看做一张拓扑图,通过一系列的"GC Roots"作为起点,这些节点向下索引,搜索所走过的路径称为引用链,当一个对象没有任何引用链到达"GC Roots",那么这个对象不可达,可以被回收。可以作为"GC
Roots"的对象包括:

  1. 虚拟机栈和本地方法栈(栈帧的局部变量表)引用的对象
  2. 方法区中类静态变量引用的对象
  3. 方法区中常量引用的对象

由图可知,obj8、obj9、obj10都没有到GCRoots对象的引用链,即便obj9和obj10之间有引用链,他们还是会被当成垃圾处理,可以进行回收。
JVM的内存模型以及性能调优

对于可达性分析算法而言,未到达的对象并非是“非死不可”的,若要宣判一个对象死亡,至少需要经历两次标记阶段。

  1. 如果对象在进行可达性分析后发现没有与GCRoots相连的引用链,则该对象被第一次标记并进行一次筛选,筛选条件为是否有必要执行该对象的finalize方法,若对象没有覆盖finalize方法或者该finali ze方法是否已经被虚拟机执行过了,则均视作不必要执行该对象的finalize方法,即该对象将会被回收。反之,若对象覆盖了finalize方法并且该finalize方法并没有被执行过,那么,这个对象会被放置在一个叫F-Queue的队列中,之后会由虚拟机自动建立的、优先级低的Finalizer线程去执行,而虚拟机不必要等待该线程执行结束,即虚拟机只负责建立线程,其他的事情交给此线程去处理。
  2. 对F-Queue中对象进行第二次标记,如果对象在finalize方法中拯救了自己,即关联上了GCRoots引用链,如把this关键字赋值给其他变量,那么在第二次标记的时候该对象将从“即将回收”的集合中移除,如果对象还是没有拯救自己,那就会被回收。如下代码演示了一个对象如何在finalize方法中拯救了自己,然而,它只能拯救自己一次,第二次就被回收了。因为对象的finalize方法最多被虚拟机调用一次。

3.1.垃圾回收时机

垃圾回收有两种类型:MinorGC和FullGC。

Minor GC即新生代GC:发生在新生代的垃圾收集动作,因为Java有朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。

Major GC / Full GC:发生在老年代,经常会伴随至少一次Minor GC。Major GC的速度一般会比Minor GC慢倍以上。

对于不同的垃圾收集器,MinorGC和FullGC的触发时机也不一样,我们就以HotSpot的serial GC实现来看:

  • Minor GC发生条件

当新对象生成,并且在Eden申请空间失败时;

  • Full GC发生条件 :

(1)、 老年代空间不足

(2)、 永久带空间不足(jdk8以前)

(3)、System.gc()被显示调用

(4)、 Minor GC晋升到老年代的平均大小大于老年代的剩余空间

(5)、 使用RMI来进行RPC或管理的JDK应用,每小时执行1次Full GC

3.2.垃圾收集算法

经典的垃圾回收算法有标记清除算法、复制算法、标记整理算法、分代收集算法。

标记清除算法

从根集合开始扫描,标记存活的对象。再扫描整个空间中没有被标记的对象,进行回收。

缺点: 容易产生大量不连续的内存碎片。程序运行过程中,需要分配较大的对象时,无法找到足够的连续的内存,可能不得不触发另一次垃圾回收操作。

复制算法

把可用的内存空间划分为大小相同的两块,每次只使用其中一块,当这一块用完之后,就把存活的对象复制到另外一块空间,把当前内存空间一次性清理掉。

适用于对象存活率低的场景,如新生代。

缺点:需要占据大量空间

事实上,商用虚拟机都采用这种方式回收新生代,据统计,新生代每次回收大概只有10%的存活对象。

标记整理算法

是标记清除算法的改进版本,标记过程相同,不过后续让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

适用于存活率高的老年代。对于老年代,复制算法效率会变低,因为对象存活率高,每次都要对很多对象进行复制操作。

分代收集算法

不同生命周期的对象位于堆中不同区域,生命周期短位于新生代,生命周期长位于老年代,不同区域采取不同的回收策略,提高JVM执行效率。

当代商用虚拟机都采用了分代收集算法,新生代采用复制算法,老年代采用标记整理算法(或标记清除算法)

3.3 .四种引用对GC的影响

Java中有四种引用方式:强引用、软引用、弱引用、虚引用。这4种引用强度依次减弱。

JVM的内存模型以及性能调优
JVM的内存模型以及性能调优
强-FinalReference:代码中普遍存在的类似"Object obj = new Object()"这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。

软-SoftReference:内存不足时会被GC回收,如果回收后还不足,则报OOM,常用于缓存一些比较占内存的资源,比如图片缓存。

弱-WeakReference :GC遇到了就会回收,可以用来解决内存泄漏问题(子线程、静态方法中、枚举中)。

虚-PhantomReference:无法通过虚引用获取对象值,它的get方法永远返回null。如果一个对象仅持有虚引用,那么这个对象任何时候都可以被回收。

虚引用要结合ReferenceQueue(引用队列)一起使用,当垃圾回收器准备回收一个对象,发现它持有虚引用,就在回收对象之前,把这个虚引用加入到与之关联的引用队列中。

程序可以通过判断ReferenceQueue中是否加入了虚引用,来判断被引用对象是否要被回收,在被回收之前可以进行一些必要的操作。

3.4.垃圾收集器(jdk8及以前)

不同虚拟机所提供的垃圾收集器可能会有很大差别,我们使用的是HotSpot,如下:
JVM的内存模型以及性能调优

上图展示了7种作用于不同分代的收集器,如果两个收集器之间存在连线,那说明它们可以搭配使用。虚拟机所处的区域说明它是属于新生代收集器还是老年代收集器。

3.4.1.Serial收集器

新生代单线程收集器,标记和清理都是单线程,优点是简单高效。是client级别默认的GC方式,可以通过-XX:+UseSerialGC来强制指定。分配给虚拟机管理的内存一般来说不会很大,收集几十兆甚至一两百兆的新生代停顿时间在几十毫秒最多一百毫秒,只要不是频繁发生,这点停顿用户是完全可以接受的。Serial收集器运行过程如下图所示:
JVM的内存模型以及性能调优

说明:

  1. 需要STW(Stop The World),停顿时间长。

  2. 简单高效,对于单个CPU环境而言,Serial收集器由于没有线程交互开销,可以获取最高的单线程收集效率。

3.4.2.ParNew收集器

ParNew收集器其实就是Serial收集器的多线程版本,除了使用多条线程进行垃圾收集外,其余行为和Serial收集器完全一样,包括使用的也是复制算法。它是Server模式下的虚拟机首选的新生代收集器,其中有一个很重要的和性能无关的原因是,除了Serial收集器外,目前只有它能与CMS收集器配合工作(看图)。CMS收集器是一款几乎可以认为有划时代意义的垃圾收集器,因为它第一次实现了让垃圾收集线程与用户线程基本上同时工作。ParNew收集器在单CPU的环境中绝对不会有比Serial收集器更好的效果,甚至由于线程交互的开销,该收集器在两个CPU的环境中都不能百分之百保证可以超越Serial收集器。当然,随着可用CPU数量的增加,它对于GC时系统资源的有效利用还是很有好处的。它默认开启的收集线程数与CPU数量相同,在CPU数量非常多的情况下,可以使用-XX:ParallelGCThreads参数来限制垃圾收集的线程数。ParNew收集器运行过程如下图所示:
JVM的内存模型以及性能调优

3.4.3.Parallel Scavenge收集器

Parallel Scavenge收集器也是一个新生代收集器,也是用复制算法的收集器,也是并行的多线程收集器,介绍这个收集器主要还是介绍吞吐量的概念。CMS等收集器的关注点是尽可能缩短垃圾收集时用户线程的停顿时间,而Parallel
Scavenge收集器的目标则是打到一个可控制的吞吐量。所谓吞吐量的意思就是CPU用于运行用户代码时间与CPU总消耗时间的比值,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),虚拟机总运行100分钟,垃圾收集1分钟,那吞吐量就是99%。另外,Parallel Scavenge收集器是虚拟机运行在Server模式下的默认垃圾收集器。

停顿时间短适合需要与用户交互的程序,良好的响应速度能提升用户体验;高吞吐量则可以高效率利用CPU时间,尽快完成运算任务,主要适合在后台运算而不需要太多交互的任务。

虚拟机提供了-XX:MaxGCPauseMillis和-XX:GCTimeRatio两个参数来精确控制最大垃圾收集停顿时间和吞吐量大小。不过不要以为前者越小越好,GC停顿时间的缩短是以牺牲吞吐量和新生代空间换取的。由于与吞吐量关系密切,Parallel Scavenge收集器也被称为“吞吐量优先收集器”。Parallel Scavenge收集器有一个-XX:+UseAdaptiveSizePolicy参数,这是一个开关参数,这个参数打开之后,就不需要手动指定新生代大小、Eden区和Survivor参数等细节参数了,虚拟机会根据当前系统的运行情况手机性能监控信息,动态调整这些参数以提供最合适的停顿时间或者最大的吞吐量。如果对于垃圾收集器运作原理不太了解,以至于在优化比较困难的时候,使用Parallel Scavenge收集器配合自适应调节策略,把内存管理的调优任务交给虚拟机去完成将是一个不错的选择。

3.4.4.Serial Old收集器

Serial收集器的老年代版本,同样是一个单线程收集器,使用“标记-整理算法”,这个收集器的主要意义也是在于给Client模式下的虚拟机使用。

3.4.5.Parallel Old收集器

Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法。这个收集器在JDK 1.6之后的出现,“吞吐量优先收集器”终于有了比较名副其实的应用组合,在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge收集器+Parallel Old收集器的组合。运行过程如下图所示:
JVM的内存模型以及性能调优

3.4.6.CMS收集器

CMS(Conrrurent
Mark Sweep)收集器是以获取最短回收停顿时间为目标的收集器。使用标记 - 清除算法,收集过程分为如下四步:

(1). 初始标记,标记GCRoots能直接关联到的对象,时间很短。

(2). 并发标记,进行GCRoots Tracing(可达性分析)过程,时间很长。

(3). 重新标记,修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,时间较长。

(4). 并发清除,回收内存空间,时间很长。

其中,并发标记与并发清除两个阶段耗时最长,但是可以与用户线程并发执行。运行过程如下图所示:

JVM的内存模型以及性能调优

说明:

  1. 对CPU资源非常敏感,可能会导致应用程序变慢,吞吐率下降。

  2. 无法处理浮动垃圾,因为在并发清理阶段用户线程还在运行,自然就会产生新的垃圾,而在此次收集中无法收集他们,只能留到下次收集,这部分垃圾为浮动垃圾,同时,由于用户线程并发执行,所以需要预留一部分老年代空间提供并发收集时程序运行使用。

  3. 由于采用的标记 - 清除算法,会产生大量的内存碎片,不利于大对象的分配,可能会提前触发一次Full GC。虚拟机提供了-XX:+UseCMSCompactAtFullCollection参数来进行碎片的合并整理过程,这样会使得停顿时间变长,虚拟机还提供了一个参数配置,-XX:+CMSFullGCsBeforeCompaction,用于设置执行多少次不压缩的Full GC后,接着来一次带压缩的GC。

3.4.7.G1收集器

G1是目前技术发展的最前沿成果之一,HotSpot开发团队赋予它的使命是未来可以替换掉JDK1.5中发布的CMS收集器。与其他GC收集器相比,G1收集器有以下特点:

(1). 并行和并发。使用多个CPU来缩短Stop
The World停顿时间,与用户线程并发执行。

(2). 分代收集。独立管理整个堆,但是能够采用不同的方式去处理新创建对象和已经存活了一段时间、熬过多次GC的旧对象,以获取更好的收集效果。

(3). 空间整合。基于标记 - 整理算法,无内存碎片产生。

(4). 可预测的停顿。能简历可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒。

在G1之前的垃圾收集器,收集的范围都是整个新生代或者老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局与其他收集器有很大差别,它将整个Java堆划分为多个大小相等的独立区域(Region),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,它们都是一部分(可以不连续)Region的集合。

3.5.理解GC日志

每种收集器的日志形式都是由它们自身的实现所决定的,换言之,每种收集器的日志格式都可以不一样。不过虚拟机为了方便用户阅读,将各个收集器的日志都维持了一定的共性,来看下面的一段GC日志:

[GC [DefNew: 310K->194K(2368K), 0.0269163 secs]310K->194K(7680K), 0.0269513 secs] [Times: user=0.00 sys=0.00, real=0.03
secs] [GC [DefNew: 2242K->0K(2368K), 0.0018814 secs]
2242K->2241K(7680K), 0.0019172 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System) [Tenured: 2241K->193K(5312K),0.0056517 secs] 4289K->193K(7680K), [Perm : 2950K->2950K(21248K)],
0.0057094 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
Heap def new generation   total 2432K, used 43K
[0x00000000052a0000, 0x0000000005540000, 0x0000000006ea0000)
eden space 2176K, 2% used [0x00000000052a0000, 0x00000000052aaeb8, 0x00000000054c0000)
from space 256K, 0% used [0x00000000054c0000, 0x00000000054c0000, 0x0000000005500000)
to   space 256K, 0% used [0x0000000005500000,0x0000000005500000, 0x0000000005540000)
tenured generation   total 5312K, used 193K
[0x0000000006ea0000, 0x00000000073d0000, 0x000000000a6a0000)
the space 5312K, 3% used [0x0000000006ea0000, 0x0000000006ed0730, 0x0000000006ed0800,
0x00000000073d0000)
compacting perm gentotal 21248K, used 2982K [0x000000000a6a0000, 0x000000000bb60000,0x000000000faa0000)
the space 21248K, 14% used [0x000000000a6a0000, 0x000000000a989980, 0x000000000a989a00,0x000000000bb60000)
No shared spaces configured.

1、日志的开头“GC”、“Full GC”表示这次垃圾收集的停顿类型,而不是用来区分新生代GC还是老年代GC的。如果有Full,则说明本次GC停止了其他所有工作线程(Stop-The-World)。看到Full GC的写法是“Full GC(System)”,这说明是调用System.gc()方法所触发的GC。

2、“GC”中接下来的“[DefNew”表示GC发生的区域,这里显示的区域名称与使用的GC收集器是密切相关的,例如上面样例所使用的Serial收集器中的新生代名为“Default New Generation”,所以显示的是“[DefNew”。如果是ParNew收集器,新生代名称就会变为“[ParNew”,意为“Parallel New Generation”。如果采用Parallel
Scavenge收集器,那它配套的新生代称为“PSYoungGen”,老年代和永久代同理,名称也是由收集器决定的。

3、后面方括号内部的“310K->194K(2368K)”、“2242K->0K(2368K)”,指的是该区域已使用的容量->GC后该内存区域已使用的容量(该内存区总容量)。方括号外面的“310K->194K(7680K)”、“2242K->2241K(7680K)”则指的是GC前Java堆已使用的容量->GC后Java堆已使用的容量(Java堆总容量)。

4、再往后“0.0269163 secs”表示该内存区域GC所占用的时间,单位是秒。最后的“[Times: user=0.00 sys=0.00 real=0.03 secs]”则更具体了,user表示用户态消耗的CPU时间、内核态消耗的CPU时间、操作从开始到结束经过的墙钟时间。后面两个的区别是,墙钟时间包括各种非运算的等待消耗,比如等待磁盘I/O、等待线程阻塞,而CPU时间不包括这些耗时,但当系统有多CPU或者多核的话,多线程操作会叠加这些CPU时间,所以如果看到user或sys时间超过real时间是完全正常的。

5、“Heap”后面就列举出堆内存目前各个年代的区域的内存情况。

3.6.内存泄漏和内存溢出

1、内存泄漏memory leak :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。

2、内存溢出 out of memory :指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。

3.7.JVM内存泄露线上排查

3.7.1.堆溢出java.lang.OutOfMemoryError:

Java heap space
原因:这种场景最为常见,它表明无法在Java堆中分配对象。这个错误并不一定意味着内存泄露。

可能仅仅是配置的问题,如应用程序指定的堆大小(或默认大小,如果不指定)不合适。代码中可能存在大对象分配。可能存在内存泄露,导致在多次GC之后,还是无法找到一块足够大的内存容纳当前对象。

措施:检查是否存在大对象的分配,最有可能的是大数组,大的list(如果存在很大的对象,请尽可能预先分配好长度)

通过jmap命令,把堆内存dump下来,使用mat工具分析一下,检查是否存在内存泄露的问题
如果没有找到明显的内存泄露,使用 -Xmx 加大堆内存
还有一点容易被忽略,考虑是否过度使用finalizer,例如:应用程序创建高优先级线程,导致终结(finalization)队列的增长速度比finalizer线程为该队列提供服务的速度更快。

3.7.2.永久代/元空间溢出java.lang.OutOfMemoryError: PermGen space

原因:JDK1.8(不含)之前的JDK通过分配永久保存区域(Permanent Generation space)加载class,系统默认设置不能满足系统加载的要求,系统运行一段时间后,永久保存区域占满就出现内存溢出。JDK8后,元空间(MetaspaceSize)替换了永久代,元空间使用的是本地内存,还有其它细节变化:字符串常量由永久代转移到堆中 和永久代相关的JVM参数已移除

措施:检查是否永久代空间或者元空间设置的过小(jdk8中元空间的默认初始大小是20.75MB,默认的元空间的最大值是无限,受本地内存限制。)

  1. 检查代码中是否存在大量的反射操作
  2. dump之后通过mat检查是否存在大量由于反射生成的代理类
  3. 重启JVM

3.7.3.GC Overhead limit exceeded java.lang.OutOfMemoryError:

GC Overhead limit exceeded

原因:详细信息“GC Overhead limit exceeded”标识垃圾收集器一直在运行,Java程序进行非常慢。在一次垃圾回收之后,如果Java进程花费了超过大约98%的时间进行垃圾回收,而如果它回收小于2%的堆,并且在最后5次(编译时常数)连续垃圾收集中均如此,那么会抛出java.lang.OutOfMemoryError。抛出这个异常通常是由于Java堆无法容纳存活的数据量,没有多少可用空间用于新的分配。

措施:检查项目中是否有大量的死循环或有使用大内存的代码,优化代码。

  1. 增加堆大小
  2. 如果没有,加大内存。

3.7.4.Metaspace java.lang.OutOfMemoryError: Metaspace

原因:错误所表达的信息是: 元数据区(Metaspace)
已被用满,这和Metaspace 的使用量与JVM加载到内存中的 class 数量/大小有关。可以说,java.lang.OutOfMemoryError:
Metaspace 错误的主要原因, 是加载到内存中的class 数量太多或者体积太大。

措施:增加 Metaspace的大小:-XX:MaxMetaspaceSize=512m

3.7.5.超大数组分配 java.lang.OutOfMemoryError:Requested array size exceeds VM limit

原因:该错误表示应用程序(或者应用程序使用的API)试图分配大于堆大小的数组。例如,如果应用程序试图分配一个512MB的数组,但是最大堆大小是256MB,那么将抛出OutOfMemoryError,原因是“Requested array size
exceeds VM limit”。

措施

  1. 检查代码中是否存在创建大数组(例如sql不分页之类的)
  2. 调整堆的大小

3.7.6.request size bytes for reason java.lang.OutOfMemoryError:request size bytes for reason. Out of swap space

原因:当本地堆分配失败以及本地堆可能接近耗尽时,Java HotSpot VM代码会报告这个异常。该消息显示失败请求的大小(字节)以及内存请求原因。

措施:调整堆的大小。

3.8.jvm调优

对JVM内存的系统级的调优主要的目的是减少GC的频率和Full GC的次数,过多的GC和Full GC是会占用很多的系统资源(主要是CPU),影响系统的吞吐量。特别要关注Full GC,因为它会对整个堆进行整理。

3.8.1常用的JVM调优参数?

•初始堆大小
-Xms2048M最小内存2048M

• 最大堆大小 -Xmx2048M最大内存2048M

• -XX:NewRatio=4设置新生代的和老年代的内存比例为 1:4

-XX:MaxPermSize=n 设置持久代大小

• -XX:SurvivorRatio=8设置新生代 Eden 和 Survivor 比例为
8:2

• -XX:+UseConcMarkSweepGC指定使用 CMS + Serial Old 垃圾回收器组合;

• -XX:+PrintGC开启打印
gc 信息;

• -XX:+PrintGCDetails打印 gc 详细信息。

3.8.2. JVM 调优的工具?

JDK 自带了很多监控工具,都位于 JDK 的
bin 目录下,其中最常用的是 jconsole 和
jvisualvm 这两款视图监控工具。

• jconsole:用于对 JVM 中的内存、线程和类等进行监控;

• jvisualvm: JDK 自带的全能分析工具,可以分析:内存快照、线程快照、程序死锁、监控内存的变化、 gc 变化等。

相关标签: 随手笔记