「java内存缺漏」java中不存在内存泄漏

博主:adminadmin 2022-12-29 03:00:21 72

本篇文章给大家谈谈java内存缺漏,以及java中不存在内存泄漏对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。

本文目录一览:

Java 线程/内存模型的缺陷和增强

Java在语言层次上实现了对线程的支持 它提供了Thread/Runnable/ThreadGroup等一系列封装的类和接口 让程序员可以高效的开发Java多线程应用 为了实现同步 Java提供了synchronize关键字以及object的wait()/notify()机制 可是在简单易用的背后 应藏着更为复杂的玄机 很多问题就是由此而起 一 Java内存模型 在了解Java的同步秘密之前 先来看看JMM(Java Memory Model) Java被设计为跨平台的语言 在内存管理上 显然也要有一个统一的模型 而且Java语言最大的特点就是废除了指针 把程序员从痛苦中解脱出来 不用再考虑内存使用和管理方面的问题 可惜世事总不尽如人意 虽然JMM设计上方便了程序员 但是它增加了虚拟机的复杂程度 而且还导致某些编程技巧在Java语言中失效 JMM主要是为了规定了线程和内存之间的一些关系 对Java程序员来说只需负责用synchronized同步关键字 其它诸如与线程/内存之间进行数据交换/同步等繁琐工作均由虚拟机负责完成 如图 所示 根据JMM的设计 系统存在一个主内存(Main Memory) Java中所有变量都储存在主存中 对于所有线程都是共享的 每条线程都有自己的工作内存(Working Memory) 工作内存中保存的是主存中某些变量的拷贝 线程对所有变量的操作都是在工作内存中进行 线程之间无法相互直接访问 变量传递均需要通过主存完成   图 Java内存模型示例图 线程若要对某变量进行操作 必须经过一系列步骤 首先从主存复制/刷新数据到工作内存 然后执行代码 进行引用/赋值操作 最后把变量内容写回Main Memory Java语言规范(JLS)中对线程和主存互操作定义了 个行为 分别为load save read write assign和use 这些操作行为具有原子性 且相互依赖 有明确的调用先后顺序 具体的描述请参见JLS第 章 我们在前面的章节介绍了synchronized的作用 现在 从JMM的角度来重新审视synchronized关键字 假设某条线程执行一个synchronized代码段 其间对某变量进行操作 JVM会依次执行如下动作 ( ) 获取同步对象monitor (lock)( ) 从主存复制变量到当前工作内存 (read and load)( ) 执行代码 改变共享变量值 (use and assign)( ) 用工作内存数据刷新主存相关内容 (store and write)( ) 释放同步对象锁 (unlock)可见 synchronized的另外一个作用是保证主存内容和线程的工作内存中的数据的一致性 如果没有使用synchronized关键字 JVM不保证第 步和第 步会严格按照上述次序立即执行 因为根据JLS中的规定 线程的工作内存和主存之间的数据交换是松耦合的 什么时候需要刷新工作内存或者更新主内存内容 可以由具体的虚拟机实现自行决定 如果多个线程同时执行一段未经synchronized保护的代码段 很有可能某条线程已经改动了变量的值 但是其他线程却无法看到这个改动 依然在旧的变量值上进行运算 最终导致不可预料的运算结果 二 DCL失效 这一节我们要讨论的是一个让Java丢脸的话题 DCL失效 在开始讨论之前 先介绍一下LazyLoad 这种技巧很常用 就是指一个类包含某个成员变量 在类初始化的时候并不立即为该变量初始化一个实例 而是等到真正要使用到该变量的时候才初始化之 例如下面的代码 代码 class Foo { private Resource res = null; public Resource getResource() { if (res == null) res = new Resource(); return res; }}由于LazyLoad可以有效的减少系统资源消耗 提高程序整体的性能 所以被广泛的使用 连Java的缺省类加载器也采用这种方法来加载Java类 在单线程环境下 一切都相安无事 但如果把上面的代码放到多线程环境下运行 那么就可能会出现问题 假设有 条线程 同时执行到了if(res == null) 那么很有可能res被初始化 次 为了避免这样的Race Condition 得用synchronized关键字把上面的方法同步起来 代码如下 代码 Class Foo { Private Resource res = null; Public synchronized Resource getResource() { If (res == null) res = new Resource(); return res; }}现在Race Condition解决了 一切都很好 N天过后 好学的你偶然看了一本Refactoring的魔书 深深为之打动 准备自己尝试这重构一些以前写过的程序 于是找到了上面这段代码 你已经不再是以前的Java菜鸟 深知synchronized过的方法在速度上要比未同步的方法慢上 倍 同时你也发现 只有第一次调用该方法的时候才需要同步 而一旦res初始化完成 同步完全没必要 所以你很快就把代码重构成了下面的样子 代码 Class Foo {Private Resource res = null; Public Resource getResource() { If (res == null){ synchronized(this){ if(res == null){ res = new Resource();}} } return res; }}这种看起来很完美的优化技巧就是Double Checked Locking 但是很遗憾 根据Java的语言规范 上面的代码是不可靠的 造成DCL失效的原因之一是编译器的优化会调整代码的次序 只要是在单个线程情况下执行结果是正确的 就可以认为编译器这样的 自作主张的调整代码次序 的行为是合法的 JLS在某些方面的规定比较自由 就是为了让JVM有更多余地进行代码优化以提高执行效率 而现在的CPU大多使用超流水线技术来加快代码执行速度 针对这样的CPU 编译器采取的代码优化的方法之一就是在调整某些代码的次序 尽可能保证在程序执行的时候不要让CPU的指令流水线断流 从而提高程序的执行速度 正是这样的代码调整会导致DCL的失效 为了进一步证明这个问题 引用一下《DCL Broken Declaration》文章中的例子 设一行Java代码 Objects[i] reference = new Object();经过Symantec JIT编译器编译过以后 最终会变成如下汇编码在机器中执行 A mov eax F E h F call F B ;为Object申请内存空间 ; 返回值放在eax中 mov dword ptr [ebp] eax ; EBP 中是objects[i] reference的地址 ; 将返回的空间地址放入其中 ; 此时Object尚未初始化 mov ecx dword ptr [eax] ; dereference eax所指向的内容 ; 获得新创建对象的起始地址 mov dword ptr [ecx] h ; 下面 行是内联的构造函数 F mov dword ptr [ecx+ ] h mov dword ptr [ecx+ ] h D mov dword ptr [ecx+ Ch] F h可见 Object构造函数尚未调用 但是已经能够通过objects[i] reference获得Object对象实例的引用 如果把代码放到多线程环境下运行 某线程在执行到该行代码的时候JVM或者操作系统进行了一次线程切换 其他线程显然会发现msg对象已经不为空 导致Lazy load的判断语句if(objects[i] reference == null)不成立 线程认为对象已经建立成功 随之可能会使用对象的成员变量或者调用该对象实例的方法 最终导致不可预测的错误 原因之二是在共享内存的SMP机上 每个CPU有自己的Cache和寄存器 共享同一个系统内存 所以CPU可能会动态调整指令的执行次序 以更好的进行并行运算并且把运算结果与主内存同步 这样的代码次序调整也可能导致DCL失效 回想一下前面对Java内存模型的介绍 我们这里可以把Main Memory看作系统的物理内存 把Thread Working Memory认为是CPU内部的Cache和寄存器 没有synchronized的保护 Cache和寄存器的内容就不会及时和主内存的内容同步 从而导致一条线程无法看到另一条线程对一些变量的改动 结合代码 来举例说明 假设Resource类的实现如下 Class Resource{ Object obj;}即Resource类有一个obj成员变量引用了Object的一个实例 假设 条线程在运行 其状态用如下简化图表示   图 现在Thread 构造了Resource实例 初始化过程中改动了obj的一些内容 退出同步代码段后 因为采取了同步机制 Thread 所做的改动都会反映到主存中 接下来Thread 获得了新的Resource实例变量res 由于没有使用synchronized保护所以Thread 不会进行刷新工作内存的操作 假如之前Thread 的工作内存中已经有了obj实例的一份拷贝 那么Thread 在对obj执行use操作的时候就不会去执行load操作 这样一来就无法看到Thread 对obj的改变 这显然会导致错误的运算结果 此外 Thread 在退出同步代码段的时刻对ref和obj执行的写入主存的操作次序也是不确定的 所以即使Thread 对obj执行了load操作 也有可能只读到obj的初试状态的数据 (注 这里的load/use均指JMM定义的操作)有很多人不死心 试图想出了很多精妙的办法来解决这个问题 但最终都失败了 事实上 无论是目前的JMM还是已经作为JSR提交的JMM模型的增强 DCL都不能正常使用 在William Pugh的论文《Fixing the Java Memory Model》中详细的探讨了JMM的一些硬伤 更尝试给出一个新的内存模型 有兴趣深入研究的读者可以参见文后的参考资料 如果你设计的对象在程序中只有一个实例 即singleton的 有一种可行的解决办法来实现其LazyLoad 就是利用类加载器的LazyLoad特性 代码如下 Class ResSingleton {public static Resource res = new Resource();} lishixinzhi/Article/program/Java/gj/201311/27582

java多线程调用ktr文件内存溢出java.lang.OutOfMemoryError:GC overhead limit exceeded

两种方案:1.把Java虚拟机的内存设大点;2.手动释放内存机制,而不是执行完之后由Java虚拟机统一释放

Java循环创建多个对象后导致内存溢出!

在解决java内存溢出问题之前,需要对jvm(java虚拟机)的内存管理有一定的认识。jvm管理的内存大致包括三种不同类型的内存区域:Permanent Generation space(永久保存区域)、Heap space(堆区域)、Java Stacks(Java栈)。其中永久保存区域主要存放Class(类)和Meta的信息,Class第一次被Load的时候被放入PermGen space区域,Class需要存储的内容主要包括方法和静态属性。堆区域用来存放Class的实例(即对象),对象需要存储的内容主要是非静态属性。每次用new创建一个对象实例后,对象实例存储在堆区域中,这部分空间也被jvm的垃圾回收机制管理。而Java栈跟大多数编程语言包括汇编语言的栈功能相似,主要基本类型变量以及方法的输入输出参数。Java程序的每个线程中都有一个独立的堆栈。容易发生内存溢出问题的内存空间包括:Permanent Generation space和Heap space。

第一种OutOfMemoryError: PermGen space

发生这种问题的原意是程序中使用了大量的jar或class,使java虚拟机装载类的空间不够,与Permanent Generation space有关。解决这类问题有以下两种办法:

增加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服务器经常宕机的问题。

清理应用程序中web-inf/lib下的jar,如果tomcat部署了多个应用,很多应用都使用了相同的jar,可以将共同的jar移到tomcat共同的lib下,减少类的重复加载。这种方法是网上部分人推荐的,我没试过,但感觉减少不了太大的空间,最靠谱的还是第一种方法。

第二种OutOfMemoryError: Java heap space

发生这种问题的原因是java虚拟机创建的对象太多,在进行垃圾回收之间,虚拟机分配的到堆内存空间已经用满了,与Heap space有关。解决这类问题有两种思路:

检查程序,看是否有死循环或不必要地重复创建大量对象。找到原因后,修改程序和算法。 我以前写一个使用K-Means文本聚类算法对几万条文本记录(每条记录的特征向量大约10来个)进行文本聚类时,由于程序细节上有问题,就导致了Java heap space的内存溢出问题,后来通过修改程序得到了解决。

增加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)参数的大小。如:set JAVA_OPTS= -Xms256m -Xmx1024m

第三种OutOfMemoryError:unable to create new native thread

在java应用中,有时候会出现这样的错误:OutOfMemoryError: unable to create new native thread.这种怪事是因为JVM已经被系统分配了大量的内存(比如1.5G),并且它至少要占用可用内存的一半。有人发现,在线程个数很多的情况下,你分配给JVM的内存越多,那么,上述错误发生的可能性就越大。

那么是什么原因造成这种问题呢?

每一个32位的进程最多可以使用2G的可用内存,因为另外2G被操作系统保留。这里假设使用1.5G给JVM,那么还余下500M可用内存。这500M内存中的一部分必须用于系统dll的加载,那么真正剩下的也许只有400M,现在关键的地方出现了:当你使用Java创建一个线程,在JVM的内存里也会创建一个Thread对象,但是同时也会在操作系统里创建一个真正的物理线程(参考JVM规范),操作系统会在余下的400兆内存里创建这个物理线程,而不是在JVM的1500M的内存堆里创建。在jdk1.4里头,默认的栈大小是256KB,但是在jdk1.5里头,默认的栈大小为1M每线程,因此,在余下400M的可用内存里边我们最多也只能创建400个可用线程。

这样结论就出来了,要想创建更多的线程,你必须减少分配给JVM的最大内存。还有一种做法是让JVM宿主在你的JNI代码里边。

给出一个有关能够创建线程的最大个数的估算公式:

(MaxProcessMemory - JVMMemory - ReservedOsMemory) / (ThreadStackSize) = Number of threads

对于jdk1.5而言,假设操作系统保留120M内存:

1.5GB JVM: (2GB-1.5Gb-120MB)/(1MB) = ~380 threads

1.0GB JVM: (2GB-1.0Gb-120MB)/(1MB) = ~880 threads

对于栈大小为256KB的jdk1.4而言,

1.5GB allocated to JVM: ~1520 threads

1.0GB allocated to JVM: ~3520 threads

对于这个异常我们首先需要判断下,发生内存溢出时进程中到底都有什么样的线程,这些线程是否是应该存在的,是否可以通过优化来降低线程数; 另外一方面默认情况下java为每个线程分配的栈内存大小是1M,通常情况下,这1M的栈内存空间是足足够用了,因为在通常在栈上存放的只是基础类型的数据或者对象的引用,这些东西都不会占据太大的内存, 我们可以通过调整jvm参数,降低为每个线程分配的栈内存大小来解决问题,例如在jvm参数中添加-Xss128k将线程栈内存大小设置为128k。

怎样使JAVA栈内存快速溢出

1.如果线程请求分配的栈容量超过java虚拟机栈允许的最大容量的时候,java虚拟机将抛出一个StackOverFlowError异常。 2.如果java虚拟机栈可以动态拓展,并且扩展的动作已经尝试过,但是目前无法申请到足够的内存去完成拓展,或者在建立新线程的时候没有足够的内存去创建对应的虚拟机栈,那java虚拟机将会抛出一个OutOfMemoryError异常。 刚看到题主在问题下的评论,xss分配的应该是每个线程的栈大小,线程数量和整个进程的大小是由操作系统来限制的。 对于单个线程,栈内存容量减小,或者变量表深度增大,就会造成StackOverFlow,这点我跟题主想的一样。 至于是堆内存溢出还是方法区内存溢出还是栈内存溢出,其实可以用一些工具比如 JConsole来监视。

java内存缺漏的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于java中不存在内存泄漏、java内存缺漏的信息别忘了在本站进行查找喔。

The End

发布于:2022-12-29,除非注明,否则均为首码项目网原创文章,转载请注明出处。