java 多线程 内存泄露_java内存泄露与内存溢出

java内存泄露与内存溢出

基本概念

内存泄露:指程序中动态分配内存给一些临时对象,但是对象不会被GC所回收,它始终占用内存。即被分配的对象可达但已无用。

内存溢出:指程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。

从定义上看,内存泄露是内存溢出的一种诱因,不是唯一因素。

JAVA中的内存泄露

Java中的内存泄露,广义并通俗的说,就是:不再会被使用的对象的内存不能被回收,就是内存泄露。Java中的内存泄露与C++中的表现有所不同。在C++中,所有被分配了内存的对象,不再使用后,都必须程序员手动的释放他们。所以,每个类,都会含有一个析构函数,作用就是完成清理工作,如果我们忘记了某些对象的释放,就会造成内存泄露。

但是在Java中,我们不用(也没有办法)自己释放内存,无用的对象由GC自动清理,这也极大的简化了我们的编程工作。但实际有时候一些不再会被使用的对象,在GC看来不能被释放,就会造成内存泄露。内存泄露的根本原因:长生命周期的对象持有短生命周期对象的引用。我们知道,对象都有生命周期的,有的长,有的短,如果长生命周期的对象持有短生命周期的引用,就很可能会出现内存泄露。下面给出了内存泄露中的一些例子:

全局变量缓存局部变量,且没有清空操作造成内存泄露

8ef17eeff6b6afcd99213b251160dae5.png

这里的object实例,其实我们期望它只作用于method1()方法中,且其他地方不会再用到它,但是,当method1()方法执行完成后,object对象所分配的内存不会马上被认为是可以被释放的对象,只有在Simple类创建的对象被释放后才会被释放,严格的说,这就是一种内存泄露。解决方法就是将object作为method1()方法中的局部变量。当然,如果一定要这么写,可以改为这样:

36866a478ca79a867d30312edeb9047f.png

这样,之前"new Object()"分配的内存,就可以被GC回收。

容器使用时的内存泄露

aed37c0fa3bc2b92c7b9c578f5f1dc0a.png

各种提供close()方法的对象

比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,以及使用其他框架的时候,除非其显式的调用了其close()方法(或类似方法)将其连接关闭,否则是不会自动被GC回收的。其实原因依然是长生命周期对象持有短生命周期对象的引用。

单例模式导致的内存泄露

单例模式,很多时候我们可以把它的生命周期与整个程序的生命周期看做差不多的,所以是一个长生命周期的对象。如果这个对象持有其他对象的引用,也很容易发生内存泄露。

JAVA中的内存溢出

堆相关内存溢出

outOfMemoryError: java heap space

在jvm规范中,堆中的内存是用来生成对象实例和数组的。如果细分,堆内存还可以分为年轻代和老年代,年轻代包括一个eden区和两个survivor区。当生成新对象时,内存的申请过程如下:

jvm先尝试在eden区分配新建对象所需的内存

如果内存大小足够,申请结束,否则下一步

jvm启动youngGC试图将eden区中不活跃的对象释放掉,释放后若eden空间仍然不足以放入新对象,则试图将部分Eden中活跃对象放入Survivor区

Surivior区被用来作为Eden及old的中间交换区域,当old区域空间足够时,Survivor区的对象将会被移到old区,否则会被保留在Survivor区

当old区域空间不够时,JVM会在old区进行full GC

full GC后,若Survivor及old区仍然无法存放从Eden复制过来的部分对象,导致JVM无法在Eden区为新对象创建内存区域,则会出现"out of memory错误"

解决办法

将堆内存 dump 下来,使用 MAT 分析一下,解决内存泄漏;

如果没有内存泄漏,使用 -Xmx 增大堆内存;

如果有自定义的 Finalizable 对象,考虑其存在的必要性。

java.lang.OutOfMemoryError:GCoverheadlimitexceeded

JDK6新增错误类型,当GC为释放很小空间占用大量时间抛出;一般是因为堆太小,导致异常的原因,没有足够的内存。

解决方法:

查看系统是否使用大内存的代码或死循环

通过添加JVM配置,来限制使用内存:-XX:-UseGCOverheadLimit

方法区内存溢出

outOfMemoryError: permgem space

在jvm规范中,方法区主要存放的是类信息、常量、静态变量等

报错原因

永久代是 HotSot 虚拟机对 方法区的具体实现,存放了已被虚拟机加载的类信息、常量、静态变量、JIT编译后的代码等。需要注意的是,在Java8后,永久代有了一个新名字:元空间,元空间使用的是本地内存。永久代里存在的信息也有了若干变化:

字符串常量由永久代转移到堆中;

和永久代相关的JVM参数已移除。

出现永久代或元空间的溢出的原因可能有如下几种:

有频繁的常量池操作(eg. String.intern),这种情况只适用于Java7之前应用;

加载了大量的类信息,且没有及时卸载;

应用部署完后没有重启。

这种事Perm区内存不够,可通过调整JVM的配置:

-XX:MaxPermSize=128m

-XX:PermSize=128m

设置永久区的初始空间和最大空间

-XX:PermSize设置持久代(perm gen)初始值,物理内存的1/64

-XX:MaxPermSize设置持久代最大值,物理内存的1/4

线程栈溢出

java.lang.StackOverflowError

线程栈是线程独有的一块内存结构,所以线程栈发生问题必定是某个线程运行时发生的错误。

一般线程栈溢出是由于递归太深或方法调用层级过多导致的。

发生栈溢出的错误信息为:

解决方法:优化程序设计,减少方法调用层次;调整-Xss参数增加线程栈大小

java.lang.OutOfMemoryError.unabletocreatenewnativethread

Stack空间不足以创建额外的线程,要么创建的线程过多,要么Stack空间确实小了。

解决:由于JVM没有提供参数设置总的stack空间大小,但可以设置单个线程栈的大小;而系统的用户空间一共是3G,除了Text/Data/BSS/MemoryMapping几个段之外,Heap和Stack空间的总量有限,是此消彼长的。因此遇到这个错误,可以通过两个途径解决:1.通过-Xss启动参数减少单个线程栈大小,这样便能开更多线程(当然不能太小,太小会出现StackOverflowError);2.通过-Xms-Xmx两参数减少Heap大小,将内存让给Stack(前提是保证Heap空间够用)。

本地方法溢出

java.lang.OutOfMemoryError: stack_trace_with_native_method

这种情况表明,本地方法在运行时出现了内存分配失败。和java.lang.OutOfMemoryError : unable to create new native Thread 保存不同,方法栈溢出出现在 JVM 的代码层面,而本地方法溢出发生在JNI代码或本地方法处。

java内存泄露排查

JVM如果出现内存泄露,典型的现象就是系统FullGC比较频繁。到最后干脆OOM(Out of Memory)了。

当发现应用内存溢出或长时间使用内存很高的情况下,通过内存dump进行分析可找到原因。当发现cpu使用率很高时,通过线程dump定位具体哪个线程在做哪个工作占用了过多的资源。内存dump是指通过jmap -dump 输出的文件,而线程dump是指通过jstack 输出的信息。在linux操作系统下(已安装jdk),执行jps命令,列出正在运行的java程序的进程ID。

5ea3459ff2f09226785f642468cd3506.png

使用top查看目前正在运行的进程使用系统资源情况。

8b6b62ddad76f04e2ec50cc026df88a9.png

首先是内存dump:

jmap –dump:live,format=b,file=heap.bin

其次是线程dump,比如说::

jstack -m >jvm_deadlocks.txt

jstack -l >jvm_listlocks.txt

但是dump堆要花较长的时间,并且文件巨大,再从服务器上拖回本地导入工具,这个过程太折腾不到万不得已最好别这么干。

可以用更轻量级的在线分析,用jmap查看存活的对象情况(jmap -histo:live [pid])。

2fb2b14fd53ad32620f3e32fc463d88e.png

比如上图所示,HashTable占用了大量的内存,如何找到导致这个事情发生的原因?可以进一步使用btrace来排查。

JvisualVM

VisualVM 是Netbeans的profile子项目,已在JDK6.0 update 7 中自带,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈(如100个String对象分别由哪几个对象分配出来的)。在JDK_HOME/bin(默认是C:\Program Files\Java\jdk1.6.0_13\bin)目录下面,有一个jvisualvm.exe文件,双击打开,从UI上来看,这个软件是基于NetBeans开发的了。

VisualVM 提供了一个可视界面,用于查看 Java 虚拟机 (Java Virtual Machine, JVM) 上运行的基于 Java 技术的应用程序的详细信息。VisualVM 对 Java Development Kit (JDK) 工具所检索的 JVM 软件相关数据进行组织,并通过一种使您可以快速查看有关多个 Java 应用程序的数据的方式提供该信息。您可以查看本地应用程序或远程主机上运行的应用程序的相关数据。此外,还可以捕获有关 JVM 软件实例的数据,并将该数据保存到本地系统,以供后期查看或与其他用户共享。

bec3d45a607422195d6e6507e069e6ca.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
面试问题整理:JVMJRE、JDK、JVM,JVM :英文名称(Java Virtual Machine),就是我们耳熟能详的 Java 虚拟机。它只认识 xxx.class 这种类型的文件,它能够将 class 文件中的字节码指令进行识别并调用操作系统向上的 API 完成动作。所以说,jvm 是 Java 能够跨平台的核心。 JRE :英文名称(Java Runtime Environment),我们叫它:Java 运行时环境。它主要包含两个部分,jvm的标准实现和 Java 的一些基本类库。它相对于 jvm 来说,多出来的是一部分的 Java 类库。 JDK :英文名称(Java Development Kit),Java 开发工具包。jdk 是整个 Java 开发的核心,它集成了 jre 和一些好用的小工具。例如:javac.exe,java.exe,jar.exe 等。 显然,这三者的关系是:一层层的嵌套关系。JDK>JRE>JVM。 运行时数据区域 线程私有: 1.程序计数器:它是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,唯一一个无OOM的区域。 2.Java虚拟机栈:描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧 ,用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 异常:线程请求的栈深度大于JVM所允许的深度:StackOverflowError 若JVM允许动态扩展,若无法申请到足够内存:OOM 3.本地方法栈:与虚拟机栈所发挥的作用是相似,它们之间的区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。 异常:同上。 线程共享:异常:OOM 内存溢出 1.Java堆:对于大多数应用来说,Java堆是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 2.方法区(元空间):方法区用于存储已被虚拟机加载的类信息、常量、静态变量,如static修饰的变量加载类的时候就被加载到方法区中。运行时常量池是方法区的一部分,class文件除了有类的字段、接口、方法等描述信息之外,还有常量池用于存放编译期间生成的各种字面量和符号引用。在老版jdk,方法区也被称为永久代。在1.8之后,由于永久代内存经常不够用或发生内存泄露,爆出异常OOM,所以在1.8之后废弃永久代,引入元空间的概念。元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。 直接内存:不受JVM GC管理,直接内存并不是虚拟机运行时数据区的一部分,也不是Java 虚拟机规范中定义的内存区域。在JDK1.4 中新加入了NIO(New Input/Output)类,引入了一种基于通道(Channel)与缓冲区(Buffer)的I/O 方式,它可以使用native 函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer 对象作为这块内存的引用进行操作。这样能在一些场景中显著提高性能,因为避免了在Java堆和Native堆中来回复制数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值