• Java垃圾回收机制以及内存泄露


    1、Java的内存泄露介绍

    首先明白一下内存泄露的概念:内存泄露是指程序执行过程动态分配了内存,可是在程序结束的时候这块内存没有被释放,从而导致这块内存不可用,这就是内存

    泄露,重新启动计算机能够解决问题,可是有可能再次发生内存泄露,内存泄露与硬件没有关系,它是软件设计的缺陷所导致的。

    Java发生内存泄露的原因非常明白,就是长声明周期对象持有短声明周期对象的引用就非常可能发生内存泄露。虽然短生命周期对象已经不再须要,可是由于长生命

    周期对象在持有它的引用而导致它不能被GC回收,这就是Java内存泄露发生的场景。

    java内存泄露场景举例:

    当我们不断的向集合类内加入元素,而没有对应的删除机制,导致内存一直被占用,这样也不一定就会造成内存泄露,当该集合类仅仅是一个局部变

    量的时候,当方法运行完成退出的时候,自然会被GC所回收,可是怕的是该集合类是一个全局的属性(比方类中的静态变量),那么会导致该集合类占用的内存仅仅

    增不减,这样就导致了内存的泄露。所以我们在使用全局性的集合类的时候要注意提供合适的删除策略或者定期清理策略。

    内存泄露能够分为4类:

    (1)常发性内存泄露:发生内存泄露的代码会多次被运行到,每次运行的时候就会有一块内存泄露

    (2)偶发性内存泄露:发生内存泄露的代码仅仅在某些特定环境下才会发生的,常发性与偶发性是相对的,对于特定的环境下,偶发性也就是常发性的。所以,測

    试方法和測试环境对检測内存泄露有是非常重要的。

    (3)一次性内存泄露:发生内存泄露的代码仅仅会被运行一次,也就内存中总有那么一块内存是不可用的。

    (4)隐式内存泄露    :程序运行过程中在不断的分配内存,直到程序结束的时候才会释放全部的内存,严格的说,这里并没有发生内存泄露,由于程序终于释放

    了全部申请的内存。可是对于一个server程序来说,往往会连续运行非常多天甚至好几个月的,这样迟早会发生内存溢出的情况。所以,我们称这类内存泄露为隐式

    内存泄露。

    2、Java内存溢出的问题及解决的方法

    JVM管理的内存大致分为三种不同类型的内存区域:

    Generation space(永久保存区域)、Heap space(堆区域)、JavaStacks(Java栈)。当中永久保存区域主要存放Class(类)和Meta的信息,Class第一次被Load

    的时候被放入PermGenspace区域,Class须要存储的内容主要包含方法和静态属性。堆区域用来存放Class的实例(即对象),对象须要存储的内容主要是非静态

    属性。每次用new创建一个对象实例后,对象实例存储在堆区域中,这部分空间也被jvm的垃圾回收机制管理。而Java栈跟大多数编程语言包含汇编语言的栈功能相

    似,主要基本类型变量以及方法的输入输出參数。Java程序的每一个线程中都有一个独立的堆栈。easy发生内存溢出问题的内存空间包含:

    Permanent Generation space和Heap space。

    第一种OutOfMemoryError PermGenspace

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

    (1) 添加java虚拟机中的XX:PermSizeXX:MaxPermSize參数的大小,当中XX:PermSize是初始永久保存区域大小,XX:MaxPermSize是最大永久保存区域大

    小。如针对tomcat6.0,在catalina.shcatalina.bat文件里一系列环境变量名说明结束处(大约在70行左右)添加一行:JAVA_OPTS=" -XX:PermSize=64M-

    XX:MaxPermSize=128m"假设是windowsserver还能够在系统环境变量中设置。感觉用tomcat公布sprint+struts+hibernate架构的程序时非常easy发生这样的内存溢出

    错误。使用上述方法,我成功攻克了部ssh项目的tomcatserver常常宕机的问题。

    (2) 清理应用程序中web-inf/lib下的jar,假设tomcat部署了多个应用,非常多应用都使用了同样的jar,能够将共同的jar移到tomcat共同的lib下,降低类的反复加

    载。这样的方法是网上部分人推荐的,我没试过,但感觉降低不了太大的空间,最靠谱的还是第一种方法。

    另外一种OutOfMemoryError  Javaheap space

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

    路:

    (1)检查程序,看是否有死循环或不必要地反复创建大量对象。找到原因后,改动程序和算法。

    我曾经写一个使用K-Means文本聚类算法对几万条文本记录(每条记录的特征向量大约10来个)进行文本聚类时,因为程序细节上有问题,就导致了Javaheap

     space的内存溢出问题,后来通过改动程序得到了解决

    (2)添加Java虚拟机中Xms(初始堆大小)和Xmx(最大堆大小)參数的大小。如:setJAVA_OPTS= -Xms256m -Xmx1024m

    3、Java的GC机制

    一个优秀的程序猿必须了解GC的原理、怎样有变化GC的性能、怎样与GC进行有限的交互,由于一些程序对性能要求较高,比如嵌入式系统、实时系统等,仅仅有

    全面提升内存的管理效率,才干有效提升程序的性能。

    GC的基本原理:

    Java的内存管理实际上就是对象的管理,包含对象的分配与释放

    对于程序猿来说,分配对象使用newkeyword,释放对象就是将对象的全部引用赋值为null,让程序不能訪问到这个对象,我们称之为‘不可达’状态,GC负责回收所

    有不可达状态的对象的内存空间。

    对于GC来说,当程序猿创建了对象,GC就開始监控这个对象的大小、地址、使用情况。通常GC採用有向图的方式来管理堆(heap)中全部对象。通过这样的方式

    来确定哪些对象是可达的,哪些对象是不可达的,当GC确定了一些对象是不可达状态的时候,GC就有责任将这些对象回收。可是,为了保证GC可以在不同平台实

    现的问题,Java规范对GC的非常多行为都没有进行严格的规定。比如,对于採用什么类型的回收算法、什么时候进行回收等重要问题都没有明白的规定。因此,不

    同的JVM的实现者往往有不同的实现算法。

    这也给Java程序猿的开发带来行多不确定性。本文研究了几个与GC工作相关的问题,努力降低这样的不确定性给Java程序带来的负面影响。 

    增量式GC( Incremental GC )

    GC在JVM中一般是由一个或一组进程来实现的,它本身也和用户程序一样占用heap空间,执行时也占用CPU.当GC进程执行时,应用程序停止执行。

    因此,当GC执行时间较长时,用户可以感到 Java程序的停顿,另外一方面,假设GC执行时间太短,则可能对象回收率太低,这意味着还有非常多应该回收的对象没

    有被回收,仍然占用大量内存。因此,在设计GC的时候,就必须在停顿时间和回收率之间进行权衡。一个好的GC实现同意用户定义自己所须要的设置,比如有些内

    存有限有设备,对内存的使用量很敏感,希望GC可以准确的回收内存,它并不在意程序速度的放慢。另外一些实时网络游戏,就不可以同意程序有长时间的中

    断。增量式GC就是通过一定的回收算法,把一个长时间的中断,划分为非常多个小的中断,通过这样的方式降低GC对用户程序的影响。尽管,增量式GC在总体性能上

    可能不如普通GC的效率高,可是它可以降低程序的最长停顿时间。Sun JDK提供的HotSpot JVM就能支持增量式GC.HotSpot JVM缺省GC方式为不使用增量GC,为

    了启动增量GC,我们必须在执行Java程序时添加-Xincgc的參数。

    HotSpot JVM增量式GC的实现是採用Train GC算法。它的基本想法就是,将堆中的全部对象依照创建和使用情况进行分组(分层),将使用频繁高和具有相关性

    的对象放在一队中,随着程序的执行,不断对组进行调整。当GC执行时,它总是先回收最老的(近期非常少訪问的)的对象,假设整组都为可回收对象,GC将整组回

    收。这样,每次GC执行仅仅回收一定比例的不可达对象,保证程序的顺畅执行。

    具体解释finalize函数

    finalize是位于Object类的一个方法,该方法的訪问修饰符为protected,因为全部类为Object的子类,因此用户类非常easy訪问到这种方法。因为,finalize函

    数没有自己主动实现链式调用,我们必须手动的实现,因此finalize函数的最后一个语句一般是super.finalize()

    通过这样的方式,我们能够实现从下到上实现finalize的调用,即先释放自己的资源,然后再释放父类的资源。依据Java语言规范,JVM保证调用finalize函数之

    前,这个对象是不可达的,可是JVM不保证这个函数一定会被调用。另外,规范还保证finalize函数最多执行一次。非常多Java刚開始学习的人会觉得这种方法类似与C++中

    的析构函数,将非常多对象、资源的释放都放在这一函数里面。事实上,这不是一种非常好的方式。原因有三:

    其一,GC为了可以支持finalize函数,要对覆盖这个函数的对象作非常多附加的工作。

    其二,在finalize执行完毕之后,该对象可能变成可达的,GC还要再检查一次该对象是否是可达的。因此,使用 finalize会减少GC的执行性能。

    其三,因为GC调用finalize的时间是不确定的,因此通过这样的方式释放资源也是不确定的。通常,finalize用于一些不easy控制、而且很重要资源的释放,例

    如一些I/O的操作,数据的连接。这些资源的释放对整个应用程序是很关键的。在这样的情况下,程序猿应该以通过程序本身管理(包含释放)这些资源为主,以

    finalize函数释放资源方式为辅,形成一种双保险的管理机制,而不应该只依靠finalize来释放资源。


  • 相关阅读:
    Pure播放器
    WPF绑定并转换
    WPF的DataTrigger使用
    NancyFx框架之检测任务管理器
    Asp.Net MVC 5使用Identity之简单的注册和登陆
    AspNetCore使用MySQL
    Head First 设计模式之适配器模式与外观模式
    Head First 设计模式之命令模式(CommandPattern)
    Head First 设计模式之工厂模式(Factory Pattern)
    .NET设计规范————类型设计规范
  • 原文地址:https://www.cnblogs.com/blfshiye/p/4369949.html
Copyright © 2020-2023  润新知