jvm学习1

来源:互联网 发布:利于优化文章系统 编辑:程序博客网 时间:2024/06/16 09:19
对于搞开发的我们其实也是一样,现在流行的框架越来越多,封装的也越来越完善,各种框架可以搞定一切,几乎不用关注底层的实现,初级程序员只要熟悉基本的使用方法,便可快速的开发上线;但对于高级程序员来讲,内功的修炼却越发的重要,比如算法、设计模式、底层原理等,只有把这些基础熟练后,才能在开发过程中知其然知其所以然,出现问题时能快速定位到问题的本质。

      对于Java程序员来讲,spring全家桶几乎可以搞定一切,spring全家桶便是精妙的招式,jvm就是内功心法很重要的一块,线上出现性能问题,jvm调优更是不可回避的问题。因此JVM基础知识对于高级程序员的重要性不必言语.

    一. jvm体系总体分四大块:
         1.类的加载机制
        2.jvm内存结构
        3.GC算法 垃圾回收

        4.GC分析 命令调优


    二. 类的加载机制
         1.什么是类的加载
         2.类的生命周期

         3.类加载器

         4.双亲委派模型

Java代码编译和执行的整个过程包含了以下三个重要的机制:

  •  Java源码编译机制
  •  类加载机制
  •  类执行机制

                           


      JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述:

                 

          加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。

    三.什么是类的加载
       类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。

类的加载分为五个过程:加载、验证、准备、解析、初始化。

    四.类的生命周期
        1.加载,查找并加载类的二进制数据,在Java堆中也创建一个java.lang.Class类的对象
        2.连接,连接又包含三块内容:验证、准备、初始化。1)验证,文件格式、元数据、字节码、符号引用验证;2)准备,为类的静态变量分配内存,并将其初始化为默认值;3)解析,把类中的符号引用转换为直接引用
        3.初始化,为类的静态变量赋予正确的初始值
        4.使用,new出对象程序中使用
        5.卸载,执行垃圾回收


    五.类加载器
        1.启动类加载器Bootstrap ClassLoader,负责加载存放在JDK\jre\lib(JDK代表JDK的安装目录,下同)下,或被-Xbootclasspath参数指定的路径中的,并且能被虚拟机识别的类库。(用来加载java核心类库,无法被java程序直接引用。)
        2.扩展类加载器:Extension ClassLoader,该加载器由sun.misc.Launcher$ExtClassLoader实现,它负责加载DK\jre\lib\ext目录中,或者由java.ext.dirs系统变量指定的路径中的所有类库(如javax.*开头的类),开发者可以直接使用扩展类加载器。(负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包)
        3.应用程序类加载器:Application ClassLoader,该类加载器由sun.misc.Launcher$AppClassLoader来实现,它负责加载用户类路径(ClassPath)所指定的类,开发者可以直接使用该类加载器

        4.Custom ClassLoader/用户自定义类加载器(通过继承 java.lang.ClassLoader类的方式实现。)

属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader


    六.类加载机制(简述java类加载机制
       虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚拟机直接使用的java类型。

        1.全盘负责,当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
        2.父类委托,先让父类加载器试图加载该类,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
        3.缓存机制,缓存机制将会保证所有加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存区寻找该Class,只有缓存区不存在,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存入缓存区。这就是为什么修改了Class后,必须重启JVM,程序的修改才会生效.

        类加载双亲委派机制

       JVM在加载类时默认采用的是双亲委派机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类,由子类去完成类的加载。
   
  补充:
    1) 32 位和 64 位的 JVM,int 类型变量的长度是多数?
 
      答: 32 位和 64 位的 JVM 中,int 类型变量的长度是相同的,都是 32 位或者 4 个字节。(Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。意思就是说,在 32 位 和 64 位 的Java 虚拟机中,int 类型的长度是相同的。)

2)  Java 中 WeakReference 与 SoftReference的区别?(答案)

答:虽然 WeakReference 与 SoftReference 都有利于提高 GC 和 内存的效率,但是 WeakReference ,一旦失去最后一个强引用,就会被 GC 回收,而软引用虽然不能阻止被回收,但是可以延迟到 JVM 内存不足的时候。

3) WeakHashMap 是怎么工作的?(答案)

WeakHashMap 的工作与正常的 HashMap 类似,但是使用弱引用作为 key,意思就是当 key 对象没有任何引用时,key/value 将会被回收。

4)怎样通过 Java 程序来判断 JVM 是 32 位 还是 64 位?(答案)

你可以检查某些系统属性如 sun.arch.data.model 或 os.arch 来获取该信息。

5)32 位 JVM 和 64 位 JVM 的最大堆内存分别是多数?(答案)
理论上说上 32 位的 JVM 堆内存可以到达 2^32,即 4GB,但实际上会比这个小很多。不同操作系统之间不同,如 Windows 系统大约 1.5 GB,Solaris 大约 3GB。64 位 JVM允许指定最大的堆内存,理论上可以达到 2^64,这是一个非常大的数字,实际上你可以指定堆内存大小到 100GB。甚至有的 JVM,如 Azul,堆内存到 1000G 都是可能的。
     6)JRE、JDK、JVM 及 JIT 之间有什么不同?(答案)

 JRE 代表 Java 运行时(Java run-time),是运行 Java 引用所必须的。

 JDK 代表 Java 开发工具(Java development kit),是 Java 程序的开发工具,如 Java 编译器,它也包含 JRE。

 JVM 代表 Java 虚拟机(Java virtual machine),它的责任是运行 Java 应用。

 JIT 代表即时编译(Just In Time compilation),当代码执行的次数超过一定的阈值时,会将 Java 字节码转换为本地代码,如,主要的热点代码会被准换为本地代码,这样有利大幅度提高 Java 应用的性能。

 7)解释 Java 堆空间及 GC?(答案)
       当通过 Java 命令启动 Java 进程的时候,会为它分配内存。内存的一部分用于创建堆空间,当程序中创建对象的时候,就从对空间中分配内存。GC 是 JVM 内部的一个进程,回收无效对象的内存用于将来的分配。


内存管理和垃圾回收


    七. jvm内存结构

        1.方法区和堆是所有线程共享的内存区域;而java栈、本地方法栈和程序员计数器是运行是线程私有的内存区域。
        2.Java堆(Heap),是Java虚拟机所管理的内存中最大的一块。Java堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。(Java虚拟机管理内存中最大的一块,线程共享区域。所有对象实例和数组都在堆上分配内存空间。)
        3.方法区(Method Area),方法区(Method Area)与Java堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据(用于存储类信息,常量,静态变量等信息,是线程共享区域。)
        4.程序计数器(Program Counter Register),程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器
        5.JVM栈(JVM Stacks),与程序计数器一样,Java虚拟机栈(Java Virtual Machine Stacks)也是线程私有的,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局部变量表、操作栈、动态链接、方法出口等信息。每一个方法被调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中从入栈到出栈的过程。
        6.本地方法栈(Native Method Stacks),本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native方法服务。(保存native方法进入区域的地址)

       关于JVM内存管理的一些建议

        1、手动将生成的无用对象,中间对象置为null,加快内存回收。
        2、对象池技术如果生成的对象是可重用的对象,只是其中的属性不同时,可以考虑采用对象池来较少对象的生成。如果有空闲的对象就从对象池中取出使用,没有再生成新的对象,大大提高了对象的复用率。

       3、JVM调优通过配置JVM的参数来提高垃圾回收的速度,如果在没有出现内存泄露且上面两种办法都不能保证JVM内存回收时,可以考虑采用JVM调优的方式来解决,不过一定要经过实体机的长期测试,因为不同的参数可能引起不同的效果。如-Xnoclassgc参数等。

  堆的分区

  堆中存储对象实例,是垃圾回收的主要区域。为了方便垃圾回收,将堆区域分为新生代老年代两个区域。

  新生代大量对象(98%)都是朝生夕死,因此在进行垃圾回收的时候采用复制算法进行垃圾回收,因为只需付出商量存货对象的复制成本就可以完成收集。但是由于新生代大量对象都是非存活状态,按照常规复制算法1:1划分内存会造成大量空间的浪费。因此新生代又可以划分为一块较大的Eden区域和两块较小的Survivor空间,每次使用Eden和其中一块Survivor区域。回收时,将存活的对象一次性拷贝到另一块Survivor空间上,再清理掉用过的Eden和Survivor空间。默认Eden区域:Survivor区域=8:1 也就是每次使用新生代容量的90%,只有10%被浪费。但是当Survivor区域不足 以存储回收后存活的对象时,需要老年代进行空间担保,这些对象直接通过分配担保机制进入老年代。

  老年代:判断对象是否死亡,至少进行两次标记过程。如果对象在Eden出生并且经过一次gc后仍然存活,并且能够被Survivor容纳的话,将会被移动到Survivor区域,并将其年龄设置为1,如果它还能熬过下一次gc收集,年龄再+1.默认情况下当年龄到达15后,就会晋升到老年代中。老年代采用标记清除和标记整理算法进行垃圾收集。

JVM 内存回收


          Sun的JVM GenerationalCollecting (垃圾回收)原理是这样的:把对象分为年青代(Young)、年老代(Tenured)、持久代(Perm),对不同生命周期的对象使用不同的算法。(基于对对象生命周期分析)

                 

      1.Young(年轻代)

      年轻代分三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满时,还存活的对象将被复制到Survivor区(两个中的一个),当这个Survivor区满时,此区的存活对象将被复制到另外一个Survivor区,当这个Survivor去也满了的时候,从第一个Survivor区复制过来的并且此时还存活的对象,将被复制年老区(Tenured。需要注意,Survivor的两个区是对称的对象会在年轻代中分配空间,如果是过大的对象也可能会直接在年老代生成(据观测在运行某程序时候每次会生成一个十兆的空间用收发消息,这部分内存就会直接在年老代分配)。年轻代在空间被分配完的时候就会发起内存回收,大部分内存会被回收,一部分幸存的内存会被拷贝至Survivor的from区,经过多次回收以后如果from区内存也分配完毕,就会也发生内存回收然后将剩余的对象拷贝至to区。等到to区也满的时候,就会再次发生内存回收然后把幸存的对象拷贝至年老区。

 通常我们说的JVM内存回收总是在指堆,没先后关系,所以同一个区中可能同时存在从Eden复制过来对象,和从前一个Survivor复制过来的对象,而复制到年老区的只有从第一个Survivor去过来的对象。而且,Survivor区总有一个是空的。

     2.Tenured(年老代)

      年老代存放从年轻代存活的对象。一般来说年老代存放的都是生命期较长的对象。

     3.Perm(持久代)

     用于存放静态文件,如今Java类、方法等。持久代对垃圾回收没有显著影响,但是有些应用可能动态生成或者调用一些class,例如Hibernate等,在这种时候需要设置一个比较大的持久代空间来存放这些运行过程中新增的类。持久代大小通过-XX:MaxPermSize=进行设置。

     举个例子:当在程序中生成对象时,正常内存回收,确实只有堆中的内容是动态申请分配的,所以以上对象的年轻代和年老代都是指的JVM的Heap空间,而持久代则是之前提到的MethodArea,不属于Heap。

简述java垃圾回收机制

在java中,程序员是不需要显示的去释放一个对象的内存的,而是由虚拟机自行执行。在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。

GC的判定方法

GC的判定就是判断这个对象是否存活,包含2个判断方式:引用计数引用链

1.引用计数:给对象添加一个引用计数器,每当有一个地方引用它时,计数器加1。引用失效时,计数器减1 。当计数器为0时,对象就不可能再被使用。

残留问题:循环引用

2.引用链(可达性分析算法):通过Gc-Root的对象为起点,通过这个节点向下搜索,搜索所走过的路径为引用链。当一个对象到Gc-Root没有任何引用链连接时,证明对象不可用。

可作为Gc-Root的对象:

  • 虚拟机栈中的引用对象。
  • 本地方法栈中引用的对象。
  • 方法区类静态属性引用的对象。
  • 方法区中常量引用的对象。

   八.对象分配规则
        1.对象优先分配在Eden区,如果Eden区没有足够的空间时,虚拟机执行一次Minor GC。
        2.大对象直接进入老年代(大对象是指需要大量连续内存空间的对象)。这样做的目的是避免在Eden区和两个Survivor区之间发生大量的内存拷贝(新生代采用复制算法收集内存)。
        3.长期存活的对象进入老年代。虚拟机为每个对象定义了一个年龄计数器,如果对象经过了1次Minor GC那么对象会进入Survivor区,之后每经过一次Minor GC那么对象的年龄加1,知道达到阀值对象进入老年区。
        4.动态判断对象的年龄。如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代。
        5.空间分配担保。每次进行Minor GC时,JVM会计算Survivor区移至老年区的对象的平均大小,如果这个值大于老年区的剩余值大小则进行一次Full GC,如果小于检查HandlePromotionFailure设置,如果true则只进行Monitor GC,如果false则进行Full GC。


   九.GC算法
        GC最基础的算法有三种:标记 -清除算法、复制算法、标记-压缩算法(标记整理算法),我们常用的垃圾回收器一般都采用分代收集算法。
        1.标记 -清除算法: “标记-清除”(Mark-Sweep)算法,如它的名字一样,算法分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,在标记完成后统一进行回收。

          缺点:标记的过程效率不高、标记清除之后产生大量不连续的内存碎片,当需要申请大块连续内存空间时,无法找到。

        2.复制算法: “复制”(Copying)的收集算法,它将可用内存按容量划分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。

          缺点:每次只能使用总内存容量的一半。在对象存活较多的情况下会进行大量复制操作,效率底下。

        3.标记-压缩算法(标记整理算法: 标记过程仍然与“标记-清除”算法一样,但后续步骤不是直接对可回收对象进行清理,先对死亡对象进行标记,接着让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。

        4.分代收集算法: “分代收集”(Generational Collection)算法,把Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。(根据对象存活周期的不同,将内存划分为新生代与老年代,新生代使用复制算法,老年代使用标记清楚或标记整理算法进行垃圾收集)


    十.垃圾回收器(
GC收集器)
        1.Serial收集器,串行收集器是最古老,最稳定以及效率高的收集器,可能会产生较长的停顿,只使用一个线程去回收。(一个单线程的收集器,在进行垃圾收集时候,必须暂停其他所有的工作线程直到它收集结束。
)

       特点:CPU利用率最高,停顿时间即用户等待时间比较长。

        2.ParNew收集器,ParNew收集器其实就是Serial收集器的多线程版本。
        3.Parallel收集器,Parallel Scavenge收集器类似ParNew收集器,Parallel收集器更关注系统的吞吐量。(采用多线程来通过扫描并压缩)

       特点:停顿时间短,回收率高,对吞吐量要求高。
        4.Parallel Old 收集器,Parallel Old是Parallel Scavenge收集器的老年代版本,使用多线程和“标记-整理”算法
        5.CMS收集器,CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。(采用"标记-清除"算法实现,使用多线程的算法去扫描堆,对发现未使用的对象进行回收。)

        6.G1收集器,G1 (Garbage-First)是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足GC停顿时间要求的同时,还具备高吞吐量性能特征。(堆被划分成许多个连续的区域。采用G1算法进行回收,吸收了CMS收集器的特点)

        特点:支持很大的堆,高吞吐量,支持多CPU与垃圾回收线程,在主线程暂停的情况下,使用并发收集,在主线程运行的情况下,使用并发收集。
        7.GC算法和垃圾回收器算法图解以及更详细内容参考JVM(3):Java GC算法 垃圾收集器

补充: Serial 与 Parallel GC之间的不同之处?

         Serial 与 Parallel 在GC执行的时候都会引起 stop-the-world。它们之间主要不同 serial 收集器是默认的复制收集器,执行 GC 的时候只有一个线程,而 parallel 收集器使用多个 GC 线程来执行

    
    十一.GC日志分析
        摘录GC日志一部分(前部分为年轻代gc回收;后部分为full gc回收):
        2016-07-05T10:43:18.093+0800: 25.395: [GC [PSYoungGen: 274931K->10738K(274944K)] 371093K->147186K(450048K), 0.0668480 secs] [Times: user=0.17 sys=0.08, real=0.07 secs] 
        2016-07-05T10:43:18.160+0800: 25.462: [Full GC [PSYoungGen: 10738K->0K(274944K)] [ParOldGen: 136447K->140379K(302592K)] 147186K->140379K(577536K) [PSPermGen: 85411K->85376K(171008K)], 0.6763541 secs] [Times: user=1.75 sys=0.02, real=0.68 secs]
        通过上面日志分析得出,PSYoungGen、ParOldGen、PSPermGen属于Parallel收集器。其中PSYoungGen表示gc回收前后年轻代的内存变化;ParOldGen表示gc回收前后老年代的内存变化;PSPermGen表示gc回收前后永久区的内存变化。young gc 主要是针对年轻代进行内存回收比较频繁,耗时短;full gc 会对整个堆内存进行回城,耗时长,因此一般尽量减少full gc的次数


    十二.调优命令
    Sun JDK监控和故障处理命令有jps jstat jmap jhat jstack jinfo jconsole(几种常用的内存调试工具)

         java内存泄露的问题调查定位:jmap,jstack的使用等等
        1.jps,JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程。(查看JVM进程的状况,如进程ID)
        2.jstat,JVM statistics Monitoring是用于监视虚拟机运行时状态信息的命令,它可以显示出虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据。(虚拟机统计信息监视工具)
        3.jmap,JVM Memory Map命令用于生成heap dump文件。(生成堆转储快照文件(某一时刻))
        4.jhat,JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器中查看(对生成堆转储快照文件进行分析)
        5.jstack,用于生成java虚拟机当前时刻的线程快照。(生成线程快照)。
        6.jinfo,JVM Configuration info 这个命令作用是实时查看和调整虚拟机运行参数。

        7.jconsole: 主要是内存监控与线程监控。
        详细的命令使用参考这里JVM(4):Jvm调优-命令篇


    十三.调优工具
    常用调优工具分为两类,jdk自带监控工具:jconsole和jvisualvm,第三方有:MAT(Memory Analyzer Tool)、GChisto。
        1.jconsole,Java Monitoring and Management Console是从java5开始,在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和类等的监控
        2.jvisualvm,jdk自带全能工具,可以分析内存快照、线程快照;监控内存变化、GC变化等。
        3.MAT,Memory Analyzer Tool,一个基于Eclipse的内存分析工具,是一个快速、功能丰富的Java heap分析工具,它可以帮助我们查找内存泄漏和减少内存消耗
        4.GChisto,一款专业分析gc日志的工具

Minor GC与Full GC

Minor GC与Full GC分别在什么时候发生?

Minor GC: 从年轻代空间(包括 Eden 和 Survivor 区域)回收内存被称为 Minor GC。 
特点:当 JVM 无法为一个新的对象分配空间时会触发 Minor GC,比如当 Eden 区满了。所以分配率越高,越频繁执行 Minor G。内存池被填满的时候,其中的内容全部会被复制,指针会从0开始跟踪空闲内存。Eden 和 Survivor 区进行了标记和复制操作,取代了经典的标记、扫描、压缩、清理操作。所以 Eden 和 Survivor 区不存在内存碎片。写指针总是停留在所使用内存池的顶部。 执行 Minor GC 操作时,不会影响到永久代。从永久代到年轻代的引用被当成 GC roots,从年轻代到永久代的引用在标记阶段被直接忽略掉。质疑常规的认知,所有的 Minor GC 都会触发“全世界的暂停(stop-the-world)”,停止应用程序的线程。对于大部分应用程序,停顿导致的延迟都是可以忽略不计的。其中的真相就 是,大部分 Eden 区中的对象都能被认为是垃圾,永远也不会被复制到 Survivor 区或者老年代空间。如果正好相反,Eden 区大部分新生对象不符合 GC 条件,Minor GC 执行时暂停的时间将会长很多。

Major GC:清理永久代

Full GC:清理整个堆空间—包括年轻代和永久代

Major GC / Full GC:老年代 GC(Major GC / Full GC):指发生在老年代的 GC,出现了 Major GC,经常会伴随至少一次的 Minor GC(但非绝对的,在 ParallelScavenge 收集器的收集策略里就有直接进行 Major GC 的策略选择过程) 。MajorGC 的速度一般会比 Minor GC 慢 10倍以上。


十四. 说一下对象的创建方法?对象的内存布局?对象的访问定位?
         四种不同的方法创建Java对象
     1.用 new 语句创建对象,知识最常用的创建对象的方式
     2. 调用对象的clone()方法;
         MyObject anotherObject = new  MyObject( );
         MyObject Object =anotherObject.clone( );
    使用clone( )方法克隆一个对象的步骤:
        1. 被克隆的类要实现Cloneable接口;
        2. 被克隆的类要重写clone( )方法。

                                                     内存泄露与内存溢出
内存泄露
           1. 内存泄是指无用对象(不再使用的对象)持续占有内存或无用对象的内存得不到及时释放,从而造成的内存空间的浪费称为内存泄露。内存泄露有时不严重且不易察觉,这样开发者就不知道存在内存泄露,但有时也会很严重,会提示你Out of memory。
        2. 造成内存泄露的原因(长生命周期的对象持有短生命周期对象的引用就很可能发生内存泄露)
        1>静态集合类引起内存泄露: 
        像HashMap、Vector等的使用最容易出现内存泄露,这些静态变量的生命周期和应用程序一致,他们所引用的所有的对象Object也不能被释放,因为他们也将一直被Vector等引用着。
         2>各种连接 
         比如数据库连接(dataSourse.getConnection()),网络连接(socket)和io连接,除非其显式的调用了其close()方 法将其连接关闭,否则是不会自动被GC 回收的。对于Resultset 和Statement 对象可以不进行显式回收,但Connection 一定要显式回收,因为Connection 在任何时候都无法自动回收,而Connection一旦回收,Resultset 和Statement 对象就会立即为NULL。但是如果使用连接池,情况就不一样了,除了要显式地关闭连接,还必须显式地关闭Resultset Statement 对象(关闭其中一个,另外一个也会关闭),否则就会造成大量的Statement 对象无法释放,从而引起内存泄漏。这种情况下一般都会在try里面去的连接,在finally里面释放连接。
        3> 监听器
         在java 编程中,我们都需要和监听器打交道,通常一个应用当中会用到很多监听器,我们会调用一个控件的诸如addXXXListener()等方法来增加监听器,但往往在释放对象的时候却没有记住去删除这些监听器,从而增加了内存泄漏的机会。
       4>变量不合理的作用域
        一般而言,如果一个变量定义的作用范围大于其使用范围,很有可能造成内存泄露;另一方面,如果没有及时地把对象设置为null,很有可能会导致内存泄露的发生。

        3. 内存泄露的解决方案
        1.>  避免在循环中创建对象;
        2. > 尽早释放无用对象的引用。(最基本的建议)
        3. > 尽量少用静态变量,因为静态变量存放在永久代(方法区),永久代基本不参与垃圾回收;
        4.>  使用字符串处理,避免使用String,应大量使用StringBuffer,每一个String对象都得独立占用内存块区域。

在实际场景中,你怎么查找内存泄露?
      
      可以使用Jconsole
      没有内存泄露: 曲线的波形分布比较均匀;
      有内存泄露:如果内存的大小持续地增长,则说明系统存在内存泄露;
 
内存溢出(OOM)
       1. 内存溢出:指程序运行过程中无法申请到足够的内存而导致的一种错误。内存泄露是内存溢出的一种诱因,不是唯一因素。
      2.内存溢出的原因:
      (1) 内存中加载的数据量过于庞大;死循环 ;静态变量和静态方法过多;递归;无法确定是否被引用的对象;
       (2 )集合类中有对对象的引用,使用完后未清空,使得JVM不能回收;
       (3) 代码中存在死循环或魂环产生过多重复的对象实体;
       (4) 启动参数内存值设定的过小。

      3.内存溢出的解决办法:
       第一步,修改JVM启动参数,直接增加内存(建议堆的最大值设置未可用内存的最大值的80%);
      第二步,检查错误日志,查看"OutOfMemory" 错误前是否有其它的异常或者错误;
      第三步,对代码进行走查与分析,找出可能发生内存溢出的位置;‘
      第四步,使用内存查看工具动态查看内存使用情况。
 
      4.内存溢出的几种情况(00M异常)
       OutOfMemorryError 异常:
          除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生OutOfMemorryError(OOM) 异常的可能。
      A. 虚拟机栈与本地方法栈溢出
          如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverError异常
      B. 堆溢出
           一般的异常信息,java.lang.OutOfMemorryError:java heap spaces
      C. 方法区溢出
          异常信息:java.lang.OutOfMemorryError: PermGen space
     D. 运行时常量池溢出
         异常信息: java.lang.OutOfMemorryError:PermGen space
         如果要向运行时常量池中添加内容,最简单的做法是使用String.intern()这个Native方法;
原创粉丝点击