作为有个java程序员,我想大家对下面出现的这几个场景并不陌生,倍感亲切,深恶痛绝,抓心挠肝,一定会回过头来问为什么为什么为什么会这样,嘿嘿,让我们看一下我们日常在开发过程中接触内存溢出的异常:

  1. Exception in thread "main" [Full GCjava.lang.OutOfMemoryError: Java heap space
  2. at java.util.Arrays.copyOf(Unknown Source)
  3. at java.util.Arrays.copyOf(Unknown Source)
  4. at java.util.ArrayList.grow(Unknown Source)
  5. at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
  6. at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
  7. at java.util.ArrayList.add(Unknown Source)
  8. at oom.HeapOOM.main(HeapOOM.java:21)
  1.  
  1. Exception in thread "main" java.lang.StackOverflowError
  2. at java.nio.CharBuffer.arrayOffset(Unknown Source)
  3. at sun.nio.cs.UTF_8.updatePositions(Unknown Source)
  4. at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(Unknown Source)
  5. at sun.nio.cs.UTF_8$Encoder.encodeLoop(Unknown Source)
  6. at java.nio.charset.CharsetEncoder.encode(Unknown Source)
  7. at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)
  8. at sun.nio.cs.StreamEncoder.write(Unknown Source)
  9. at java.io.OutputStreamWriter.write(Unknown Source)
  10. at java.io.BufferedWriter.flushBuffer(Unknown Source)
  11. at java.io.PrintStream.write(Unknown Source)
  12. at java.io.PrintStream.print(Unknown Source)
  13. at java.io.PrintStream.println(Unknown Source)
  1. java.lang.OutOfMemoryError: PermGen space Exception in thread "main" java.lang.OutOfMemoryError
  2. at sun.misc.Unsafe.allocateMemory(Native Method)
  3. at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)

是不是有大家很熟悉的,遇见这样的问题解决起来可能不简单,但是如果现在让大家写个程序,故意让程序出现下面的异常,估计能很快写出来的也不是很多,这就要求开发人员对于java内存区域以及jvm规范有比较深的了解。

既然抛出了异常,首先我们肯定这些都是内存异常,只是内存异常中的不同种类,我们就试着了解一下为什么会出现以上的异常,可以看出有两种异常状况::

OutOfMemoryError

StackOverflowError

其中OutOfMemoryError是在程序无法申请到足够的内存的时候抛出的异常,StackOverflowError是线程申请的栈深度大于虚拟机所允许的深度所抛出的异常。 可是从上面列出的异常内容也可以看出在OutOfMemoryError类型的一场中也存在这很多异常的可能。这是为什么?以为是在内存的不同结构中出现的错误,所以抛出的异常也就形形色色,说道这我们不得不介绍一下java的内存结构,请看下图(从网上摘的):

在运行时的内存区域有5个部分,Method Area(方法区),Java stack(java 虚拟机栈),Native MethodStack(本地方法栈),Heap(堆),Program Counter Regster(程序计数器)。从图中看出方法区和堆用黄色标记,和其他三个区域的不同点就是,方法区和堆是线程共享的,所有的运行在jvm上的程序都能访问这两个区域,堆,方法区和虚拟机的生命周期一样,随着虚拟机的启动而存在,而栈和程序计数器是依赖用户线程的启动和结束而建立和销毁。

Program Counter Regster(程序计数器):每一个用户线程对应一个程序计数器,用来指示当前线程所执行字节码的行号。由程序计数器给文字码解释器提供吓一条要执行的字节码的的位置。根据jvm规范,在这个区域中不会抛出OutOfMemoryError的内存异常。

Java stack(java 虚拟机栈):这个区域是最容易出现内存异常的区域,每一个线程对应生成一个线程栈,线程每执行一个方法的时候,都会创建一个栈帧,用来存放方法的局部变量表,操作树栈,动态连接,方法入口,这和C#是不一样的,在C#CLR中没有栈帧的概念,都是在线程栈中通过压栈和出栈的方式进行数据的保存。jvm规范对这个区域定义了两种内存异常,OutOfMemoryError,StackOverflowError。

Native MethodStack(本地方法栈):和虚拟机栈一样,不同的是处理的对象不一样,虚拟机栈处理java的字节码,而本地栈则是处理的Native方法。其他方面一致。

Heap(堆):前面说了堆是所有线程都能访问的,随着虚拟机的启动而存在,这块区域很大,因为所有的线程都在这个区域保存实例化的对象,因为每一个类型中,每个接口实现类需要的内存不一样,一个方法内的多个分支需要的内存也不尽相同,我们只有在运行的时候才能知道要创建多少对象,需要分配多大的地址空间。GC关注的正是这样的部分内容,所以很多时候也将堆称为GC堆。堆中肯定不会抛出StackOverflowError类型的异常,所以只有OutOfMemoryError相关类型的异常。

Method Area(方法区):用于存放已被虚拟机加载的类信息,常量,静态方法,即使编译后的代码。同样只能抛出OutOfMemoryError相关类型的异常。

介绍完jvm内存结构中的常见区域,下面该是和我们主题呼应的时候了,在什么情况下,在那个区域,如何才能复现开始提到的异常信息?从第一个开始,异常信息的内容为:

  1. Exception in thread "main" [Full GCjava.lang.OutOfMemoryError: Java heap space
  2. at java.util.Arrays.copyOf(Unknown Source)
  3. at java.util.Arrays.copyOf(Unknown Source)
  4. at java.util.ArrayList.grow(Unknown Source)
  5. at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
  6. at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
  7. at java.util.ArrayList.add(Unknown Source)
  8. at oom.HeapOOM.main(HeapOOM.java:21)

可想而知是在堆中出现的问题,如何重现,由于是在堆中出现这个异常,那么就要处理好,不能被垃圾回收器给回收了,设置一下jvm中堆的最大值(这样才能够更快的出现错误),设置jvm值的方法是通过-Xms(堆的最小值),-Xmx(堆的最大值)。下面动手试一下:

  1. package oom;
  2.  
  3. import java.util.ArrayList;
  4. import java.util.List;
  5.  
  6. import testbean.UserBean;
  7.  
  8. /***
  9. *
  10. * @author Think
  11. *
  12. */
  13. public class HeapOOM {
  14. static class OOMObject {
  15. }
  16.  
  17. public static void main(String[] args) {
  18. List<UserBean> users = new ArrayList<UserBean>();
  19. while (true) {
  20. users.add(new UserBean());
  21. }
  22. }
  23. }

UserBean对象定义如下:

  1. package testbean;
  2.  
  3. public class UserBean {
  4. String name;
  5. int age;
  6.  
  7. public String getName() {
  8. return name;
  9. }
  10.  
  11. public void setName(String name) {
  12. this.name = name;
  13. }
  14.  
  15. public int getAge() {
  16. return age;
  17. }
  18.  
  19. public void setAge(int age) {
  20. this.age = age;
  21. }
  22.  
  23. public UserBean() {
  24. super();
  25. }
  26.  
  27. }

然后在运行的时候设置jvm参数,如下:

运行一下看看结果:

  1. Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  2. at java.util.Arrays.copyOf(Unknown Source)
  3. at java.util.Arrays.copyOf(Unknown Source)
  4. at java.util.ArrayList.grow(Unknown Source)
  5. at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)
  6. at java.util.ArrayList.ensureCapacityInternal(Unknown Source)
  7. at java.util.ArrayList.add(Unknown Source)
  8. at oom.HeapOOM.main(HeapOOM.java:21)

成功在java虚拟机堆中溢出。

下面看第二个关于栈的异常,内容如下:

  1. Exception in thread "main" java.lang.StackOverflowError
  2. at java.nio.CharBuffer.arrayOffset(Unknown Source)
  3. at sun.nio.cs.UTF_8.updatePositions(Unknown Source)
  4. at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(Unknown Source)
  5. at sun.nio.cs.UTF_8$Encoder.encodeLoop(Unknown Source)
  6. at java.nio.charset.CharsetEncoder.encode(Unknown Source)
  7. at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)
  8. at sun.nio.cs.StreamEncoder.write(Unknown Source)
  9. at java.io.OutputStreamWriter.write(Unknown Source)
  10. at java.io.BufferedWriter.flushBuffer(Unknown Source)
  11. at java.io.PrintStream.write(Unknown Source)
  12. at java.io.PrintStream.print(Unknown Source)
  13. at java.io.PrintStream.println(Unknown Source)

因为是与栈相关的话,那么我们在重现异常的时候就要相应的将栈内存容量设置的小一些,设置栈大小的方法是设置-Xss参数,看如下实现:

  1. package oom;
  2.  
  3. import testbean.Recursion;
  4.  
  5. /***
  6. *
  7. * @author Think
  8. *
  9. */
  10. public class VMStackOOM {
  11.  
  12. public static void main(String[] args) {
  13. Recursion recursion = new Recursion();
  14. try {
  15. recursion.recursionself();
  16. } catch (Throwable e) {
  17. System.out.println("current value :" + recursion.currentValue);
  18. throw e;
  19. }
  20. }
  21.  
  22. }

Recursion的定义如下:

  1. package testbean;
  2.  
  3. public class Recursion {
  4. public int currentValue = 0;
  5.  
  6. public void recursionself() {
  7. currentValue += 1;
  8. recursionself();
  9. }
  10. }

运行时jvm参数的设置如下:

运行结果如下:

  1. current value :999
  2. Exception in thread "main" java.lang.StackOverflowError
  3. at testbean.Recursion.recursionself(Recursion.java:7)
  4. at testbean.Recursion.recursionself(Recursion.java:8)
  5. at testbean.Recursion.recursionself(Recursion.java:8)
  6. at testbean.Recursion.recursionself(Recursion.java:8)
  7. at testbean.Recursion.recursionself(Recursion.java:8)
  8. at testbean.Recursion.recursionself(Recursion.java:8)
  9. 省略下面的异常信息

第三个异常是关于perm的异常内容,我们需要的是设置方法区的大小,实现方式是通过设置-XX:PermSize和-XX:MaxPermSize参数,内容如下:

  1. java.lang.OutOfMemoryError: PermGen space

如果程序加载的类过多,例如tomcatweb容器,就会出现PermGen space异常,如果我将HeapOOM类的运行时的XX:PermSize设置为2M,如下:

那么程序就不会执行成功,执行的时候出现如下异常:

  1. Error occurred during initialization of VM
  2. java.lang.OutOfMemoryError: PermGen space
  3. at sun.misc.Launcher$ExtClassLoader.getExtClassLoader(Unknown Source)
  4. at sun.misc.Launcher.<init>(Unknown Source)
  5. at sun.misc.Launcher.<clinit>(Unknown Source)
  6. at java.lang.ClassLoader.initSystemClassLoader(Unknown Source)
  7. at java.lang.ClassLoader.getSystemClassLoader(Unknown Source)

第四个异常估计遇到的人就不多了,是DirectMemory内存相关的,内容如下:

  1. Exception in thread "main" java.lang.OutOfMemoryError
  2. at sun.misc.Unsafe.allocateMemory(Native Method)
  3. at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)

DirectMemoruSize可以通过设置 -XX:MaxDirectMemorySize参数指定容量大小,如果不指定的话,那么就跟堆的最大值一致,下面是代码实现:

  1. package oom;
  2.  
  3. import java.lang.reflect.Field;
  4.  
  5. import sun.misc.Unsafe;
  6.  
  7. /***
  8. *
  9. * @author Think
  10. *
  11. */
  12. public class DirectMemoryOOM {
  13.  
  14. private static final int _1MB = 1024 * 1024;
  15.  
  16. public static void main(String[] args) throws IllegalArgumentException,
  17. IllegalAccessException {
  18. Field unsafeField = Unsafe.class.getDeclaredFields()[0];
  19. unsafeField.setAccessible(true);
  20. Unsafe unsafe = (Unsafe) unsafeField.get(null);
  21. while (true) {
  22. unsafe.allocateMemory(_1MB);
  23. }
  24.  
  25. }
  26. }

运行时设置的jvm参数如下:

很容易就复线了异常信息:

  1. Exception in thread "main" java.lang.OutOfMemoryError
  2. at sun.misc.Unsafe.allocateMemory(Native Method)
  3. at oom.DirectMemoryOOM.main(DirectMemoryOOM.java:23)

垃圾回收:

垃圾回收算法的具体实现就是SUN提供的垃圾回收器。新生代是Serial, ParNew,Parallel Scanage;老生代包括Serial Old, Parallel Old, CMS

  • 标记-清除(Mark-Sweep)
    此算法执行分两阶段。第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除。此算法需要暂停整个应用,同时,会产生内存碎片。
  • 复制(Copying)

    此算法把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾回收时,遍历当前使用区域,把正在使用中的对象复制到另外一个区域中。次算法每次只处理正在使用中的对象,因此复制成本比较小,同时复制过去以后还能进行相应的内存整理,不过出现"碎片"问题。当然,此算法的缺点也是很明显的,就是需要两倍内存空间。
  • 标记-整理(Mark-Compact)

    此算法结合了"标记-清除"和"复制"两个算法的优点。也是分两阶段,第一阶段从根节点开始标记所有被引用对象,第二阶段遍历整个堆,把清除未标记对象并且把存活对象"压缩"到堆的其中一块,按顺序排放。此算法避免了"标记-清除"的碎片问题,同时也避免了"复制"算法的空间问题。
  • 增量收集(Incremental Collecting)

    实施垃圾回收算法,即:在应用进行的同时进行垃圾回收。不知道什么原因JDK5.0中的收集器没有使用这种算法的。
  • 分代(Generational Collecting)

    基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的。

引用计数还有一个致命的缺陷,当程中出现序循环引用时,引用计数算法无法检测出来,被循环引用的内存对象就成了无法回收的内存。从而引起内存泄露。

从内存溢出看Java 环境中的内存结构(转)的更多相关文章

  1. 从内存溢出看Java 环境中的内存结构

    作为有个java程序员,我想大家对下面出现的这几个场景并不陌生,倍感亲切,深恶痛绝,抓心挠肝,一定会回过头来问为什么为什么为什么会这样,嘿嘿,让我们看一下我们日常在开发过程中接触内存溢出的异常: Ex ...

  2. 什么是内存溢出以及java中内存泄漏5种情况的总结

    内存泄漏定义(memory leak):一个不再被程序使用的对象或变量还在内存中占有存储空间. 一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出.内存溢出 out of memory ...

  3. 通过 thread dump 分析找到高CPU耗用与内存溢出的Java代码

    http://heylinux.com/archives/1085.html通过 thread dump 分析找到高CPU耗用与内存溢出的Java代码 首先,要感谢我的好朋友 钊花 的经验分享. 相信 ...

  4. Eclipse部署多个Web项目内存溢出,java.lang.OutOfMemoryError: PermGen space

    Eclipse部署多个Web项目内存溢出,java.lang.OutOfMemoryError: PermGen space >>>>>>>>>& ...

  5. JVM内存溢出分析java.lang.OutOfMemoryError: Java heap space

    JVM内存溢出查询java.lang.OutOfMemoryError: Java heap space查出具体原因分为几个预备步骤 1.在运行java程序是必须设置jvm -XX:+HeapDump ...

  6. Java 内存溢出(java.lang.OutOfMemoryError)的常见情况和处理方式

    导致OutOfMemoryError异常的常见原因有以下几种: 内存中加载的数据量过于庞大,如一次从数据库取出过多数据: 集合类中有对对象的引用,使用完后未清空,使得JVM不能回收: 代码中存在死循环 ...

  7. 细分java环境中的JDK、JVM、JRE

    细分java环境中的JDK.JVM.JRE 近来小看了下Android,扑面而来一堆概念JDK.JVM.JRE.SDK.NDK.ADT.缕了一下,其中JDK.JVM.JRE是java环境的东西,而SD ...

  8. java环境中基于jvm的两大语言:scala,groovy

    一.java环境中基于jvm的两大语言:scala,groovy 可以在java项目里混编这两种语言: scala:静态语言,多范式语言,糅合了面向对象.面向过程:可以与java和net互操作:融汇了 ...

  9. 记一次线上环境的内存溢出(java.lang.OutOfMemoryError)

    事故背景 今天客户说风控项目有个别用户查询不到数据不是报错就是一直卡在那里,我就去那个接口看了下. 一看项目日志今天的都几个g了,平常也就几百兆吧,很明显出了问题. 请求接口后使用命令tail -f ...

随机推荐

  1. 使用solrj查询数据(java代码)

    实体类Student,添加Field注解 package com.cs.solr.entity; import org.apache.solr.client.solrj.beans.Field; pu ...

  2. mysql5.6 online ddl—索引

    尝试对mysiam表(1500万)删除索引失败 #uk表字段类型比较简单,都是int/tinyint/timestamp类型. CREATE TABLE `uk` (  `id` int(11) NO ...

  3. POJ 3347 Kadj Squares (线段覆盖)

    题目大意:给你几个正方形的边长,正方一个顶点在x轴上然后边与x轴的夹角为45度,每个正方形都是紧贴的,问从上面看能看的正方形的编号 题目思路:线段覆盖,边长乘上2防止产生小数,求出每个正方形与x轴平行 ...

  4. log4net 日志文件占用,不能及时释放

    在appender 下面加 <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />

  5. iOS不可变字符串的所有操作

    可以直接复制代码即可运行看看结果,方便理解 //NSString //OC字符串不能用printf输出 //但是遗憾的是OC没有一个从终端读取数据的方式,需要使用scanf读取C字符串然后转换成OC的 ...

  6. unknow type name 'off64_t'

    或者  错误"error: 'off64_t' does not name a type" MinGW的bug,使用-std=c++11, 有可能出现, 修改{MinGW dir} ...

  7. css在网页中划线

    在行边距上的线可以通过 1 div,表格等的border属性实现 2 <hr/>实现 3 通过背景图片实现 4 页面内写入横线图片 通过相对定位实现 5 通过css伪类实现 <sty ...

  8. Shiro 的FilterChain

    /** * Shiro的FilterChain * @see ===================================================================== ...

  9. echo json数据给ajax后, 需要加上exit,防止往下执行,带上其他数据,到时ajax失败

    01返回json数据给ajax后需要加上exit.返回json数据前不能有其他输出 function apply(){ if(IS_POST){$info['status'] = 1; echo js ...

  10. Http Post与Get等

    Http定义了与服务器交互的不同方法,最基本的方法有4种,分别是GET,POST,PUT,DELETE.URL全称是资源描述符,我们可以这样认为:一个URL地址,它用于描述一个网络上的资源,而HTTP ...