zoukankan      html  css  js  c++  java
  • 找工作——JVM内存管理

    1. JVM类加载机制

    类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括:加载、连接(验证、准备、解析)、初始化、使用和卸载阶段。

    加载:根据查找路径找到对应的class文件,然后倒入。

       检查:检查待加载的class文件的正确性。

     准备:给类中的静态变量分配存储空间。

       解析:将符号引用转化成直接引用。

    初始化:对静态变量和静态代码执行初始化工作。

    Java字节代码的表现形式是字节数组(byte[]),而Java类在JVM中的表现形式是java.lang.Class类的对象。一个Java类从字节代码到能够在JVM中被使用,需要经过加载、链接和初始化这三个步骤。这三个步骤中,对开发人员直接可见的是Java类的加载,通过使用Java类加载器(class loader)可以在运行时刻动态的加载一个Java类;而链接和初始化则是在使用Java类之前会发生的动作。

    原理:类加载器本身一个类,其实质是把类文件从硬盘读取到内存中。在Java语言中类的加载时动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类完全加载到JVM中国,至于其它类,则在需要时才加载。在Java语言中,可以把类分为3类:系统类、扩展类和自定义类。Java针对这3种不同类提供了3中类型的加载器

    启动类加载器:Bootstrap ClassLoader,它负责加载系统类(lib t.jar的类),扩展类加载器:Extension ClassLoader,它负责加载扩展类(jrelibext*.jar的类),应用程序类加载器:Application ClassLoader,它负责加载用户类路径(ClassPath)所指定的类。以上3类加载器是通过委托方式来完成类的加载,具体而言就是当有类需要被载入的时,类加载器会请求父类来完成这个工作,父类会使用其自己的搜索路径来搜索需要被载入的类,如果搜索不到,才会由子类按照其搜索路径来搜索待加载的类。

    2. JVM运行时数据区(JVM内存分配情况)

    Java虚拟机定义了若干种程序运行时使用到的运行时数据区

       有些是随虚拟机的启动而创建,随虚拟机的退出而销毁。

       第二种则是与线程对应,随着线程的开始和结束而创建和销毁。

    程序计数器:字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础都需要依赖这个计数器来完成。如果线程正在执行的是一个java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是Native方法,这个计数器值为空。此内存区域是唯一一个在Java虚拟机没有规定任何OutOfMemoryError情况的区域。

    虚拟机栈:此栈中的元素叫做栈帧,线程在调用Java方法时,会为每一个方法创建一个栈帧,来存储局部变量表、操作栈、动态链接、方法出口等信息。每个方法被调用和完成的过程,都对应一个栈帧从虚拟机上入栈和出栈的过程。虚拟机栈的生命周期和线程相同。

    本地方法栈:为线程私有,功能和虚拟机栈非常类似。来存储线程调用本地方法时,本地方法的局部变量表、操作栈等信息。

    :堆(Heap)区被所有线程共享,在虚拟机启动时创建。此区的功能就是存放对象实例,几乎所有的对象实例都是在这里分配内容。Heap区是垃圾回收器管理的主要区域。

         现在的的垃圾收集器都采用分代收集算法,所以Java堆还可以细分为新生代和老年代,进一步划分的目的是为了更好回收内存。

    方法区:该区为各个线程共享,用于存储已经被虚拟机加载的类信息、常量、静态变量、即时编译器编译出来的代码等数据。

    3. 垃圾回收算法

       在垃圾收集钱需要判断对象是否存活:

      引用计数法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加1;当引用失效时,计数器就减1;任何时刻计数器为0的对象就是不能能再被使用的。(无法解决对象之间相互循环引用)

      可达性分析算法:这个算法的基本思想就是通过一系列的称为“GC Roots”的对象作为起始点,从这个节点开始向下搜索,搜索所经过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连接(用图论的话来说,就是从GC Roots到这个对象不可达)时,则证明此对象不可引用。(在Java中,可以作为GC Roots的对象包括:虚拟器栈中引用的对象;方法区中类静态属性引用的对象;方法区中常量引用的对象;本地方法栈中JNI引用的对象)

       Java堆中各代分布:

       heap区又分:Eden Space(伊甸园)、Survivor Space(幸存者区)、Tenured Gen(老年代-养老区)、Perm Gen(永久代)(注:许多人多喜欢把方法区成为“永久代”,本质上两者并不等价,只因为HotSpot虚拟机的设计团队把GC分代收集扩展至方法区,这样可以像管理Java堆一样管理这部分内存,能够省去专门为方法区编写内存管理代码的工作,对于其他虚拟机来说不存在永久代的概念)

       Young:主要是用来存放新生的对象。

     Old:主要存放应用程序中生命周期长的内存对象。

     Permanent:是指内存的永久保存区域,主要存放Class和Meta的信息,Class在被 Load的时候被放入PermGen space区域. 它和和存放Instance的Heap区域不同,GC(Garbage Collection)不会在主程序运行期对PermGen space进行清理,所以如果你的APP会LOAD很多CLASS的话,就很可能出现PermGen space错误。

    http://my.oschina.net/sunnywu/blog/332870

    2. JVM 使用的GC算法是什么?

    分代收集。

    即将内存分为几个区域,将不同生命周期的对象放在不同区域里;

    在GC收集的时候,频繁收集生命周期短的区域(Young area);

    比较少的收集生命周期比较长的区域(Old area);

    基本不收集的永久区(Perm area)。

    3. GC 和 Full GC 有什么区别?

    GC(或Minor GC):收集 生命周期短的区域(Young area)。

    Full GC (或Major GC):收集生命周期短的区域(Young area)和生命周期比较长的区域(Old area)对整个堆进行垃圾收集。

    他们的收集算法不同,所以使用的时间也不同。 GC 效率也会比较高,我们要尽量减少 Full GC 的次数。 当显示调用System.gc() 时,gc does a full collection(both young generation and tenured generation).

       标记—清除算法:算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成之后统一回收所有被标记的对象。

        缺点:一时效率问题,标记和清除两个过程的效率都不高;另一个是空间问题,标记清除之后会产生大量不连续的内存碎片,空间碎片太多可能会导致以后在程序运行过程中需要分配较大对象时,无法找到足够的内存而不得不提前触发另一次垃圾垃圾收集动作。

     复制算法:将可利用的内存分为大小相等的两块,每次只使用其中的一块,当只一块的内存用完了,就将还存活的对象复制到另外一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配不必考虑内存碎片问题,只需要移动堆顶指针,按顺序分配内存即可,实现简单。只是这种算法的代价是将内存缩小为原来的一半,未免太高了。

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

     分代收集算法:新生代每次垃圾回收时都会发现大批对象死亡,那就采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集;而老年代中因为对象存活率高,可以使用标记—清理算法或标记—整理算法。

    引用:

      强引用:类似”Object obj = new Object()“这类的引用,只要强引用还在垃圾收集器永远不会回收掉被引用的对象。

      软引用:软引用是用来描述一些还有用但非必须的对象,对于软引用关联的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围进行第二次回收,如果这次回收还没有足够的内存,才会抛出内存异常。在JDK1.2之后,提供了SoftReference类来实现软引用。

      弱引用:弱引用也是用来描述非必须对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK1.2之后,提供了WeakReference类来实现弱引用。

      虚引用:虚引用也称幽灵引用,它是一种最弱的引用关系,一个对象是否有虚引用的存在,完全不会对其生存的时间构成影响,也无法通过虚引用来取的一个对象实例。为一个对象设置虚拟引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。早JDK1.2之后,提供了PhantomReference类来实现虚引用。

    HashMap和WeakHashMap:WeakHashMap中key采用的是弱引用的方式,只要WeakHashMap中的key不再被外部引用,他就可以被垃圾回收器回收,而HashMap中的key采用的强引用的方式,当HashMap中的key没有被外部引用时,只有在这个key从HashMap中删除之后,才可以被垃圾回收器回收。

    OutOfMemoryError:除了程序计数器外,虚拟机内存的其他几个运行时区域都有可能发生OOM。

    Java堆的溢出: Java堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制来清除这些对象,那么在对象达到最大空堆的容量限制后就会产生内存溢出异常。

    public class HeapOOM {
    	static class OOMObject{}
    	public static void main(String[] args) {
    		List<OOMObject> list = new ArrayList<HeapOOM.OOMObject>();
    		while(true){
    			list.add(new OOMObject());
    		}
    	}
    }
    

      -Xms20m -Xmx20m

    要解决这个区域的异常,一般的手段是通过内存映像分析工具(如:Eclipse Memory Analyzer)对Dump出来的堆转储快照进行分析,重点是确认内存中的对象是否是必要的,也就是要分清楚到底是出现了内存泄露(Memory Leak)还是内存溢出(Memory Overflow)。如果是内存泄露,可进一步通过工具查看泄露对象到GC Roots的引用链,于是就能找到泄露对象是通过怎样的路径与GC Roots相关联并导致垃圾收集器无法自动回收他们的。掌握了泄露对象的类型信息及GC Roots引用链的信息,就可以比较准确地定位出泄露代码的位置。如果不存在泄露,换句话来说,就是内存中的内存确实都还必须存活着,那就应当检查虚拟机的堆参数(-Xmx与Xms),与机器物理内存对比看是否还可以调大,从代码上检查是否存在某些对象生命周期过长、持有状态时间过长的情况,尝试减少程序运行期的内存消耗。

    内存泄露(内存泄露是指在一个不再被程序使用的对象或变量还在内存中占有存储空间)

    指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况,内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

    堆内存泄漏(Heap leak)。对内存指的是程序运行中根据需要分配通过malloc,realloc new等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak.

    系统资源泄露(Resource Leak).主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。  

  • 相关阅读:
    【华为云技术分享】浅谈服务化和微服务化(上)
    STM32 GPIO的原理、特性、选型和配置
    【华为云技术分享】如何设计高质量软件-领域驱动设计DDD(Domain-Driven Design)学习心得
    【华为云技术分享】如何做一个优秀软件-可扩展的架构,良好的编码,可信的过程
    【华为云技术分享】华为云MySQL新增MDL锁视图特性,快速定位元数据锁问题
    如何使网站支持https
    如何说孩子才会听,怎么听孩子才肯说
    box-sizing布局学习笔记
    vertical-align属性笔记
    Github上整理的日常发现的好资源【转】
  • 原文地址:https://www.cnblogs.com/java-cjt/p/5245529.html
Copyright © 2011-2022 走看看