精华内容
下载资源
问答
  • 当时在做分片,当分片容量大于512M内存溢出,抛出异常:java.lang.OutOfMemoryError: Java heap space。 分析一下:当时虚拟机堆内存正好设置为512M,当申请堆空间大于该值出现如上异常。 今日重写代码...

    回忆一下:1年前在做断点续传因粗心编码导致的内存溢出问题。当时在做分片时,当分片容量大于512M时,内存溢出,抛出异常:java.lang.OutOfMemoryError: Java heap space。

    分析一下:当时虚拟机堆内存正好设置为512M,当申请堆空间大于该值时出现如上异常。

    今日重写代码测试,讲解出错的原因以及解决方案。

    public static void testOutMemory() throws IOException {
    		//绝对路径
    		String inPath = "D:/TestFile/in/bigFileTest.zip";
    		String outPath = "D:/TestFile/out/bigFileTest.zip";
    		
    		File inFile = new File(inPath);
    		File outFile = new File(outPath);
    		if (!inFile.exists()) {
    			//TODO 文件不存在
    		}
    		long fileLength = inFile.length();//文件大小,我的测试值为: 1820595407
    		
    		ByteArrayOutputStream bos = new ByteArrayOutputStream((int) fileLength);//这一行会抛出异常
    		BufferedInputStream in = new BufferedInputStream(new FileInputStream(inFile));
    		FileOutputStream fileOutputStream = new FileOutputStream(outFile);
    			
    		int buf_size = 1024;
    		byte[] buffer = new byte[buf_size];
    		int len = 0;
    		while (-1 != (len = in.read(buffer, 0, buf_size))) {
    			bos.write(buffer, 0, len);
    		}
    			
    		byte[] data = bos.toByteArray();
    		fileOutputStream.write(data);
    		fileOutputStream.flush();
    		fileOutputStream.close();
            bos.close();
    		System.out.println("测试完成");
    	}

    执行方法,在ByteArrayOutputStream bos = new ByteArrayOutputStream((int) fileLength);这一行会抛出异常,查看ByteArrayOutputStream构造方法,其中有一句为buf = new byte[size], 也正是在这一句抛出的异常,问题已经找到。

    难道需要通过增加堆内存的方式来解决这个问题?当然不是。虽然可以暂时解决问题,但依然会埋下一枚地雷,当下一文件更大时,这个问题将会重新出现。

    废话不多说,在代码中用一次缓存较大数据本身就不可取,上面的代码实现的文件传输本就不应该先把文件存放到内存,再将内存中的数据传输到其他位置,很明显这是一次很垃圾的编码。于是,优化如下:

    public void testOutMemory() throws IOException {
    		// 绝对路径
    		String inPath = "D:/TestFile/in/bigFileTest.zip";
    		String outPath = "D:/TestFile/out/bigFileTest.zip";
    
    		File inFile = new File(inPath);
    		File outFile = new File(outPath);
    		if (!inFile.exists()) {
    			// TODO 文件不存在
    		}
    		BufferedInputStream in = new BufferedInputStream(new FileInputStream(inFile));
    		FileOutputStream fileOutputStream = new FileOutputStream(outFile);
    
    		int buf_size = 1024;
    		byte[] buffer = new byte[buf_size];
    		int len = 0;
    		while (-1 != (len = in.read(buffer, 0, buf_size))) {
    			fileOutputStream.write(buffer, 0, len);//一次仅传输1K,不会溢出
    		}
    
    		fileOutputStream.flush();
    		fileOutputStream.close();
    		System.out.println("测试完成");
    	}

    (PS:我的堆内存为2G,文件大小约1.7G,为什么会内存溢出了,在这篇文章中会进行详细探索)。

     

    回复里面好像不参添加图片,为了说明这段代码不会出现OOM,刚才亲测文件下载,大约5个G,@我问佛陀 请见:

    展开全文
  • 下面小编就为大家带来一篇完美解决java读取大文件内存溢出的问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • java写文件的时候内存溢出

    千次阅读 2013-07-24 10:30:54
    解决步骤: 1、设置tomcat的jvm参数: ...rem ----- Execute The Requested Command ---------------------------------------set JAVA_OPTS=-Xms1024m -Xmx1024m//添加这一行,内存使用 1G   2、在写文件...

    解决步骤:

    1、设置tomcat的jvm参数:

    在tomcat 的安装目录找到catalina.bat,在文件中的

    rem ----- Execute The Requested Command ---------------------------------------
    set JAVA_OPTS=-Xms1024m -Xmx1024m//添加这一行,内存使用 1G

     

    2、在写文件的时候,我之前的写法是用一个Stringbuffer 把所有内容appen进去,最后用FileWriter 的write方法写,但是当StringBuffer内容太多的时候(比如200M),这个时候就出问题了,内存溢出,

    所以还是建议用BufferedWriter 逐行写入:

    FileWriter writer = null;
    		BufferedWriter bufferedWriter=null;
    		try {
    			writer = new FileWriter(csvFilePath, true);
    			bufferedWriter=new BufferedWriter(writer);
    			HashMap<String, Integer> dataMap = csv.getDataMap();
    			Iterator<String> iterator = dataMap.keySet().iterator();
    			String line = null;
    			while (iterator.hasNext()) {
    				line = iterator.next();
    				bufferedWriter.write(line);
    				bufferedWriter.newLine();
    				bufferedWriter.flush();//注意添加这行,否则还是往内存里写
    			}
    
    
    
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    				try {
    					writer.close();
    					bufferedWriter.close();
    				} catch (IOException e) {
    					e.printStackTrace();
    				}
    		}

     

     

    展开全文
  • 深入理解Java虚拟机-Java内存区域与内存溢出异常

    万次阅读 多人点赞 2020-01-03 21:42:24
    文章目录概述运行数据区域程序计数器(线程私有)Java虚拟机栈(线程私有)局部变量表操作数栈动态链接方法返回地址小结本地方法栈(线程私有)Java堆(全局共享)方法区(全局共享)运行常量池直接内存HotSpot...

    本博客主要参考周志明老师的《深入理解Java虚拟机》第二版

    读书是一种跟大神的交流。阅读《深入理解Java虚拟机》受益匪浅,对Java虚拟机有初步的认识。这里写博客主要出于以下三个目的:一方面是记录,方便日后阅读;一方面是加深对内容的理解;一方面是分享给大家,希望对大家有帮助。

    《深入理解Java虚拟机》全书总结如下:

    序号内容链接地址
    1深入理解Java虚拟机-走近Javahttps://blog.csdn.net/ThinkWon/article/details/103804387
    2深入理解Java虚拟机-Java内存区域与内存溢出异常https://blog.csdn.net/ThinkWon/article/details/103827387
    3深入理解Java虚拟机-垃圾回收器与内存分配策略https://blog.csdn.net/ThinkWon/article/details/103831676
    4深入理解Java虚拟机-虚拟机执行子系统https://blog.csdn.net/ThinkWon/article/details/103835168
    5深入理解Java虚拟机-程序编译与代码优化https://blog.csdn.net/ThinkWon/article/details/103835883
    6深入理解Java虚拟机-高效并发https://blog.csdn.net/ThinkWon/article/details/103836167

    Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的"高墙",墙外面的人想进去,墙里面的人却想出来。

    概述

    对于从事C、C++程序开发的开发人员来说,在内存管理领域,他们既是拥有最高权力的“皇帝”又是从事最基础工作的“劳动人民”——既拥有每一个对象的“所有权”,又担负着每一个对象生命开始到终结的维护责任。
    对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new操作去写配对的delete/free代码,不容易出现内存泄漏和内存溢出问题,由虚拟机管理内存这一切看起来都很美好。不过,也正是因为Java程序员把内存控制的权力交给了Java虚拟机,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样使用内存的,那么排查错误将会成为一项异常艰难的工作。

    运行时数据区域

    在这里插入图片描述

    JVM包含两个子系统和两个组件,两个子系统为Class loader(类装载)、Execution engine(执行引擎);两个组件为Runtime data area(运行时数据区)、Native Interface(本地接口)。

    Class loader(类装载):根据给定的全限定名类名(如:java.lang.Object)来装载class文件到Runtime data area中的method area。

    Execution engine(执行引擎):执行classes中的指令。

    Native Interface(本地接口):与native libraries交互,是其它编程语言交互的接口。

    Runtime data area(运行时数据区域):这就是我们常说的JVM的内存。

    Java 虚拟机在执行 Java 程序的过程中会把它所管理的内存区域划分为若干个不同的数据区域。这些区域都有各自的用途,以及创建和销毁的时间,有些区域随着虚拟机进程的启动而存在,有些区域则是依赖线程的启动和结束而建立和销毁。Java 虚拟机所管理的内存被划分为如下几个区域:

    在这里插入图片描述

    程序计数器(线程私有)

    程序计数器是一块较小的内存区域,可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。「属于线程私有的内存区域」

    附加:

    1. 当前线程所执行的字节码行号指示器
    2. 每个线程都有一个自己的PC计数器。
    3. 线程私有的,生命周期与线程相同,随JVM启动而生,JVM关闭而死。
    4. 线程执行Java方法时,记录其正在执行的虚拟机字节码指令地址
    5. 线程执行Native方法时,计数器记录为(Undefined)。
    6. 唯一在Java虚拟机规范中没有规定任何OutOfMemoryError情况区域。

    Java虚拟机栈(线程私有)

    线程私有内存空间,它的生命周期和线程相同。线程执行期间,每个方法被执行时,都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每个方法从被调用到执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。「属于线程私有的内存区域」

    注意:下面的内容为附加内容,对Java虚拟机栈进行详细说明,感兴趣的小伙伴可以有针对性的阅读

    下面依次解释栈帧里的四种组成元素的具体结构和功能:

    局部变量表

    局部变量表局部变量表是 Java 虚拟机栈的一部分,是一组变量值的存储空间,用于存储方法参数局部变量。 在 Class 文件的方法表的 Code 属性的 max_locals 指定了该方法所需局部变量表的最大容量

    局部变量表在编译期间分配内存空间,可以存放编译期的各种变量类型:

    1. 基本数据类型boolean, byte, char, short, int, float, long, double8种;
    2. 对象引用类型reference,指向对象起始地址引用指针;不等同于对象本身,根据不同的虚拟机实现,它可能是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或者其他与此对象相关的位置
    3. 返回地址类型returnAddress,返回地址的类型。指向了一条字节码指令的地址

    变量槽(Variable Slot):

    变量槽局部变量表最小单位,规定大小为32位。对于64位的longdouble变量而言,虚拟机会为其分配两个连续Slot空间。

    操作数栈

    操作数栈Operand Stack)也常称为操作栈,是一个后入先出栈。在 Class 文件的 Code 属性的 max_stacks 指定了执行过程中最大的栈深度。Java虚拟机的解释执行引擎被称为基于栈的执行引擎 ,其中所指的就是指-操作数栈

    1. 局部变量表一样,操作数栈也是一个以32字长为单位的数组。
    2. 虚拟机在操作数栈中可存储的数据类型intlongfloatdoublereferencereturnType等类型 (对于byteshort以及char类型的值在压入到操作数栈之前,也会被转换为int)。
    3. 局部变量表不同的是,它不是通过索引来访问,而是通过标准的栈操作压栈出栈来访问。比如,如果某个指令把一个值压入到操作数栈中,稍后另一个指令就可以弹出这个值来使用。

    虚拟机把操作数栈作为它的工作区——大多数指令都要从这里弹出数据,执行运算,然后把结果压回操作数栈

    begin
    iload_0    // push the int in local variable 0 onto the stack
    iload_1    // push the int in local variable 1 onto the stack
    iadd       // pop two ints, add them, push result
    istore_2   // pop int, store into local variable 2
    end
    

    在这个字节码序列里,前两个指令 iload_0iload_1 将存储在局部变量表中索引为01的整数压入操作数栈中,其后iadd指令从操作数栈中弹出那两个整数相加,再将结果压入操作数栈。第四条指令istore_2则从操作数栈中弹出结果,并把它存储到局部变量表索引为2的位置。

    下图详细表述了这个过程中局部变量表操作数栈的状态变化(图中没有使用的局部变量表操作数栈区域以空白表示)。

    在这里插入图片描述

    动态链接

    每个栈帧都包含一个指向运行时常量池中所属的方法引用,持有这个引用是为了支持方法调用过程中的动态链接

    Class文件的常量池中存在有大量的符号引用,字节码中的方法调用指令就以常量池中指向方法的符号引用为参数。这些符号引用:

    1. 静态解析:一部分会在类加载阶段或第一次使用的时候转化为直接引用(如finalstatic域等),称为静态解析
    2. 动态解析:另一部分将在每一次的运行期间转化为直接引用,称为动态链接
    方法返回地址

    当一个方法开始执行以后,只有两种方法可以退出当前方法:

    1. 正常返回:当执行遇到返回指令,会将返回值传递给上层的方法调用者,这种退出的方式称为正常完成出口(Normal Method Invocation Completion),一般来说,调用者的PC计数器可以作为返回地址。
    2. 异常返回:当执行遇到异常,并且当前方法体内没有得到处理,就会导致方法退出,此时是没有返回值的,称为异常完成出口(Abrupt Method Invocation Completion),返回地址要通过异常处理器表来确定。

    当一个方法返回时,可能依次进行以下3个操作:

    1. 恢复上层方法局部变量表操作数栈
    2. 返回值压入调用者栈帧操作数栈
    3. PC计数器的值指向下一条方法指令位置。
    小结

    注意:在Java虚拟机规范中,对这个区域规定了两种异常。

    其一:如果当前线程请求的栈深度大于虚拟机栈所允许的深度,将会抛出 StackOverflowError 异常(在虚拟机栈不允许动态扩展的情况下);

    其二:如果扩展时无法申请到足够的内存空间,就会抛出 OutOfMemoryError 异常。

    本地方法栈(线程私有)

    本地方法栈Java虚拟机栈发挥的作用非常相似,主要区别是Java虚拟机栈执行的是Java方法服务,而本地方法栈执行Native方法服务(通常用C编写)。

    有些虚拟机发行版本(譬如Sun HotSpot虚拟机)直接将本地方法栈Java虚拟机栈合二为一。与虚拟机栈一样,本地方法栈也会抛出StackOverflowErrorOutOfMemoryError异常。

    Java堆(全局共享)

    对大多数应用而言,Java 堆是虚拟机所管理的内存中最大的一块,是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一作用就是存放对象实例,几乎所有的对象实例都是在这里分配的(不绝对,在虚拟机的优化策略下,也会存在栈上分配、标量替换的情况,后面的章节会详细介绍)。

    Java 堆是 GC 回收的主要区域,因此很多时候也被称为 GC 堆。

    从内存回收的角度看,由于现在收集器基本都采用分代收集算法,所以在Java堆被划分成两个不同的区域:新生代 (Young Generation) 、老年代 (Old Generation) 。新生代 (Young) 又被划分为三个区域:一个Eden区和两个Survivor区 - From Survivor区和To Survivor区。不过无论如何划分,都与存放内容无关,无论哪个区域,存储的都仍然时对象实例,记你一步划分的目的是为了使JVM能够更好的管理堆内存中的对象,包括内存的分配以及回收。

    简要归纳:新的对象分配是首先放在年轻代 (Young Generation) 的Eden区,Survivor区作为Eden区和Old区的缓冲,在Survivor区的对象经历若干次收集仍然存活的,就会被转移到老年代Old中。

    从内存回收的角度看,线程共享的 Java 堆可能划分出多个线程私有的分配缓冲区(Thread Local Allocation Buffer,TLAB)。「属于线程共享的内存区域」

    方法区(全局共享)

    方法区和Java堆一样,为多个线程共享,它用于存储类信息常量静态常量即时编译后的代码等数据。Non-Heap(非堆)「属于线程共享的内存区域」

    运行时常量池

    运行时常量池是方法区的一部分。Class文件中除了有类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(Constant Pool Table),用于存放编译期生成的各种字面常量和符号引用,这部分内容会在类加载后进入方法区的运行时常量池。

    下面信息为附加信息

    • HotSpot虚拟机中,将方法区称为“永久代”,本质上两者并不等价,仅仅是因为HotSpot虚拟机把GC分代收集扩展至方法区。
    • JDK 7的HotSpot中,已经将原本存放于永久代中的字符串常量池移出。
    • 根据虚拟机规范的规定,当方法区无法满足内存分配需求时,将会抛出OutOfMemoryError异常。当常量池无法再申请到内存时也会抛出OutOfMemoryError异常。
    • JDK 8的HotSpot中,已经将永久代废除,用元数据实现了方法区。元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。理论上取决于32位/64位系统可虚拟的内存大小。可见也不是无限制的,需要配置参数。

    在这里插入图片描述

    直接内存

    直接内存(Direct Memory)并不是虚拟机运行时数据区的一部分,也不是 Java 虚拟机规范中定义的内存区域。Java 中的 NIO 可以使用 Native 函数直接分配堆外内存,通常直接内存的速度会优于Java堆内存,然后通过一个存储在 Java 堆中的 DiectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景显著提高性能,对于读写频繁、性能要求高的场景,可以考虑使用直接内存,因为避免了在 Java 堆和 Native 堆中来回复制数据。直接内存不受 Java 堆大小的限制。

    HotSpot虚拟机对象探秘

    对象的创建

    说到对象的创建,首先让我们看看 Java 中提供的几种对象创建方式:

    Header解释
    使用new关键字调用了构造函数
    使用Class的newInstance方法调用了构造函数
    使用Constructor类的newInstance方法调用了构造函数
    使用clone方法没有调用构造函数
    使用反序列化没有调用构造函数

    下面是对象创建的主要流程:

    在这里插入图片描述

    虚拟机遇到一条new指令时,先检查常量池是否已经加载相应的类,如果没有,必须先执行相应的类加载。类加载通过后,接下来分配内存。若Java堆中内存是绝对规整的,使用“指针碰撞“方式分配内存;如果不是规整的,就从空闲列表中分配,叫做”空闲列表“方式。划分内存时还需要考虑一个问题-并发,也有两种方式: CAS同步处理,或者本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。然后内存空间初始化操作,接着是做一些必要的对象设置(元信息、哈希码…),最后执行<init>方法。

    下面内容是对象创建的详细过程

    对象的创建通常是通过new关键字创建一个对象的,当虚拟机接收到一个new指令时,它会做如下的操作。

    1.判断对象对应的类是否加载、链接、初始化

    虚拟机接收到一条new指令时,首先会去检查这个指定的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被类加载器加载、链接和初始化过。如果没有则先执行相应的类加载过程。

    在这里插入图片描述

    2.为对象分配内存

    类加载完成后,接着会在Java堆中划分一块内存分配给对象。内存分配根据Java堆是否规整,有两种方式:

    • 指针碰撞:如果Java堆的内存是规整,即所有用过的内存放在一边,而空闲的的放在另一边。分配内存时将位于中间的指针指示器向空闲的内存移动一段与对象大小相等的距离,这样便完成分配内存工作。
    • 空闲列表:如果Java堆的内存不是规整的,则需要由虚拟机维护一个列表来记录那些内存是可用的,这样在分配的时候可以从列表中查询到足够大的内存分配给对象,并在分配后更新列表记录。

    选择哪种分配方式是由 Java 堆是否规整来决定的,而 Java 堆是否规整又由所采用的垃圾收集器是否带有压缩整理功能决定。

    在这里插入图片描述

    3.处理并发安全问题

    对象的创建在虚拟机中是一个非常频繁的行为,哪怕只是修改一个指针所指向的位置,在并发情况下也是不安全的,可能出现正在给对象 A 分配内存,指针还没来得及修改,对象 B 又同时使用了原来的指针来分配内存的情况。解决这个问题有两种方案:

    • 对分配内存空间的动作进行同步处理(采用 CAS + 失败重试来保障更新操作的原子性);
    • 把内存分配的动作按照线程划分在不同的空间之中进行,即每个线程在 Java 堆中预先分配一小块内存,称为本地线程分配缓冲(Thread Local Allocation Buffer, TLAB)。哪个线程要分配内存,就在哪个线程的 TLAB 上分配。只有 TLAB 用完并分配新的 TLAB 时,才需要同步锁。通过-XX:+/-UserTLAB参数来设定虚拟机是否使用TLAB。

    在这里插入图片描述

    4.初始化分配到的内存空间

    内存分配完后,虚拟机要将分配到的内存空间初始化为零值(不包括对象头)。如果使用了 TLAB,这一步会提前到 TLAB 分配时进行。这一步保证了对象的实例字段在 Java 代码中可以不赋初始值就直接使用。

    5.设置对象的对象头

    接下来设置对象头(Object Header)信息,包括对象的所属类、对象的HashCode和对象的GC分代年龄等数据存储在对象的对象头中。

    6.执行init方法进行初始化

    执行init方法,初始化对象的成员变量、调用类的构造方法,这样一个对象就被创建了出来。

    对象的内存布局

    HotSpot虚拟机中,对象在内存中存储的布局可以分为三块区域:对象头Header)、实例数据Instance Data)和对齐填充Padding)。

    在这里插入图片描述

    对象头

    HotSpot虚拟机中,对象头有两部分信息组成:运行时数据类型指针,如果是数组对象,还有一个保存数组长度的空间。

    • Mark Word(运行时数据):用于存储对象自身运行时的数据,如哈希码(hashCode)、GC分带年龄线程持有的锁偏向线程ID 等信息。在32位系统占4字节,在64位系统中占8字节;

      HotSpot虚拟机对象头Mark Word在其他状态(轻量级锁定、重量级锁定、GC标记、可偏向)下对象的存储内容如下表所示:

    存储内容标志位状态
    对象哈希码、对象分代年龄01未锁定
    指向锁记录的指针00轻量级锁定
    指向重量级锁的指针10膨胀(重量级锁定)
    空,不需要记录信息11GC标记
    偏向线程ID、偏向时间戳、对象分代年龄01可偏向
    • Class Pointer(类型指针):用来指向对象对应的Class对象(其对应的元数据对象)的内存地址。在32位系统占4字节,在64位系统中占8字节;
    • Length:如果是数组对象,还有一个保存数组长度的空间,占4个字节;
    实例数据

    实例数据 是对象真正存储的有效信息,无论是从父类继承下来的还是该类自身的,都需要记录下来,而这部分的存储顺序受虚拟机的分配策略定义的顺序的影响。

    默认分配策略:

    long/double -> int/float -> short/char -> byte/boolean -> reference

    如果设置了-XX:FieldsAllocationStyle=0(默认是1),那么引用类型数据就会优先分配存储空间:

    reference -> long/double -> int/float -> short/char -> byte/boolean

    结论:

    分配策略总是按照字节大小由大到小的顺序排列,相同字节大小的放在一起。

    对齐填充

    无特殊含义,不是必须存在的,仅作为占位符。

    HotSpot虚拟机要求每个对象的起始地址必须是8字节的整数倍,也就是对象的大小必须是8字节的整数倍。而对象头部分正好是8字节的倍数(32位为1倍,64位为2倍),因此,当对象实例数据部分没有对齐的时候,就需要通过对齐填充来补全。

    对象的访问定位

    Java程序需要通过 JVM 栈上的引用访问堆中的具体对象。对象的访问方式取决于 JVM 虚拟机的实现。目前主流的访问方式有 句柄直接指针 两种方式。

    指针: 指向对象,代表一个对象在内存中的起始地址。

    句柄: 可以理解为指向指针的指针,维护着对象的指针。句柄不直接指向对象,而是指向对象的指针(句柄不发生变化,指向固定内存地址),再由对象的指针指向对象的真实内存地址。

    句柄访问

    Java堆中划分出一块内存来作为句柄池,引用中存储对象的句柄地址,而句柄中包含了对象实例数据对象类型数据各自的具体地址信息,具体构造如下图所示:
    在这里插入图片描述
    优势:引用中存储的是稳定的句柄地址,在对象被移动(垃圾收集时移动对象是非常普遍的行为)时只会改变句柄中实例数据指针,而引用本身不需要修改。

    直接指针

    如果使用直接指针访问,引用 中存储的直接就是对象地址,那么Java堆对象内部的布局中就必须考虑如何放置访问类型数据的相关信息。
    在这里插入图片描述
    优势:速度更,节省了一次指针定位的时间开销。由于对象的访问在Java中非常频繁,因此这类开销积少成多后也是非常可观的执行成本。HotSpot 中采用的就是这种方式。

    实战:OutOfMemoryError异常

    内存异常是我们工作当中经常会遇到问题,但如果仅仅会通过加大内存参数来解决问题显然是不够的,应该通过一定的手段定位问题,到底是因为参数问题,还是程序问题(无限创建,内存泄露)。定位问题后才能采取合适的解决方案,而不是一内存溢出就查找相关参数加大。

    概念

    内存泄露:代码中的某个对象本应该被虚拟机回收,但因为拥有GCRoot引用而没有被回收。

    内存溢出:虚拟机由于堆中拥有太多不可回收对象没有回收,导致无法继续创建新对象。

    在分析问题之前先给大家讲一讲排查内存溢出问题的方法,内存溢出时JVM虚拟机会退出,那么我们怎么知道JVM运行时的各种信息呢,Dump机制会帮助我们,可以通过加上VM参数-XX:+HeapDumpOnOutOfMemoryError让虚拟机在出现内存溢出异常时生成dump文件,然后通过外部工具(VisualVM)来具体分析异常的原因。

    除了程序计数器外,Java虚拟机的其他运行时区域都有可能发生OutOfMemoryError的异常,下面分别给出验证:

    Java堆溢出

    Java堆用来存储对象,因此只要不断创建对象,并保证 GC Roots 到对象之间有可达路径来避免垃圾回收机制清楚这些对象,那么当对象数量达到最大堆容量时就会产生 OOM。

    /**
     * java堆内存溢出测试
     * VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
     */
    public class HeapOOM {
    
        static class OOMObject{}
    
        public static void main(String[] args) {
            List<OOMObject> list = new ArrayList<OOMObject>();
            while (true) {
                list.add(new OOMObject());
            }
        }
    }
    

    运行结果:

    java.lang.OutOfMemoryError: Java heap space 
    Dumping heap to java_pid7164.hprof … 
    Heap dump file created [27880921 bytes in 0.193 secs] 
    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:242) 
    at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216) 
    at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208) 
    at java.util.ArrayList.add(ArrayList.java:440) 
    at com.jvm.oom.HeapOOM.main(HeapOOM.java:17)
    

    堆内存 OOM 是经常会出现的问题,异常信息会进一步提示 Java heap space

    虚拟机栈和本地方法栈溢出

    在 HotSpot 虚拟机中不区分虚拟机栈和本地方法栈,栈容量只由 -Xss 参数设定。关于虚拟机栈和本地方法栈,在 Java 虚拟机规范中描述了两种异常:

    • 如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常。
    • 如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出 OutOfMemoryError 异常。
    /**
     * 虚拟机栈和本地方法栈内存溢出测试,抛出stackoverflow exception
     * VM ARGS: -Xss128k 减少栈内存容量
     */
    public class JavaVMStackSOF {
    
        private int stackLength = 1;
    
        public void stackLeak () {
            stackLength++;
            stackLeak();
        }
    
        public static void main(String[] args) throws Throwable {
            JavaVMStackSOF oom = new JavaVMStackSOF();
            try {
                oom.stackLeak();
            } catch (Throwable e) {
                System.out.println("stack length = " + oom.stackLength);
                throw e;
            }
    
        }
    
    }
    

    运行结果:

    stack length = 11420 
    Exception in thread “main” java.lang.StackOverflowError 
    at com.jvm.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12) 
    at com.jvm.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13) 
    at com.jvm.oom.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:13) 
    

    以上代码在单线程环境下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配时,抛出的都是 StackOverflowError 异常。

    如果测试环境是多线程环境,通过不断建立线程的方式可以产生内存溢出异常,代码如下所示。但是这样产生的 OOM 与栈空间是否足够大不存在任何联系,在这种情况下,为每个线程的栈分配的内存足够大,反而越容易产生OOM 异常。这点不难理解,每个线程分配到的栈容量越大,可以建立的线程数就变少,建立多线程时就越容易把剩下的内存耗尽。这点在开发多线程的应用时要特别注意。如果建立过多线程导致内存溢出,在不能减少线程数或更换64位虚拟机的情况下,只能通过减少最大堆和减少栈容量来换取更多的线程。

    /**
     * JVM 虚拟机栈内存溢出测试, 注意在windows平台运行时可能会导致操作系统假死
     * VM Args: -Xss2M -XX:+HeapDumpOnOutOfMemoryError
     */
    
    public class JVMStackOOM {
    
        private void dontStop() {
            while (true) {}
        }
    
        public void stackLeakByThread() {
            while (true) {
                Thread thread = new Thread(new Runnable() {
    
                    @Override
                    public void run() {
                        dontStop();
                    }
                });
                thread.start();
            }
        }
    
        public static void main(String[] args) {
            JVMStackOOM oom = new JVMStackOOM();
            oom.stackLeakByThread();
        }
    }
    

    方法区和运行时常量池溢出

    方法区用于存放Class的相关信息,对这个区域的测试,基本思路是运行时产生大量的类去填满方法区,直到溢出。使用CGLib实现。

    方法区溢出也是一种常见的内存溢出异常,在经常生成大量Class的应用中,需要特别注意类的回收情况,这类场景除了使用了CGLib字节码增强和动态语言外,常见的还有JSP文件的应用(JSP第一次运行时要编译为Java类)、基于OSGI的应用等。

    /**
     * 测试JVM方法区内存溢出
     * VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
     */
    public class MethodAreaOOM {
    
        public static void main(String[] args) {
            while (true) {
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOMObject.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy proxy) throws Throwable {
                        return proxy.invokeSuper(obj, args);
                    }
                });
                enhancer.create();
            }
        }
    
        static class OOMObject{}
    }
    

    本机直接内存溢出

    DirectMemory 容量可通过 -XX:MaxDirectMemorySize 指定,如不指定,则默认与Java堆最大值一样。测试代码使用了 Unsafe 实例进行内存分配。

    由 DirectMemory 导致的内存溢出,一个明显的特征是在Heap Dump 文件中不会看见明显的异常,如果发现 OOM 之后 Dump 文件很小,而程序直接或间接使用了NIO,那就可以考虑检查一下是不是这方面的原因。

    /**
     * 测试本地直接内存溢出
     * VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M
     */
    public class DirectMemoryOOM {
    
        private static final int _1MB = 1024 * 1024;
    
        public static void main(String[] args) throws Exception {
            Field unsafeField = Unsafe.class.getDeclaredFields()[0];
            unsafeField.setAccessible(true);
            Unsafe unsafe = (Unsafe) unsafeField.get(null);
            while (true) {
                unsafe.allocateMemory(_1MB);
            }
        }
    }
    

    本章小结

    通过本章的学习,我们明白了虚拟机中的内存是如何划分的,哪部分区域、什么样的代码和操作可能导致内存溢出异常。虽然Java有垃圾收集机制,但内存溢出异常离我们仍然并不遥远,本章只是讲解了各个区域出现内存溢出异常的原因。

    展开全文
  • springboot上传大文件时内存溢出的可能解决办法 在springboot中上传大文件时要考虑内存的情况,一般我们会通过在执行服务加入-Xms512m -Xmx512m等参数加大堆内存,但这是指标不治本的,关键还是看代码处理的...

    springboot上传大文件时内存溢出的可能解决办法

    • 在springboot中上传大文件时要考虑内存的情况,一般我们会通过在执行服务时加入-Xms512m -Xmx512m等参数加大堆内存,但这是指标不治本的,关键还是看代码处理的时候有无导致内存泄漏的原因。
      例如:
      java -Xms512m -Xmx512m -jar rent_web-1.0.0.jar
      
    • 有时候我们会需要把上传的文件再调用其他服务进行上传,即a把文件上传给b,b再把文件上传给c,那么在中间的这个转发服务如果处理不好就会导致内存溢出。
    • 下面是导致内存溢出的代码,这里溢出的原因是RequestBody的创建用了file.getBytes(),如果你继续跟进RequestBody.create就会发现最终会调用System.arraycopy来拷贝file.getBytes(),这相当于拷贝了一份,如果本来是512M,一拷贝就变成了1024M,内存肯定吃不消,于是就抛出内存溢出。
     public ApiResult<UploadResult> storeFile(MultipartFile file) throws IOException {
          	...
          	
            String originalName= URLEncoder.encode(file.getOriginalFilename(),"utf8");
            RequestBody rb = RequestBody.create(MediaType.parse("multipart/form-data"), file.getBytes());
            MultipartBody.Part part = MultipartBody.Part.createFormData("file",originalName, rb);
            EFSService efsService = getRandEFSService();
            Response<ApiResult<UploadResult>> res = efsService.upload(part, module).execute();
         	...
        }
    
    • 经过自己实践,我的思路是先把文件存到本地临时目录(其实上传文件大小超过10KB就已经会被存在本地临时目录,但由于是私有对象不可访问,当然你高兴可以用反射获得,但既然官方不开放这样的接口,那还是不要使用反射去获取了,避免以后官方实现发现了变化,那先前的反射也就会报错了),再将file置空让gc能够回收原来MultipartFile指向的内存,这样子没有拷贝多一份数据,大文件就不会多加一倍内存而溢出了。
     public ApiResult<UploadResult> storeFile( MultipartFile file) throws IOException {
           ...
            File tempFile = File.createTempFile("video", null);
            Log.info("临时文件目录:" + tempFile.getCanonicalPath());
            file.transferTo(tempFile);
            String originalName= URLEncoder.encode(file.getOriginalFilename(),"utf8");
            file=null;//file置为null是为了告诉gc此块内存可以回收
            RequestBody rb = RequestBody.create(MediaType.parse("multipart/form-data"), tempFile);
            MultipartBody.Part part = MultipartBody.Part.createFormData("file",originalName, rb);
            EFSService efsService = getRandEFSService();
            Response<ApiResult<UploadResult>> res = efsService.upload(part, module).execute();
           ...
        }
    
    • 本人测试过程中发现上传文件有时会出现以下这个错误,重启后会消失,奇怪的是出现这个问题时下断点发现竟然都没执行到,不知在哪里就抛出了这个异常(原因暂时不详,可能是springboot上传文件用到了临时目录,而这个临时目录又很容易被系统删除)。
    Failed to parse multipart servlet request; nested exception is java.io.IOException: The temporary upload location [C:\Users\HuWeiJian\AppData\Local\Temp\tomcat.4476717864971067098.8101\work\Tomcat\localhost\ROOT] is not valid
    

    解决办法是通过

     /**
         * 自定义临时文件目录,确保目录不会被系统删除导致抛异常
         * @return
         */
        @Bean
        public MultipartConfigElement multipartConfigElement(){
            MultipartConfigFactory factory=new MultipartConfigFactory();
            String location =System.getProperty("user.dir")+tempFileDir;
            Log.info("location="+location);
            File tmpDir=new File(location);
            if(!tmpDir.exists()){
                tmpDir.mkdirs();
    
            }
            factory.setLocation(location);
            return factory.createMultipartConfig();
        }
    
    • 或者在配置文件中增加:server.tomcat.basedir: /yourDir
    • 本人能力有限,如有错误请不吝指教。

    后话

    • MultipartFile其实只是一个接口,真正实现在org.springframework.web.multipart.support.StandardMultipartHttpServletRequest.StandardMultipartFile,
    • StandardMultipartFile里面有个javax.servlet.http.Part对象,
    • Part也是一个接口,真正实现在org.apache.catalina.core.ApplicationPart,
    • ApplicationPart里有个org.apache.tomcat.util.http.fileupload.FileItem接口和File,
    • .FileItem实现是org.apache.tomcat.util.http.fileupload.disk.DiskFileItem,File表示上传的文件(是一个临时存放文件)
    • DiskFileItem是由org.apache.tomcat.util.http.fileupload.disk.DiskFileItemFactory创建出来的,
    • 从DiskFileItemFactory.DEFAULT_SIZE_THRESHOLD = 10240可以得知如果文件大小在10KB以内,则直接放在内存,如果大于10KB,则存储在本地磁盘中(注意是存在临时目录中,意味着这个文件如果你不持久化那这个文件随时会被系统清除)

    参考

    Java中OutOfMemoryError(内存溢出)的三种情况及解决办法 - chen_lay的博客 - CSDN博客
    https://blog.csdn.net/chen_lay/article/details/52209748
    SpringBoot项目优化和Jvm调优(楼主亲测,真实有效) - 熊本同学 - CSDN博客
    https://blog.csdn.net/wd2014610/article/details/82182617

    展开全文
  • 对于 Java 的开发者来说,在虚拟机的自动内存管理机制的帮助下,不再需要为每一个 new 操作去配对的 delete/ free 代码,这样不容易出现内存泄露和内存溢出的问题,只要全权交给虚拟机去处理。不过,也正是因为...
  • Java内存区域与内存溢出

    千次阅读 2020-02-09 17:27:55
    在C语言中,开发者需要维护对象的出生和死亡,往往需要为每个new出来的对象编写配套的delete/free代码来释放内存,否则可能发生内存泄漏或溢出。 而在Java中,内存由JVM管理,垃圾回收器GC会帮助开发者自动回收不再...
  • JAVA内存溢出

    2016-04-21 10:18:55
    Java中OutOfMemoryError(内存溢出)的三种情况及解决办法
  • 深入理解Java内存区域与内存溢出异常 前言 Java开发中,我们不需要像在进行C/C++开发那样,需要自己去delete/free来释放申请的内存,在Java中内存是交给虚拟机管理,凡事都是两面的,因为我们把内存控制权交给...
  • Java内存结构与内存溢出

    千次阅读 2020-07-02 22:10:16
    也正在因为这个区别,JAVA在虚拟机自动内存管理的帮助下,一旦出现内存泄漏和溢出方面的问题,如果不了解虚拟机是怎样分配、回收内存的,在问题排查上会是一份艰难的工作。 Java虚拟机在执行Java程序会把所管理...
  • 解决java读取大文件内存溢出的问题

    千次阅读 2019-12-20 11:16:57
    1. 传统方式:在内存中读取文件内容 读取文件行的标准方式是在内存中读取,Guava 和Apache Commons IO都提供了如下所示快速读取文件行的方法: ? 1 2 Files.readLines(new File(path), ...
  • 解决java读取大文件内存溢出问题

    千次阅读 2017-08-09 17:59:00
    1. 传统方式:在内存中读取文件内容  读取文件行的标准方式是在内存中读取,Guava 和Apache Commons IO都提供了如下所示快速读取文件行的方法: Files.readLines(new File(path), Charsets.UTF_8); FileUtils....
  • JVM:自动内存管理之Java内存区域与内存溢出

    千次阅读 多人点赞 2021-01-07 12:03:22
    一、Java内存区域与内存溢出异常 1、运行数据区域 运行数据分为七大块 先来看看JVM内存分布图 1、程序计数器 程序计数器是一个记录着当前线程所执行的字节码的行号指示器。 Java虚拟机中每条线程都有...
  • 实战:OutOfMemoryError异常Java堆溢出虚拟机栈和本地方法栈溢出方法区和运行常量池溢出本机直接内存溢出????JVM参数设定 ????运行数据区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个...
  • Java与C++之间有- -堵由内存动态分配和垃圾收集技术所围成的“高墙”,墙外面的人想进去,墙里面的人却想出来。 2.1 概述 对于从事C、C++程序开发的开发人员来说,在内存管理领域,他们既是拥有最高权力的“皇帝”...
  • 读取文件行的标准方式是在内存中读取,Guava 和Apache Commons IO都提供了如下所示快速读取文件行的方法: 1 2 3 Files.readLines(new File(path), Charsets.UTF_8);   ...
  • 对于Java开发人员来讲,在虚拟机自动内存管理机制的帮助下,不需要为每一个new操作去配对的delete/free代码, 不容易出现内存泄露和内存溢出的问题。 正是因为JVM管理内存控制的权利,一旦出现内存泄露和内存溢出...
  • Java中OutOfMemoryError(内存溢出)的三种情况及解决办法

    万次阅读 多人点赞 2018-11-05 09:42:22
    Java中OutOfMemoryError(内存溢出)的三种情况及解决办法    相信有一定java开发经验的人或多或少都会遇到OutOfMemoryError的问题,这个问题曾困扰了我很长时间,随着解决各类问题经验的积累以及对问题根源的...
  • 问题: 当我们使用POI导出xlsx格式的excel之后,使用microsoft excel打开保存之后,发现变大...此后我们通过poi去读取这个由microsoft excel保存之后的文件java内存溢出了。是否具有好的方法去读取这个大文件的excel.
  • java 读 大文件excel 内存溢出 解决

    千次阅读 2017-05-25 13:27:31
    POI官方推荐解决内存溢出的方式使用CVS格式解析,我们不可能手工将Excel文件转换成CVS格式再上传,这样做太麻烦了,好再POI给出了xlsx转换CVS的例子,基于这个例子我进行了一下改造,即可解决用户模式读取Excel内存...
  • jxl文件时会出现内存溢出原因 jxl文件时会出现内存溢出原因
  • JVM划重点 第二章 Java内存区域与内存溢出异常概述运行数据区域 概述 在虚拟机自动内存管理机制的帮助下,不需要为new操作去配对的delete/free代码。但正因为把内存控制的权力交给了虚拟机,一旦出现内存泄漏...
  • 点击进入_更多_Java千百问1、Java堆什么情况会溢出所有对象的实例都在Java堆上分配内存,堆大小由-Xmx和-Xms来调节,如果程序使用的内存超过了堆最大内存(-Xmx),则会溢出Java heap space。了解java内存模型看这里...
  • https://www.cnblogs.com/zuochengsi-9/p/6485737.html
  • tomcat内存溢出处理方法适用于java,开发工具myeclipse或eclipse
  • java内存溢出之永久代内存溢出

    千次阅读 2018-02-03 15:29:25
    在实际开发中,我们经常会遇到oom,即所谓的内存溢出情况,但是不能是所有的内存溢出都一概而论,我们需要搞清楚具体内存的溢出原因和分类然后对症下药。这里和大家一起学习一下内存溢出中的永久代内存溢出。 ...
  • java内存溢出(OutOfMemoryError)————dump文件以及内存分析 文章目录java内存溢出(OutOfMemoryError)————dump文件以及内存分析1.dump内存快照1.1.测试环境(程序启动)1.2.生产环境(程序运行)2.分析...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 108,690
精华内容 43,476
关键字:

java写文件时内存溢出

java 订阅