持久代

Permanent Generation space(JDK1.7及以下版本存在)

  • 主要存放存放class文件中类的版本、字段、方法、接口,类和方法的元数据等描述信息,运行时常量池,用于存放编译器生成的各种字面量和符号引用。
  • 上限是MaxPermSize
  • PremGen与old区绑定在一起,在fullGC时会一起被GC

MetaSpace(JDK1.8及以上版本)

PermGen中方法区移至Metaspace,字符串常量移至Java Heap。类的元数据信息(metadata)还在,只不过不再是存储在连续的堆空间上,而是移动到叫做“Metaspace”的本地内存(Native memory)中。

** MetaSpace组成

**

  • Klass Metaspace: 用来存klass的(class文件在jvm里的运行时数据结构),这块内存是紧接着Heap的。
  • NoKlass Metaspace: 专门来存klass相关的其他的内容,比如method,constantPool等,这块内存是由多块内存组合起来的,所以可以认为是不连续的内存块组成的。这块内存是必须的,虽然叫做NoKlassMetaspace,但是也其实可以存klass的内容,上面已经提到了对应场景。如果KlassMetaspace用完了,那就会OOM了,不过一般情况下不会,NoKlassMestaspace是由一块块内存慢慢组合起来的,在没有达到限制条件的情况下,会不断加长这条链,让它可以持续工作。

MetaSpace主要参数

  • -XX:MetaspaceSize ,初始空间大小,达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整。
  • -XX:MaxMetaspaceSize ,最大空间,默认是没有限制的(自增)。 建议设置,如果出现内存增长不释放,会占用所有的内存空间直至被OS kill掉。
  • -XX:CompressedClassSpaceSize ,默认1G,这个参数主要是设置KlassMetaspace的大小,不过这个参数设置了也不一定起作用,前提是能开启压缩指针,假如Xmx超过了32G,压缩指针是开启不来的。如果有Klass Metaspace,那这块内存是和Heap连着的。
  • -XX:InitialBootClassLoaderMetaspaceSize ,64位下默认4M,32位下默认2200K,NoKlassMetaspace是由一块块内存组合起来的,这个参数决定了NoKlassMetaspace的第一个内存Block的大小,即2*InitialBootClassLoaderMetaspaceSize,同时为bootstrapClassLoader的第一块内存chunk分配了InitialBootClassLoaderMetaspaceSize的大小。
  • -XX:MinMetaspaceExpansion和XX:MaxMetaspaceExpansion ,默认分别是332.8K和5.2M,这两个值为了增大触发metaspace GC的阈值。gcLocker或者should_concurrent_collect的一些场景,因为这些场景下接下来会做一次GC,相信在接下来的GC中可能会释放一些metaspace的内存,于是先临时扩大下metaspace触发GC的阈值,而有些内存分配失败其实正好是因为这个阈值触顶导致的,于是可以通过增大阈值暂时绕过去。
  • -XX:MinMetaspaceFreeRatio和XX:MaxMetaspaceFreeRatio ,默认分别是默认40和70,主要是影响触发metaspaceGC的阈值。每次GC完之后,假设我们允许接下来metaspace可以继续被commit的内存占到了被commit之后总共committed的内存量的MinMetaspaceFreeRatio%,如果这个总共被committed的量比当前触发metaspaceGC的阈值要大,那么将尝试做扩容,增大触发metaspaceGC的阈值,不过这个增量至少是MinMetaspaceExpansion才会做,不然不会增加这个阈值。这个参数主要是为了避免触发metaspaceGC的阈值和gc之后committed的内存的量比较接近,于是将这个阈值进行扩大。一般情况下在gc完之后,如果被committed的量还是比较大的时候,换个说法就是离触发metaspaceGC的阈值比较接近的时候,这个调整会比较明显。

java.lang.out of memory

内存泄漏与内存溢出

先了解下内存泄漏与内存溢出的区别

out of memory:是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。

memory leak:是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。

memory leak会最终会导致out of memory!

JVM常见内存溢出区域

堆溢出

java.lang.OutOfMemoryError:Java heap space

堆溢出GC过程:

  • Eden内存不够》进入old分配》分配失败触发youngGC》分配失败触发fullGC(soft reference不会被强制回收)》分配失败再强制fullGC(包括soft reference)》分配失败抛出OOM

解决方案:

  • 检查程序,看是否有死循环或不必要地重复创建大量对象。
  • 增加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的大小。
  • 打印heapdump文件和GC日志分析:-Xmx1536m -Xms1536m -Xmn1024m -XX:SurvivorRatio=4 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=路径 -XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -XX:+PrintHeapAtGC -Xloggc:../logs/gc.log

永久区内存溢出

java.lang.OutOfMemoryError:PermGen space

  • 永久区是存放类元数据的区域。JDK1.8中,永久区被一块元数据的区域替代。

解决方案:

  • 应用程序清理引用来触发类卸载。
  • 增加MaxPermSize的大小。
  • 减少系统需要的类的数量。
  • 使用classloader合理地装载各个类,并定期回收。

out_of_memory_error_matespace

问题引发:

  • 太多的类或太大的类加载到元空间。
  • 内存泄露会耗尽整个本地内存,导致内存交换(swapping)

解决方案:

  • 设置XX:MaxMetaspaceSize的大小。

直接内存溢出

java.lang.OutOfMemoryError:allocate Memory

  • java的NIO中支持直接内存使用,可以通过java代码获取的一块堆外的内存空间,这块内存是直接向操作系统申请的,其申请速度比堆内存慢,是访问速度要快于堆内存。此空间没有被JVM完全托管,使用不当会造成内存溢出。

解决方案:

  • 设置-XX:MaxDirectMenorySize。若不设置最大内存可以使用到Xmx的值(建议)
  • 使用完毕后执行system.gc(慎用)

线程过多导致OOM

java.lang.OutOfMemoryError: unable to create new native thread

每个线程都要占用内存空间,因此当线程数量太多时,也有可能导致OOM,由于线程的栈空间是堆外内存分配的,因此与直接内存非常相似。

问题引发:

  • 线程的栈空间在堆外内存分配,线程太多导致OOM

解决方案:

  • 尝试减少堆空间(Xmx)
  • 减少每一个线程所占的内存空间,使用Xss参数(默认1M)可以指定线程的栈空间
  • 减少线程数

GC效率低下导致溢出

java.lang.OutOfMemoryError:GC overhead limit exceeded

  • GC overhead limt exceed检查是Hotspot VM 1.6定义的一个策略,通过统计GC时间来预测是否要OOM了,提前抛出异常,防止OOM发生。Sun 官方对此的定义是:“并行/并发回收器在GC回收时间过长时会抛出OutOfMemroyError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存。用来避免内存过小造成应用不能正常工作

触发条件:

  1. 花在GC上的时间是否超过了98%
  2. 老年代释放内存是否小于2%
  3. eden内存是否小于2%
  4. 以上情况同时且连续5次出现
  • 假如生产环境中遇到了这个问题,在不知道原因时不要简单地猜测和规避。可以通过**-verbose:gc -XX:+PrintGCDetails 看下到底什么原因造成了异常。通常原因都是因为 old区占用过多导致频繁Full GC**,最终导致GC overhead limit exceed。如果gc log不够可以借助于JProfile等工具查看内存的占用,old区是否有内存泄露。分析内存泄露还有一个方法**-XX:+HeapDumpOnOutOfMemoryError**,这样OOM时会自动做Heap Dump,可以拿MAT来排查了。还要留意 young区,如果有过多短暂对象分配,可能也会抛这个异常 。
  • 下载数据时文件大小超过某一峰值 是会报这个错误。原因是在页面点击下载时,在数据库查询了很庞大的数据量,导致内存使用增加,才会出现这个问题。

解决办法:

  • 查看项目中是否有大量的死循环或有使用大内存的代码,优化代码。
  • JVM给出这样一个参数: -XX:UseGCOverheadLimit 禁用这个检查,其实这个参数解决不了内存问题,只是把错误的信息延后,替换成 java.lang.OutOfMemoryError: Java heap space。
  • 增大堆内存 set JAVA_OPTS=server Xms512m Xmx1024m XX:MaxNewSize=1024m XX:MaxPermSize=1024m

遇到OOM我们应该怎么办?

  • 如果有设置**-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=路径,直接拿dump文件去分析,常用工具: MAT 与Visual VM(JDK自带的jvisualvm.exe)。**
  • 如果没有设置,则执行 jmap -dump:live,format=b,file=filename.bin PID ,拿到****dump文件再去分析。

最后补充一个小知识点:

浅堆(Shallow Heap)与深堆(Retained Heap)

  • 浅堆 :一个对象所消耗的内存。
  • 深堆 :一个对象被回收后,可以真实释放的内存大小。

对象的实际大小定义为一个对象所能触及的所有对象的浅堆大小之和,也就是通常意义上说的对象大小。与深堆相比,这个概念在日常中更容易被接受,但实际上这个概念和垃圾回收无关。

如图所示:对象A的实际大小为A、C、D之和,但A的深堆大小为A、D之和。

 

原文链接:https://juejin.cn/post/6931181310489100301

如果觉得本文对你有帮助,可以关注一下我公众号,回复关键字【面试】即可得到一份Java核心知识点整理与一份面试大礼包!另有更多技术干货文章以及相关资料共享,大家一起学习进步!