当前位置: 首页 > 编程日记 > 正文

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

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

复制代码
Exception in thread "main" [Full GCjava.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Unknown Source)at java.util.Arrays.copyOf(Unknown Source)at java.util.ArrayList.grow(Unknown Source)at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)at java.util.ArrayList.ensureCapacityInternal(Unknown Source)at java.util.ArrayList.add(Unknown Source)at oom.HeapOOM.main(HeapOOM.java:21)  
复制代码
复制代码
Exception in thread "main" java.lang.StackOverflowErrorat java.nio.CharBuffer.arrayOffset(Unknown Source)at sun.nio.cs.UTF_8.updatePositions(Unknown Source)at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(Unknown Source)at sun.nio.cs.UTF_8$Encoder.encodeLoop(Unknown Source)at java.nio.charset.CharsetEncoder.encode(Unknown Source)at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)at sun.nio.cs.StreamEncoder.write(Unknown Source)at java.io.OutputStreamWriter.write(Unknown Source)at java.io.BufferedWriter.flushBuffer(Unknown Source)at java.io.PrintStream.write(Unknown Source)at java.io.PrintStream.print(Unknown Source)at java.io.PrintStream.println(Unknown Source)
复制代码
java.lang.OutOfMemoryError: PermGen space 
Exception in thread "main" java.lang.OutOfMemoryErrorat sun.misc.Unsafe.allocateMemory(Native Method)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内存结构中的常见区域,下面该是和我们主题呼应的时候了,在什么情况下,在那个区域,如何才能复现开始提到的异常信息?从第一个开始,异常信息的内容为:

复制代码
Exception in thread "main" [Full GCjava.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Unknown Source)at java.util.Arrays.copyOf(Unknown Source)at java.util.ArrayList.grow(Unknown Source)at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)at java.util.ArrayList.ensureCapacityInternal(Unknown Source)at java.util.ArrayList.add(Unknown Source)at oom.HeapOOM.main(HeapOOM.java:21)
复制代码

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

复制代码
package oom;import java.util.ArrayList;
import java.util.List;import testbean.UserBean;/*** * * @author Think* */
public class HeapOOM {static class OOMObject {}public static void main(String[] args) {List<UserBean> users = new ArrayList<UserBean>();while (true) {users.add(new UserBean());}}
}
复制代码

UserBean对象定义如下:

复制代码
package testbean;public class UserBean {String name;int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public UserBean() {super();}}
复制代码

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

运行一下看看结果:

复制代码
Exception in thread "main" java.lang.OutOfMemoryError: Java heap spaceat java.util.Arrays.copyOf(Unknown Source)at java.util.Arrays.copyOf(Unknown Source)at java.util.ArrayList.grow(Unknown Source)at java.util.ArrayList.ensureExplicitCapacity(Unknown Source)at java.util.ArrayList.ensureCapacityInternal(Unknown Source)at java.util.ArrayList.add(Unknown Source)at oom.HeapOOM.main(HeapOOM.java:21)
复制代码

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

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

复制代码
Exception in thread "main" java.lang.StackOverflowErrorat java.nio.CharBuffer.arrayOffset(Unknown Source)at sun.nio.cs.UTF_8.updatePositions(Unknown Source)at sun.nio.cs.UTF_8$Encoder.encodeArrayLoop(Unknown Source)at sun.nio.cs.UTF_8$Encoder.encodeLoop(Unknown Source)at java.nio.charset.CharsetEncoder.encode(Unknown Source)at sun.nio.cs.StreamEncoder.implWrite(Unknown Source)at sun.nio.cs.StreamEncoder.write(Unknown Source)at java.io.OutputStreamWriter.write(Unknown Source)at java.io.BufferedWriter.flushBuffer(Unknown Source)at java.io.PrintStream.write(Unknown Source)at java.io.PrintStream.print(Unknown Source)at java.io.PrintStream.println(Unknown Source)
复制代码

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

复制代码
package oom;import testbean.Recursion;/*** * * @author Think* */
public class VMStackOOM { public static void main(String[] args) {Recursion recursion = new Recursion();try {recursion.recursionself();} catch (Throwable e) {System.out.println("current value :" + recursion.currentValue);throw e;}}}
复制代码

Recursion的定义如下:

复制代码
package testbean;public class Recursion {public int currentValue = 0;public void recursionself() {currentValue += 1;recursionself();}
}
复制代码

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

运行结果如下:

复制代码
current value :999
Exception in thread "main" java.lang.StackOverflowErrorat testbean.Recursion.recursionself(Recursion.java:7)at testbean.Recursion.recursionself(Recursion.java:8)at testbean.Recursion.recursionself(Recursion.java:8)at testbean.Recursion.recursionself(Recursion.java:8)at testbean.Recursion.recursionself(Recursion.java:8)at testbean.Recursion.recursionself(Recursion.java:8)
省略下面的异常信息
复制代码

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

java.lang.OutOfMemoryError: PermGen space

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

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

复制代码
Error occurred during initialization of VM
java.lang.OutOfMemoryError: PermGen spaceat sun.misc.Launcher$ExtClassLoader.getExtClassLoader(Unknown Source)at sun.misc.Launcher.<init>(Unknown Source)at sun.misc.Launcher.<clinit>(Unknown Source)at java.lang.ClassLoader.initSystemClassLoader(Unknown Source)at java.lang.ClassLoader.getSystemClassLoader(Unknown Source)
复制代码

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

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

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

复制代码
package oom;import java.lang.reflect.Field;import sun.misc.Unsafe;/*** * * @author Think* */
public class DirectMemoryOOM {private static final int _1MB = 1024 * 1024;public static void main(String[] args) throws IllegalArgumentException,IllegalAccessException {Field unsafeField = Unsafe.class.getDeclaredFields()[0];unsafeField.setAccessible(true);Unsafe unsafe = (Unsafe) unsafeField.get(null);while (true) {unsafe.allocateMemory(_1MB);}}
}
复制代码

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

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

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

相关文章:

【ACM】杭电OJ 1003。

运行环境VS2017 题目链接&#xff1a;杭电OJ 1003 主要思想是&#xff1a; 用d[i]来存放前i项中最大的和&#xff0c;得到end&#xff0c;然后再倒推&#xff0c;得起始的位置begin 然而在程序42行的疑问&#xff0c;大家可以讨论一下吗&#xff1f;&#xff1f;&#xff…

js 实现精确加减乘除运算之BigDecimal.js

在前端实际开发中&#xff0c;进行前端计算会出现丢失精度的问题&#xff0c;这里我们项目中运用了BigDecimal.js。 js计算丢失精度原因 计算机的二进制实现和位数限制有些数无法有限表示。就像一些无理数不能有限表示&#xff0c;如 圆周率 3.1415926...&#xff0c;1.3333...…

【ACM】杭电OJ 2012。

题目链接&#xff1a;杭电OJ 2012 思路很简单&#xff0c;但是有一种高效算法显示编译错误&#xff0c;不知道为什么 运行环境&#xff1a;VS2017 AC代码&#xff1a; #include <stdio.h> #include <math.h>int main() {int x, y, i,j,num,count,t;while (scan…

ubuntu配置jdk环境

1.下载jdk&#xff0c;解压 2.配置 ~/.bashrc&#xff0c;添加jdk路径 3.重启系统 4.当看到java -version有输出时&#xff0c;配置系统的默认java变量(默认jdk) sudo update-alternatives --install /usr/bin/java java /home/liweigao/software/jdk1.7.0_17/bin/java 300 …

解决 The mysql extension is deprecated and will be r

为什么80%的码农都做不了架构师&#xff1f;>>> 解决 The mysql extension is deprecated and will be removed in the future技术 maybe yes 发表于2015-07-27 15:19 原文链接 : http://blog.lmlphp.com/archives/132/Tutorial_of_solve_mysql_extension_is_depre…

Android控件系统(三)——Window与WindowMananger

Android版本&#xff1a;7.0(API27) [TOC] 澄清几个概念 窗口&#xff08;不是指的Window类&#xff09;&#xff1a;这是一个纯语义的说法&#xff0c;即程序员所看到的屏幕上的某个独立的界面&#xff0c;比如一个带有Title Bar的Activity界面、一个对话框、一个Menu菜单等&a…

Restore Volume 操作 - 每天5分钟玩转 OpenStack(60)

前面我们 backup 了 voluem&#xff0c;今天我们将讨论如何 restore volume。 restore 的过程其实很简单&#xff0c;两步走&#xff1a; 在存储节点上创建一个空白 volume。 将 backup 的数据 copy 到空白 voluem 上。 下面我们来看 restore 操作的详细流程&#xff1a; …

gdb 查找动态库方法

当GDB无法显示so动态库的信息或者显示信息有误时&#xff0c;通常是由于库搜索路径错误导致的&#xff0c;可使用set sysroot、set solib-absolute-prefix、set solib-search-path来指定库搜索路径。 1. set sysroot 与 set solib-absolute-prefix 是同一条命令&#xff0c;实…

【ACM】杭电OJ 1004

题目链接&#xff1a;杭电OJ 1004 运行环境&#xff1a;Dev-C 5.11 思路&#xff1a; 先把先把num数组全部赋值为1&#xff1b;第一个颜色单独输入&#xff0c;从第二个开始&#xff0c;需要与前面的进行比较&#xff0c;如果前面有相同的颜色&#xff0c;则在目前的num[i]上…

zabbix 监控mysql(实例)

修改zabbix_agentd.conf UnsafeUserParameters1 UserParametermysql.version,mysql -V UserParametermysql.status[*],/usr/local/zabbix/share/zabbix/alertscripts/chk_mysql.sh $1 UserParametermysql.ping,mysqladmin ping | grep -c alive 设置mysql的链接 ln -s /data/my…

linux查看系统版本信息命令

几种查看Linux版本信息的方法&#xff1a; 1. uname -a 2. cat /proc/version 3. cat /etc/issue 4. lsb_release -a 详解 lsb_release -a 登录到服务器执行 lsb_release -a &#xff0c;即可列出所有版本信息&#xff0c;例如&#xff1a; 1. [root3.5.5Biz-46 ~]# lsb_releas…

【ACM】杭电OJ 1005

题目链接&#xff1a;杭电OJ 1005 超时代码如下&#xff08;而且开辟的数组空间大小不够&#xff09;: #include <stdio.h> int m[100000]; int f(int n,int a,int b) {m[1] 1;m[2] 1;for (int i 3; i < n; i){m[i] (a * m[i - 1] b * m[i - 2]) % 7;}return …

PostgreSQL:Java使用CopyManager实现客户端文件COPY导入

在MySQL中&#xff0c;可以使用LOAD DATA INFILE和LOAD DATA LOCAL INFILE两种方式导入文本文件中的数据到数据库表中&#xff0c;速度非常快。其中LOAD DATA INFILE使用的文件要位于MySQL所在服务器上&#xff0c;LOAD DATA LOCAL INFILE则使用的是客户端的文件。 LOAD DATA I…

c, c++函数名编译符号修饰符说明

C 编译器的函数名修饰规则 函数名字修饰&#xff08;Decorated Name&#xff09;方式 函数的名字修饰&#xff08;Decorated Name&#xff09;就是编译器在编译期间创建的一个字符串。用来指明函数的定义或原型。 LINK程序或其它工具有时须要指定函数的名字修饰来定位函数的正确…

VS Code - Debugger for Chrome调试JavaScript的两种方式

VS Code - Debugger for Chrome调试JavaScript的两种方式 最近由于出差的缘故&#xff0c;博客写的不是很多&#xff0c;一直想写一篇VS Code - Debugger for Chrome相关的文章&#xff0c;没想到一直拖到了今天。VS Code 开源以后确实在社区得到了很多人的支持&#xff0c;当中…

【ACM】杭电OJ 2018

题目链接&#xff1a;杭电OJ 2018 从n>4开始&#xff0c;每一年的牛的数量前一年的牛的数量三年前的牛的数量 问&#xff1a;为什么是三年前&#xff1f; 答&#xff1a;假设三年前有一头小牛出生&#xff0c;出生的那一年即为第一年&#xff0c;到了第四年&#xff0c;即…

python 帮助文档、自我解释

现在让我们以交互方式使用 Python 来开始研究。当我们从命令行启动 Python 时&#xff0c;就进入了 Python shell&#xff0c;在这里可以输入 Python 代码&#xff0c;而且立刻会从 Python 解释器获得响应。 清单 1. 以交互方式启动 Python 解释器 Python 2.7.15rc1 (default, …

RCF远程调用框架

介绍 RCF&#xff08;远程调用框架&#xff09;是一个C IPC框架&#xff0c;提供了一种在C 程序中实现进程间通信的简单而一致的方法。它基于强类型的客户端/服务器接口的概念&#xff0c;这是基于IDL的中间件&#xff08;如CORBA和DCOM&#xff09;的用户熟悉的概念。然而&am…

Python变量类型(l整型,长整形,浮点型,复数,列表,元组,字典)学习

#codingutf-8 __author__ Administrator#Python变量类型#Python数字&#xff0c;python支持四种不同的数据类型 int整型 long长整型 float浮点型 complex复数 var1 10; #表示整型 var2 678L #表示长整型 var3 12.34;#表示浮点型 var4 123j #复数 var5 12345j #复数print(…

【ACM】杭电OJ 2020(排序)

题目链接&#xff1a;杭电OJ 2020 排序可以有冒泡排序&#xff0c;选择排序&#xff0c;或者直接调用函数。 下面是选择排序&#xff1a; #include <stdio.h> #include <math.h> int main () {int n,i,j,t,a[100],temp;while(scanf("%d",&n)!EOF)…

使用Novell.Directory.Ldap.NETStandard在.NET Core中验证AD域账号

Novell.Directory.Ldap.NETStandard是一个在.NET Core中&#xff0c;既支持Windows平台&#xff0c;又支持Linux平台&#xff0c;进行Windows AD域操作的Nuget包。 首先我们新建一个.NET Core控制台项目&#xff1a;NetCoreLdap&#xff0c;并下载如下Nuget包&#xff1a; Nove…

android AIDL IPC深入分析

深入分析AIDL原理 博客分类&#xff1a; Android 在上一篇文章&#xff08;Service使用方式&#xff09;中&#xff0c;介绍了Android进程间通信&#xff08;IPC&#xff09;的使用&#xff0c;并给出了一个示例。但并没有深入分析aidl是怎样可以做到进程间通信的&#xff0c;…

xml常用操作(js、sql、vb)

我们经常会用到xml操作&#xff0c;如下介绍了js、sql、vb等对xml的操作。 JS创建xml对象 //创建对象 function getDataXML() {var objTds $("TEXTAREA");var count objTds.length;var jsonData;var xmlDoc new ActiveXObject("Microsoft.XMLDOM");//创…

【ACM】杭电OJ 2019

可以用链表写&#xff0c;也可以用顺序表写&#xff1a; 下面是链表的AC代码&#xff1a; #include <stdio.h> #include <stdlib.h> typedef struct node {int info;struct node* next; }N; N* init() {return NULL; } N* creat(N* head,int n) {int i,x;N *p, *…

Nancy in .Net Core学习笔记 - 初识Nancy

原文:Nancy in .Net Core学习笔记 - 初识Nancy前言 去年11月份参加了青岛MVP线下活动&#xff0c;会上老MVP衣明志介绍了Nancy, 一直没有系统的学习一下&#xff0c;最近正好有空&#xff0c;就结合.NET Core学习总结了一下。 注&#xff1a; 本文中大部分内容都是对官网文档的…

rpath和runpath的区别

考虑翻译Qt官方blog中的RPATH and RUNPATH这篇文章&#xff0c;在继续之前&#xff0c;我需要先验证自己的理解是正确的&#xff0c;至少能自圆其说&#xff0c;能说服自己。 用例子说话 二进制 对应源码 有一个程序 a.out main.c 需要加载插件A libA.so liba…

scanf(%s,a)和gets(a)的差别

gets()和scanf()的区别在于输入的字符串是否中间有空格&#xff1a;对于前者&#xff0c;只有遇到"\n"时才停止输入&#xff0c;而对于后者&#xff0c;出现"\n"或空格都停止输入。

阿里注册中心nacos使用整合Dubbo-原创

阿里注册中心nacos是今年开源的框架&#xff0c;一开始以为就是个zk。后面看了图才明白他对标的竟然是consul\eureka&#xff0c;最重要是完美支持dubbo。我想今年开源它也是别有用意 。&#xff08;目前nacos0.7版本&#xff09; Dubbo 融合 Nacos 成为注册中心 Nacos 作为 Du…

UBUNTU adb连接android设备

1sudo vi /etc/udev/rules.d/70-android.rules 2最新修改方法&#xff0c;不用去看设备的ID&#xff0c;直接在rules.d下增加一个文件51-android.rules&#xff0c;内容为&#xff1a; SUBSYSTEM"usb" ENV{DEVTYPE}"usb_device", MODE"0666" …

jetty9请求form表单太小限制

报错&#xff1a;java.lang.IllegalStateException: Form too large: 201975 > 200000解决&#xff1a;vi jetty.xml<Configure id"Server" class"org.eclipse.jetty.server.Server">在Server这行下面增加以下代码<!-- guowang add --><…