地址: https://www.cnblogs.com/xiaoxi/p/7406903.html
1.JVM Heap(堆)溢出: java.lang.OutOfMemoryError: Java heap space
JVM 在启动的时候会自动设置 JVM Heap 的值, 可以利用 JVM 提供的 - Xmn -Xms -Xmx 等选项可进行设置. Heap 的大小是 Young Generation 和 Tenured Generaion 之和. 在 JVM 中如果 98% 的时间是用于 GC, 且可用的 Heap size 不足 2% 的时候将抛出此异常信息.
解决方法: 手动设置 JVM Heap(堆)的大小.
Java 堆用于储存对象实例. 当需要为对象实例分配内存, 而堆的内存占用又已经达到 - Xmx 设置的最大值. 将会抛出 OutOfMemoryError 异常. 例子如下:
- package com.demo.test;
- import java.util.ArrayList;
- import java.util.List;
- /**
- * VM Args: -Xms5m -Xmx5m
- */
- public class HeapOOM {
- public static void main(String[] args) {
- int count = 0;
- List<Object> list = new ArrayList<Object>();
- while(true){
- list.add(new Object());
- System.out.println(++count);
- }
- }
- }
然后在运行时设置 jvm 参数, 如下:
-Xmx 为 5m. 其中的一次测试结果为, 当 count 的值累加到 360145 时, 发生如下异常:
- Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
- at java.util.Arrays.copyOf(Arrays.java:2245)
- at java.util.Arrays.copyOf(Arrays.java:2219)
- at java.util.ArrayList.grow(ArrayList.java:213)
- at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:187)
- at java.util.ArrayList.add(ArrayList.java:411)
- at com.demo.test.HeapOOM.main(HeapOOM.java:12)
修改 - Xmx 为 10m. 其中的一次测试结果为, 当 count 的值累加到 540217 时, 发生 OutOfMemoryError 异常. 随着 - Xmx 参数值的增大, java 堆中可以存储的对象也越多.
2.PermGen space 溢出: java.lang.OutOfMemoryError: PermGen space
PermGen space 的全称是 Permanent Generation space, 是指内存的永久保存区域. 为什么会内存溢出, 这是由于这块内存主要是被 JVM 存放 Class 和 Meta 信息的, Class 在被 Load 的时候被放入 PermGen space 区域, 它和存放 Instance 的 Heap 区域不同, sun 的 GC 不会在主程序运行期对 PermGen space 进行清理, 所以如果你的 App 会载入很多 CLASS 的话, 就很可能出现 PermGen space 溢出. 一般发生在程序的启动阶段.
解决方法: 通过 - XX:PermSize 和 - XX:MaxPermSize 设置永久代大小即可.
方法区用于存放 java 类型的相关信息, 如类名, 访问修饰符, 常量池, 字段描述, 方法描述等. 在类装载器加载 class 文件到内存的过程中, 虚拟机会提取其中的类型信息, 并将这些信息存储到方法区. 当需要存储类信息而方法区的内存占用又已经达到 - XX:MaxPermSize 设置的最大值, 将会抛出 OutOfMemoryError 异常. 对于这种情况的测试, 基本的思路是运行时产生大量的类去填满方法区, 直到溢出. 这里需要借助 CGLib 直接操作字节码运行时, 生成了大量的动态类. 例子如下:
- package com.demo.test;
- import java.lang.reflect.Method;
- import.NET.sf.cglib.proxy.Enhancer;
- import.NET.sf.cglib.proxy.MethodProxy;
- import.NET.sf.cglib.proxy.MethodInterceptor;
- /**
- * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
- */
- public class MethodAreaOOM {
- public static void main(String[] args) {
- int count = 0;
- while (true) {
- Enhancer enhancer = new Enhancer();
- enhancer.setSuperclass(MethodAreaOOM.class);
- enhancer.setUseCache(false);
- enhancer.setCallback(new MethodInterceptor() {
- public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
- return proxy.invoke(obj, args);
- }
- });
- enhancer.create();
- System.out.println(++count);
- }
- }
- }
-XX:MaxPermSize 为 10m. 其中的一次测试结果为, 当 count 的值累加到 800 时, 发生如下异常:
- Caused by: java.lang.OutOfMemoryError: PermGen space
- at java.lang.ClassLoader.defineClass1(Native Method)
- at java.lang.ClassLoader.defineClass(ClassLoader.java:792)
- ... 8 more
随着 - XX:MaxPermSize 参数值的增大, java 方法区中可以存储的类型数据也越多.
3. 栈溢出: java.lang.StackOverflowError : Thread Stack space
栈溢出了, JVM 依然是采用栈式的虚拟机, 这个和 C 和 Pascal 都是一样的. 函数的调用过程都体现在堆栈和退栈上了. 调用构造函数的 "层" 太多了, 以致于把栈区溢出了. 通常来讲, 一般栈区远远小于堆区的, 因为函数调用过程往往不会多于上千层, 而即便每个函数调用需要 1K 的空间(这个大约相当于在一个 C 函数内声明了 256 个 int 类型的变量), 那么栈区也不过是需要 1MB 的空间. 通常栈的大小是 1-2MB 的. 通俗一点讲就是单线程的程序需要的内存太大了. 通常递归也不要递归的层次过多, 很容易溢出.
解决方法: 1: 修改程序. 2: 通过 -Xss: 来设置每个线程的 Stack 大小即可.
在 Java 虚拟机规范中, 对这个区域规定了两种异常状况: StackOverflowError 和 OutOfMemoryError 异常.
(1)StackOverflowError 异常
每当 java 程序代码启动一个新线程时, Java 虚拟机都会为它分配一个 Java 栈. Java 栈以帧为单位保存线程的运行状态. 当线程调用 java 方法时, 虚拟机压入一个新的栈帧到该线程的 java 栈中. 只要这个方法还没有返回, 它就一直存在. 如果线程的方法嵌套调用层次太多(如递归调用), 随着 java 栈中帧的逐渐增多, 最终会由于该线程 java 栈中所有栈帧大小总和大于 - Xss 设置的值, 而产生 StackOverflowError 内存溢出异常. 例子如下:
- package com.demo.test;
- /**
- * VM Args: -Xss128k
- */
- public class JavaVMStackSOF {
- private int count = 0;
- public static void main(String[] args) {
- new JavaVMStackSOF().method();
- }
- public void method() {
- System.out.println(++count);
- method();
- }
- }
-Xss 为 128k. 其中的一次测试结果为, 当 count 的值累加到 2230 时, 发生如下异常:
- Exception in thread "main" java.lang.StackOverflowError
- at sun.nio.cs.UTF_8.updatePositions(UTF_8.java:77)
- at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(UTF_8.java:564)
- at sun.nio.cs.UTF_8$Encoder.encodeLoop(UTF_8.java:619)
- at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:561)
- at sun.nio.cs.StreamEncoder.implWrite(StreamEncoder.java:271)
- at sun.nio.cs.StreamEncoder.write(StreamEncoder.java:125)
- at java.io.OutputStreamWriter.write(OutputStreamWriter.java:207)
- at java.io.BufferedWriter.flushBuffer(BufferedWriter.java:129)
- at java.io.PrintStream.write(PrintStream.java:526)
- at java.io.PrintStream.print(PrintStream.java:597)
- at java.io.PrintStream.println(PrintStream.java:736)
- at com.demo.test.JavaVMStackSOF.method(JavaVMStackSOF.java:15)
随着 - Xss 参数值的增大, 可以嵌套的方法调用层次也相应增加. 综上所述, StackOverflowError 异常是由于方法调用的层次太深, 最终导致为某个线程分配的所有栈帧大小总和大于 - Xss 设置的值, 从而发生 StackOverflowError 异常.
(2)OutOfMemoryError 异常
java 程序代码启动一个新线程时, 没有足够的内存空间为该线程分配 java 栈(一个线程 java 栈的大小由 - Xss 参数确定),jvm 则抛出 OutOfMemoryError 异常. 例子如下:
- package com.demo.test;
- /**
- * VM Args: -Xss128k
- */
- public class JavaVMStackOOM {
- public static void main(String[] args) {
- int count = 0;
- while (true) {
- Thread thread = new Thread(new Runnable() {
- public void run() {
- while (true) {
- try {
- Thread.sleep(5000);
- } catch (Exception e) {
- }
- }
- }
- });
- thread.start();
- System.out.println(++count);
- }
- }
- }
-Xss 为 128k. 其中的一次测试结果为, 当 count 的值累加到 11958 时, 发生如下异常:
- Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread
- at java.lang.Thread.start0(Native Method)
- at java.lang.Thread.start(Thread.java:693)
- at com.demo.test.JavaVMStackOOM.main(JavaVMStackOOM.java:21)
随着 - Xss 参数值的增大, java 程序可以创建的总线程数越少.
4. 所以 Server 容器启动的时候我们经常关心和设置 JVM 的几个参数如下:
-Xms:java Heap 初始大小, 默认是物理内存的 1/64.
-Xmx:java Heap 最大值, 不可超过物理内存.
-Xmn:young generation 的 heap 大小, 一般设置为 Xmx 的 3,4 分之一 . 增大年轻代后, 将会减小年老代大小, 可以根据监控合理设置.
-Xss: 每个线程的 Stack 大小, 而最佳值应该是 128K, 默认值好像是 512k.
-XX:PermSize: 设定内存的永久保存区初始大小, 缺省值为 64M.
-XX:MaxPermSize: 设定内存的永久保存区最大大小, 缺省值为 64M.
-XX:SurvivorRatio:Eden 区与 Survivor 区的大小比值, 设置为 8, 则两个 Survivor 区与一个 Eden 区的比值为 2:8, 一个 Survivor 区占整个年轻代的 1/10.
-XX:+UseParallelGC:F 年轻代使用并发收集, 而年老代仍旧使用串行收集.
-XX:+UseParNewGC: 设置年轻代为并行收集, JDK5.0 以上, JVM 会根据系统配置自行设置, 所无需再设置此值.
-XX:ParallelGCThreads: 并行收集器的线程数, 值最好配置与处理器数目相等 同样适用于 CMS.
-XX:+UseParallelOldGC: 年老代垃圾收集方式为并行收集(Parallel Compacting).
-XX:MaxGCPauseMillis: 每次年轻代垃圾回收的最长时间(最大暂停时间), 如果无法满足此时间, JVM 会自动调整年轻代大小, 以满足此值.
-XX:+ScavengeBeforeFullGC:Full GC 前调用 YGC, 默认是 true.
实例如: JAVA_OPTS="-Xms4g -Xmx4g -Xmn1024m -XX:PermSize=320M -XX:MaxPermSize=320m -XX:SurvivorRatio=6″
第一种 OutOfMemoryError: PermGen space
发生这种问题的原意是程序中使用了大量的 jar 或 class, 使 java 虚拟机装载类的空间不够, 与 Permanent Generation space 有关. 解决这类问题有以下两种办法:
1, 增加 java 虚拟机中的 XX:PermSize 和 XX:MaxPermSize 参数的大小, 其中 XX:PermSize 是初始永久保存区域大 小, XX:MaxPermSize 是最大永久保存区域大小. 如针对 tomcat6.0, 在 catalina.sh 或 catalina.bat 文件中一系列环境变量名说明结束处(大约在 70 行左右) 增加一行: JAVA_OPTS="-XX:PermSize=64M -XX:MaxPermSize=128m" 如果是 Windows 服务器还可以在系统环境变量中设置. 感觉用 tomcat 发布 sprint+struts+hibernate 架构的程序时很容易发生这种内存溢出错误. 使用上述方法, 我成功解决了部署 SSH 项目的 tomcat 服务器经常宕机的问题.
2, 清理应用程序中 web-inf/lib 下的 jar, 如果 tomcat 部署了多个应用, 很多应用都使用了相同的 jar, 可以将共同的 jar 移到 tomcat 共同的 lib 下, 减少类的重复加载. 这种方法是网上部分人推荐的, 我没试过, 但感觉减少不了太大的空间, 最靠谱的还是第一种方法.
第二种 OutOfMemoryError: Java heap space
发生这种问题的原因是 java 虚拟机创建的对象太多, 在进行垃圾回收之间, 虚拟机分配的到堆内存空间已经用满了, 与 Heap space 有关. 解决这类问题有两种思路:
1, 检查程序, 看是否有死循环或不必要地重复创建大量对象. 找到原因后, 修改程序和算法. 我以前写一个使用 K-Means 文本聚类算法对几万条文本记录 (每条记录的特征向量大约 10 来个) 进行文本聚类时, 由于程序细节上有问题, 就导致了 Java heap space 的内存溢出问题, 后来通过修改程序得到了解决.
2, 增加 Java 虚拟机中 Xms(初始堆大小)和 Xmx(最大堆大小)参数的大小. 如: set JAVA_OPTS= -Xms256m -Xmx1024m
来源: http://www.bubuko.com/infodetail-3301257.html