JVM内存结构和调优

JVM

   1. Java源文件----jvm编译器-->字节码文件
   2. 字节码文件---->jvm解释器-->机器码

jvm的内存模型?

JVM有五部分组成,分别是java虚拟机栈、本地方法栈,程序计数器、方法区和堆。
其中方法区和堆线程共享的,其他的为私有。

栈和堆的介绍:(了解)

栈是运行时的单位,而堆是存储的。
栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;
堆解决的是数据存储的问题,即数据怎么放、放在哪儿。

在Java中一个线程就会相应有一个线程栈与之对应,这点很容易理解,因为不同的线程执行逻辑有所不同,因此需要一个独立的线程栈。而堆则是所有线程共享的。栈因为是运行单位,因此里面存储的信息都是跟当前线程(或程序)相关信息的。包括局部变量、程序运行状态、方法返回值等等;而堆只负责存储对象信息。
栈代表了处理逻辑,而堆代表了数据。
堆与栈的分离,使得堆中的内容可以被多个栈共享(也可以理解为多个线程访问同一个对象)。
面向对象就是堆和栈的完美结合。
对象的属性其实就是数据,存放在堆中;而对象的行为(方法),就是运行逻辑,放在栈中。
在Java中,Main函数就是栈的起始点,也是程序的起始点。

JVM内存结构图

在这里插入图片描述

java虚拟机栈

这块空间是一个栈结构。特点是后进先出一个线程对应一个java虚拟机栈
栈里面的每一个元素是栈帧,一个栈帧对应线程执行的一个方法。线程运行哪个方法,这个方法就要被封装成栈帧放入到java虚拟机栈里面

每个线程run时都会分配自己的栈内存空间,每个方法运行时会在自己的栈内存中分配栈帧内存区,各方法的局部变量存储于各自的栈帧内存区,
方法调用直至结束,就是栈帧入栈到出栈的过程
栈帧随着方法的调用而创建,随着方法结束而销毁

栈帧的介绍:(了解)

一个栈帧,又分为四块:
1.局部变量表:方法的局部变量。基本类型的变量直接存储。对象型的变量,在这个表里面存储的是引用。
2.操作数栈:cpu在运行指令时需要用到的基础数据,都存放在这块空间里面。
3.动态链接:方法里面接口类型的变量调用方法时,真正的指令地址。符号引用转换为直接引用。
4.方法出口:栈帧对应的方法运行完后要返回到哪。

本地方法栈

这个空间的结构根java虚拟机栈的结构一模一样,是用来存储本地方法信息。也就是native修饰的方法。(本地方法:简单地讲,一个Native Method就是一个java调用非java代码的接口)。也会有 StackOverflowError 和 OutOfMemoryError 异常。

虚拟机栈和本地方法栈都会出现StackOverflowError和OutofMemoryError,程序计数器是java虚拟机中唯一一块不会产生error的内存区域,

StackOverflowErro:如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;
OutofMemoryError代表的是,当再申请新的内存时,虚拟机分配给线程的内存大小中无法再分配新的内存,就会出现此error。

虚拟机栈和本地方法栈为什么是私有的?

  • 虚拟机栈: 每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
  • 本地方法栈: 和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

所以,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的

程序计数器

程序计数器跟线程相关,一个线程对应一个程序计数器
存放的是对应线程运行到了哪一行指令。

程序计数器为什么是私有的?

程序计数器主要有下面两个作用:

  • 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
  • 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
    需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。

所以,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置。

方法区

属于共享内存区域,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 方法区有时被称为持久代(PermGen) ,当方法区无法满足内存分配需求时,将抛出OutOfMemoryError异常。 这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载 。

对于大多数应用来说,Java堆(Java Heap)是Java虚拟机所管理的内存中最大的一块。Java堆属于共享内存区域,在虚拟机启动时创建。几乎所有(有例外)的对象实例都在这里分配内存。 所有的对象在实例化后的整个运行周期内,都被存放在堆内存中

Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可,就像我们的磁盘空间一样。在实现时,既可以实现成固定大小的,也可以是可扩展的,不过当前主流的虚拟机都是按照可扩展来实现的(通过-Xmx堆的最大空间大小和-Xms堆的最小空间大小控制)。

如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出OutOfMemoryError异常。

Java堆是垃圾收集器管理的主要区域,如果从内存回收的角度看,由于现在收集器基本都是采用的分代收集算法,所以Java堆中还可以细分为:新生代和老年代;

在这里插入图片描述

JVM内存结构?创建的对象一定会放在堆中吗?

一般认为new出来的对象都是被分配在堆上,但是这个结论不是那么的绝对,通过对Java对象分配的过程分析,可以知道有两个地方会导致Java中new出来的对象并一定分别在所认为的堆上。这两个点分别是Java中的逃逸分析TLAB(Thread Local Allocation Buffer

1.逃逸分析

Java在Java SE 6u23(1.6)以及以后的版本中支持并默认开启了逃逸分析的选项。Java的 HotSpot JIT编译器,能够在方法重载或者动态加载代码的时候对代码进行逃逸分析
经过逃逸分析之后,可以得到三种对象的逃逸状态
GlobalEscape(全局逃逸), 即一个对象的引用逃出了方法或者线程。例如,一个对象的引用是复制给了一个类变量,或者存储在在一个已经逃逸的对象当中,或者这个对象的引用作为方法的返回值返回给了调用方法。
ArgEscape(参数级逃逸),即在方法调用过程当中传递对象的应用给一个方法。这种状态可以通过分析被调方法的二进制代码确定。
NoEscape(没有逃逸),一个可以进行标量替换的对象。可以不将这种对象分配在传统的堆上。

编译器可以使用逃逸分析的结果,对程序进行一下优化。
堆分配对象变成栈分配对象。一个方法当中的对象,对象的引用没有发生逃逸,那么这个方法可能会被分配在栈内存上而非常见的堆内存上
消除同步。线程同步的代价是相当高的,同步的后果是降低并发性和性能。逃逸分析可以判断出某个对象是否始终只被一个线程访问,如果只被一个线程访问,那么对该对象的同步操作就可以转化成没有同步保护的操作,这样就能大大提高并发程度和性能。
矢量替代。逃逸分析方法如果发现对象的内存存储结构不需要连续进行的话,就可以将对象的部分甚至全部都保存在CPU寄存器内,这样能大大提高访问速度。

2.TLAB

JVM在内存新生代Eden Space中开辟了一小块线程私有的区域,称作TLAB(Thread-local allocation buffer)默认设定为占用Eden Space的1%。在Java程序中很多对象都是小对象且用过即丢,它们不存在线程共享也适合被快速GC,所以对于小对象通常JVM会优先分配在TLAB上,并且TLAB上的分配由于是线程私有所以没有锁开销。因此在实践中分配多个小对象的效率通常比分配一个大对象的效率要高。
也就是说,Java中每个线程都会有自己的缓冲区称作TLAB(Thread-local allocation buffer),每个TLAB都只有一个线程可以操作,TLAB结合bump-the-pointer技术可以实现快速的对象分配,而不需要任何的锁进行同步,也就是说,在对象分配的时候不用锁住整个堆,而只需要在自己的缓冲区分配即可。
关于对象分配的JDK源码可以参见 JVM 之 Java对象创建[初始化]中对OpenJDK源码的分析。

3. Java对象分配的过程

编译器通过逃逸分析,确定对象是在栈上分配还是在堆上分配。如果是在堆上分配,则进入选项2.
如果tlab_top + size <= tlab_end,则在在TLAB上直接分配对象并增加tlab_top 的值,如果现有的TLAB不足以存放当前对象则3.
重新申请一个TLAB,并再次尝试存放当前对象。如果放不下,则4.
在Eden区加锁(这个区是多线程共享的),如果eden_top + size <= eden_end则将对象存放在Eden区,增加eden_top 的值,如果Eden区不足以存放,则5.
执行一次Young GC(minor collection)。
经过Young GC之后,如果Eden区任然不足以存放当前对象,则直接分配到老年代。
对象不在堆上分配主要的原因还是堆是共享的,在堆上分配有锁的开销。无论是TLAB还是栈都是线程私有的,私有即避免了竞争(当然也可能产生额外的问题例如可见性问题),这是典型的用空间换效率的做法。

gc垃圾回收算法

如何判断一个对象是否是垃圾对象?

①引用计数器算法。②对象可达性算法。

引用计数器算法的缺点:(了解)

a) 需要额外的空间和时间来维护计数器
b) 但是难以解决循环引用问题 。
主流的jvm都不采用这个算法。

对象可达性算法:

需要去遍历gc root里面的引用,能被gc root找到的对象就不是垃圾对象。

JDK 1.2以前的版本中,若一个对象不被任何变量引用,那么程序就无法再使用这个对象。也就是说,只有对象处于(reachable)可达状态,程序才能使用它。
JDK 1.2版本开始,对象的引用被划分为4种级别,从而使程序能更加灵活地控制对象的生命周期。这4种级别由高到低依次为:强引用、软引用、弱引用和虚引用
详细

gc root是什么?

java虚拟机栈中局部变量表。本地方法栈里面的局部变量表。方法区。

如何清除掉垃圾对象?

常用复制算法、标记-清除算法

复制算法

把堆空间分成两个相同大小的空间。先利用一块空间存储对象,当这块空间满了,就把这个空间中的对象复制到另一块空间里面。
效率最高。但是空间利用率低。
指针碰撞:非垃圾对象都存储在连续的空间里面,当有新对象创建的时候,我们让指针进行直接偏移来存储数据。这种方式就是指针碰撞。

标记-清除算法

先标记非垃圾对象。然后再清除掉没有标记的对象。
效率不高。需要两次遍历。但空间利用率高。
对象的空间不连续,需要有一个空闲列表来存储空闲区域。
空闲列表跟指针碰撞含义刚好相反。

什么样的对象会被放入到老年代?

通过判断生命值大小、对象大小等,可以根据需要来调整参数。

  1. 生命值达到15.
  2. 对象大小超过3M
  3. 动态年龄的判断。s0 区域的空间达到50%后,进行年龄判断。
  4. 当极端情况,s区域的空间不够用了,直接把对象晋升为老年代对象。

JVM堆空间的老年代和新生代及GC算法的不同?>

hotspot虚拟机的堆空间是分代管理的。分为新生代和老年代,比例是1:2。新创建的对象会默认放在新生代。新生代又分为三个区域,分别是伊甸区、s0和s1区比例为eden:s0:s1=8:1:1

在这里插入图片描述
新生代每次垃圾回收都有大量对象死去,只有少量存活,选用复制算法比较合理。

老年代中对象存活率较高、没有额外的空间分配对它进行担保。所以必须使用 标记 —— 清除

新生代 GC (Minor GC) ——Minor /ˈmaɪnə/ adj. 轻微的; 较小的; 次要的

发生在新生代的垃圾回收动作,频繁,速度快

老年代 GC (Major GC / Full GC)

发生在老年代的垃圾回收动作,出现了Major GC 经常会伴随至少一次 Minor GC(非绝对)。Major GC 的速度一般会比 Minor GC 慢十倍以上

minor gc是新生代的垃圾回收。

major gc是老年代的垃圾回收。

full gc是小gc和大gc都发生。

Minor GC ,Full GC 触发条件

  • Minor GC触发条件:当Eden区满时,触发Minor GC。
  • Full GC触发条件:
  • (1)调用System.gc时,系统建议执行Full GC,但是不必然执行
  • (2)老年代空间不足
  • (3)方法区空间不足
  • (4)通过Minor GC后进入老年代的平均大小大于老年代的可用内存
  • (5)由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小

通过一张图来了解如何通过参数来控制各区域的内存大小
在这里插入图片描述
控制参数

堆区配置:

  • -Xms设置堆的最小空间大小
  • -Xmx设置堆的最大空间大小
  • -XX:NewSize设置新生代最小空间大小
  • -XX:MaxNewSize设置新生代最大空间大小

没有直接设置老年代的参数,但是可以设置堆空间大小和新生代空间大小两个参数来间接控制。
老年代空间大小=堆空间大小-年轻代空间大小

Old 区的大小等于 Xmx 减去 -Xmn

非堆区配置:

  • -XX:PermSize设置永久代最小空间大小
  • -XX:MaxPermSize设置永久代最大空间大小
  • -Xss设置每个线程的堆栈大小。

栈调整:参数有 +UseDefaultStackSize -Xss256K,表示每个线程可申请 256k的栈空间

1.提示: Heap Size 最大不要超过可用物理内存的 80%,一般的要将 -Xms和 -Xmx选项设置为相同,而 -Xmn为 1/4的 -Xmx值。

2.提示: JVM初始分配的内存由 -Xms指定,默认是物理内存的 1/64; JVM最大分配的内存由 -Xmx指定,默认是物理内存的 1/4。

3默认空余堆内存小于 40%时, JVM就会增大堆直到 -Xmx的最大限制;空余堆内存大于 70%时, JVM会减少堆直到 -Xms的最小限制。因此服务器一般设置 -Xms、 -Xmx相等以避免在每次 GC 后调整堆的大小。

4.提示:假设物理内存无限大的话, JVM内存的最大值跟操作系统有很大的关系。

简单的说就 32位处理器虽然可控内存空间有 4GB,但是具体的操作系统会给一个限制,

这个限制一般是 2GB-3GB(一般来说 Windows系统下为 1.5G-2G, Linux系统下为 2G-3G), 而 64bit以上的处理器就不会有限制了

5.提示:注意:如果 Xms超过了 Xmx值,或者堆最大值和非堆最大值的总和超过了物理内 存或者操作系统的最大限制都会引起服务器启动不起来。

6.提示:设置 NewSize、 MaxNewSize相等, “new”的大小最好不要大于 “old”的一半,原因是 old区如果不够大会频繁的触发 “主 ” GC ,大大降低了性能

JVM使用 -XX:PermSize设置非堆内存初始值,默认是物理内存的 1/64;

由 XX:MaxPermSize设置最大非堆内存的大小,默认是物理内存的 1/4。

Tomcat 设置示例

手动设置 Heap size
修改tomcat/bin目录下的catalina.bat。(windows环境下修改文件catalina.bat, linux环境下修改文件catalina.sh)

在“ echo “Using CATALINA_BASE: $CATALINA_BASE””上面加入以下行:

JAVA_OPTS=”-server -Xms800m -Xmx800m -XX:MaxNewSize=256m”

我们经常在tomcat的启动参数中添加JVM相关参数,这里有一个典型的示例:

JAVA_OPTS="-server -Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256m -XX:SurvivorRatio=4-verbose:gc -Xloggc:$CATALINA_HOME/logs/gc.log -Djava.awt.headless=true -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -Dsun.rmi.dgc.server.gcInterval=600000 -Dsun.rmi.dgc.client.gcInterval=600000-XX:+UseConcMarkSweepGC -XX:MaxTenuringThreshold=15"

根据上面的参数我们来做一下解析:

-Xms2000m-Xmx2000m-Xmn800m-XX:PermSize=64m-XX:MaxPermSize=256m
Xms,即为jvm启动时得JVM初始堆大小,Xmx为jvm的最大堆大小,xmn为新生代的大小,permsize为永久代的初始大小,MaxPermSize为永久代的最大空间。

-XX:SurvivorRatio=4
SurvivorRatio为新生代空间中的Eden区和救助空间Survivor区的大小比值,默认是32,也就是说Eden区是 Survivor区的32倍大小,要注意Survivo是有两个区的,因此Surivivor其实占整个young genertation的1/34。调小这个参数将增大survivor区,让对象尽量在survitor区呆长一点,减少进入年老代的对象。去掉救助空间的想法是让大部分不能马上回收的数据尽快进入年老代,加快年老代的回收频率,减少年老代暴涨的可能性,这个是通过将-XX:SurvivorRatio 设置成比较大的值(比如65536)来做到。

-verbose:gc-Xloggc:$CATALINA_HOME/logs/gc.log
将虚拟机每次垃圾回收的信息写到日志文件中,文件名由file指定,文件格式是平文件,内容和-verbose:gc输出内容相同。

-Djava.awt.headless=true Headless模式是系统的一种配置模式。在该模式下,系统缺少了显示设备、键盘或鼠标。

-XX:+PrintGCTimeStamps-XX:+PrintGCDetails
设置gc日志的格式

-Dsun.rmi.dgc.server.gcInterval=600000-Dsun.rmi.dgc.client.gcInterval=600000
指定rmi调用时gc的时间间隔

-XX:+UseConcMarkSweepGC-XX:MaxTenuringThreshold=15 采用并发gc方式,经过15次minor gc 后进入年老代

内存溢出和内存泄漏关系和区别?

内存泄露:

Memory Leak,申请的内存使用完之后没有释放,导致虚拟机不能再次使用该内存,此时这段内存就泄露了,内存泄漏次数多了就会导致内存溢出。

内存溢出:

OOM,全称“Out Of Memory”,申请的内存超出了JVM能提供的内存大小,此时称之为溢出。可能是代码或者算法设计有问题(比如递归调用可能导致)。

内存溢出的解决?

内存溢出的可能原因?

1启动参数内存值设定的过小;
2内存中加载的数据量过于庞大,如一次从数据库取出过多数据;
3集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
4代码中存在死循环或循环产生过多重复的对象实体;
5使用的第三方软件中的BUG;

先查找是哪儿的OutOfMemoryError

对内存结构清晰的认识同样可以帮助理解不同OutOfMemoryErrors:

1、原因:对象不能被分配到堆内存中

Exception in thread “main”: java.lang.OutOfMemoryError: Java heap space

2、原因:永久区域内存不足。它可能出现在一个程序加载很多类的时候,比如引用了很多第三方的库;

Exception in thread “main”: java.lang.OutOfMemoryError: PermGen space

3、原因:创建的数组大于堆内存的空间
exceeds 英[ɪkˈsiːdz] v. 超过(数量); 超越(法律、命令等)的限制;

Exception in thread “main”: java.lang.OutOfMemoryError: Requested array size exceeds VM limit

4、原因:分配本地分配失败。JNI、本地库或者Java虚拟机都会从本地堆中分配内存空间。

Exception in thread “main”: java.lang.OutOfMemoryError: request <size> bytes for <reason>. Out of swap space?

5、原因:同样是本地方法内存分配失败,只不过是JNI或者本地方法或者Java虚拟机发现

Exception in thread “main”: java.lang.OutOfMemoryError: <reason> <stack trace>(Native method)

对应的解决办法

1启动参数内存值设定的过小时,直接修改JVM启动参数,增加内存。

JVM默认可以使用的内存为64MTomcat默认可以使用的内存为128MB。这对于其他大型引用绝对是不够的

2检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误。

3对代码进行走查和分析,

检查代码中是否有死循环或递归调用

检查是否有大循环重复产生新对象实体

检查对数据库查询中,是否有一次获得全部数据的查询,而没有使用分页

检查List、MAP等集合对象是否有使用完后,未清除的问题。List、MAP等集合对象会始终存有对对象的引用,使得这些对象不能被GC回收。

4、尽早释放无用对象的引用。好的办法是使用临时变量的时候,让引用变量在退出活动域后,自动设置为 null ,暗示垃圾收集器来收集该对象,防止发生内存泄露。

对于仍然有指针指向的实例, jvm 就不会回收该资源 , 因为垃圾回收会将值为 null 的对象作为垃圾,提高 GC 回收机制效率;

6 、尽量少用静态变量,因为静态变量是全局的, GC 不会回收的;

7 、避免集中创建对象尤其是大对象, JVM 会突然需要大量内存,这时必然会触发 GC 优化系统内存环境;显示的声明数组空间,而且申请数量还极大。

比如:

有个excel文件上传的功能,excel内容有非常大,每次上传都导致jvm需要大量的内存。

8 、尽量运用对象池技术以提高系统性能;生命周期长的对象拥有生命周期短的对象时容易引发内存泄漏,例如大集合对象拥有大数据量的业务对象的时候,可以考虑分块进行处理,然后解决一块释放一块的策略。

9 、不要在经常调用的方法中创建对象,尤其是忌讳在循环中创建对象。可以适当的使用 hashtable , vector 创建一组对象容器,然后从容器中去取那些对象,而不用每次 new 之后又丢弃

10 、一般都是发生在开启大型文件或跟数据库一次拿了太多的数据,造成 Out Of Memory Error的状况,这时就大概要计算一下数据量的最大值是多少,并且设定所需最小及最大的内存空间值。

使用内存查看工具动态查看内存使用情况

1内存查看工具有许多,比较有名的有:Optimizeit Profiler、JProbe Profiler、JinSight和Java1.5的Jconsole等。它们的基本工作原理大同小异,都是监测Java程序运行时所有对象的申请、释放等动作,将内存管理的所有信息进行统计、分析、可视化。开发人员可以根据这些信息判断程序是否有内存泄漏问题。一般来说,一个正常的系统在其启动完成后其内存的占用量是基本稳定的,而不应该是无限制的增长的。持续地观察系统运行时使用的内存的大小,可以看到在内存使用监控窗口中是基本规则的锯齿形的图线,如果内存的大小持续地增长,则说明系统存在内存泄漏问题。通过间隔一段时间取一次内存快照,然后对内存快照中对象的使用与引用等信息进行比对与分析,可以找出是哪个类的对象在泄漏。

内存溢出类型

1 java.lang.OutOfMemoryError: PermGen space

1JVM 管理两种类型的内存,堆和非堆。
堆是给开发人员用的 ;
非堆是留给 JVM 自己用的,用来存放类的信息的,运行期内 GC 不会释放空间。

如果 web app 用了大量的第三方 jar 或者应用有太多的 class 文件而恰好 MaxPermSize 设置较小,超出了也会导致这块内存的占用过多造成溢出,或者 tomcat 热部署时侯不会清理前面加载的环境,只会将 context 更改为新部署的,非堆存的内容就会越来越多。

2 java.lang.OutOfMemoryError: Java heap space

其默认空间 ( 即 -Xms) 是物理内存的 1/64 ,最大空间 (-Xmx) 是物理内存的 1/4 。如果内存剩余不到 40 %, JVM 就会增大堆到 Xmx 设置的值,内存剩余超过 70 %, JVM 就会减小堆到 Xms 设置的值。所以服务器的 Xmx 和 Xms 设置一般应该设置相同避免每次 GC 后都要调整虚拟机堆的大小。假设物理内存无限大,那么 JVM 内存的最大值跟操作系统有关,一般 32 位机是 1.5g 到 3g 之间,而 64 位的就不会有限制了。

注意:如果 Xms 超过了 Xmx 值,或者堆最大值和非堆最大值的总和超过了物理内存或者操作系统的最大限制都会引起服务器启动不起来。

GC 有两个线程:

新创建的对象被分配到 New 区,当该区被填满时会被 GC 辅助线程移到 Old 区,当 Old 区也填满了会触发 GC 主线程遍历堆内存里的所有对象。 Old 区的大小等于 Xmx 减去 -Xmn

栈调整:参数有 +UseDefaultStackSize -Xss256K,表示每个线程可申请 256k的栈空间

每个线程都有他自己的 Stack

其他的OOM异常的解决思路?(了解)

生成Dump快照文件:

通过jvm参数—XX:-HeapDumpOnOutOfMemoryError可以让JVM在出现内存溢出是Dump出当前的内存转储快照

用jmap生产dump文件,win通过任务管理器查看tomcat的进程pid,linux用ps命令查看进程pid,然后用jmap命令

先通过内存映像分析工具(如Eclipse的Memory Analyzer)进行分析,常见的情况有:

内存泄露,对象已经死了,无法通过垃圾收集器进行自动回收,通过找出泄露的代码位置和原因,才好确定解决方案;

内存溢出,内存中的对象都还必须存活着,这说明Java堆分配空间不足,检查堆设置大小(-Xmx与-Xms),检查代码是否存在对象生命周期太长、持有状态时间过长的情况。


版权声明:本文为qq_40419080原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。