zoukankan      html  css  js  c++  java
  • Java内存与垃圾收集知识总结

    总结一下关于Java内存的知识,今天我不生产知识,我只是知识的搬运工。

    1.运行时数据区域

    java虚拟机在执行JAVA程序的过程中会把它所管理的内存划分为若干个不同的数据区域。

    由所有线程共享的数据区

    • 堆[Heap]:

      Java堆是Java虚拟机管理的内存中最大的一块,此内存区域的唯一目的就是存放对象实例。所有的对象实例以及数组都要在堆上分配,但随着虚拟机技术的发展,这个变得不这么绝对。Java堆是垃圾收集其管理的主要区域,因此很多时候也被称为GC堆。根据虚拟机规范的规定,Java堆可以是处于物理上不连续的内存空间中,只要逻辑上是连续的即可。

    • 方法区[Method Area]:

      方法区用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

    线程隔离的数据区:

    • 虚拟机栈[VM]:

      虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行时都会创建一个栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。

    • 本地方法栈[Native Method Stack]:

      本地方法栈与虚拟机栈发挥的作用非常相似,区别就在于它是为Native方法服务的。

    • 程序计数器[Program Counter Register]:

      程序计数器是一块儿较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。

    上面是运行时数据非配的区域,我们了解之后,关注下面的重头戏,JVM很重要的垃圾回收机制。

    2.对象的生命旅程

    为了避免程序员繁重的内存管理工作,JVM提供了垃圾回收机制。

    2.1对象是否可达

    我们通过可达性分析来判断一个对象是否存活。
    这个算法的基本思路就是通过一系列的成为“GC Roots”的对象作为起始点,从这些点开始写向下搜索,搜索走过的路径成为引用链,当一个对象到GC Roots没有任何引用链相连(用图论的话说就是从GC Roots到这个对象不可达时),则证明这些对象是不可用的。
    在Java中,可作为GC Roots的对象包括下面几种:

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

    即使在可达性分析算法中不可达的对象,也并非是“非死不可”的,这时候他们暂时处于缓刑阶段,如果要真正需要宣告一个对象死亡,至少要经历两次标记的过程。
    第一次标记是看该对象是否有必要执行finalize()方法,如果有必要会放入F-Queue队列中。
    第二次标记前,对象可以通过重新与引用链上的任何一个对象建立关联,则可以逃脱被回收的命运,否则就会被回收。

    2.2方法区回收

    我们大多数时候认为方法区是属于永久代的,因为永久代的回收成本较高,所以我们可以选择不对永久代就行回收,但也可以对其进行回收。永久代的垃圾回收主要会后两部分内容:废弃常量和无用的类。
    无用的类需要同时满足下面三个条件:

    • 该类的所有实例都已经被回收,也就是Java堆中不存在该类的任何实例。
    • 加载该类的ClassLoader已经被回收。
    • 该类对应的java.lang.class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。

    虚拟机可以对满足上述3个条件的无用类进行回收。

    2.3四种引用

    由上文可知,一个对象是否被回收,和引用有很大关系,Java语言提供了四个级别的引用

    • 强引用

      强引用是我们最常见的Object o = new Object()这类引用,只要强引用还存在,垃圾回收器就永远不会回收被引用的对象。

    • 软引用

      软引用用来描述一些还有用但并非必须的对象,对于软引用关联的这对象,当内存资源紧张的时候,才会触发GC。是GC二次回收的对象,只有当回收软引用对象空间仍不足是,才会抛出内存异常对象。
      常用方法有SoftRefrence<T> tSoftRef = new SoftRefrence<T>(t)tSoftRef.get()方法
      每个软引用都可以附带一个引用队列,当对象的可达性状态发生改变时(由可达变成不可达),软引用对象就会进入引用队列,通过这个引用队列可以跟踪那个对象的回收情况,在初始化时加入一个引用队列的参数即可。

    • 弱引用

      弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作室,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。用WeakRefrence实现弱引用,和软引用一样,有两种初始化的方法。

    • 虚引用

      最弱的一种引用关系,一个对象有没有虚引用,完全不会对其生存时间构成影响,随时都可能被垃圾回收器回收。虚引用必须和引用队列一起使用,它的作用在于跟踪垃圾回收的过程。也无法通过get方法获得对象的实例。当垃圾回收器准备回收一个对象是,如果发现它有虚引用,就会在回收对象后,将这个虚引用加入引用队列,已通知应用程序对象的回收情况。可以使用PhantomRefrence实现虚引用。

    3.垃圾回收算法

    3.1单打独斗的算法

    • 引用计数法

      对于一个对象A,给其配备一个整型的计数器,只要任何一个对象引用了A,则A的引用计数器就加1,当引用失效后,引用计数器就减1,只要对象的引用计数器为0,则对象A就不可以在被引用,会被回收掉。
      但这种算法那有两个问题:(1)无法处理循环引用的情况,会引起内存泄露(2)每次引用产生和消除的时候,都需要伴随一个计数器变化的操作,影响性能。

    • 标记清除法

      如其名,分为两个阶段。标记阶段,实现通过根节点,标记所有从根节点开始的可达对象,因此,未被标记的对象就是未被引用的垃圾对象。然后,在清除阶段,清楚所有未被标记的对象。标记清楚算法可能产生的最大问题就是空间碎片。

    • 复制算法

      它将可用内存按照用量划分为大小相等的两块,每次只是用其中的一块,当这一块的内存用完了,就将还存活的对象复制到另外一块上去,然后再将已使用的内存空间一次性清理掉。这样使得每次都是对整个半区进行内存回收,不用考虑内存碎片等复杂情况,只需要移动堆顶指针,按顺序分配内存即可。这种方法的缺陷在于代价太大,将可用内存缩小到了原来的一半。所以在老年代这种每次回收只有一小部分甚至没有对象被回收的场景不适合用这种方法。

    • 标记-整理算法

      根据老年代的特点提出的算法。第一阶段标记过后,让所有存活的对象都向一端移动,然后直接清理掉端边界的内存。

    3.2 集众长,分代收集算法

    结合java对象的生存特点,提出了分代收集算法,结合上面各种算法的优点。
    分代算法根据对象的特点将内存区间分成几块

    • 新生代

      存放年轻对象的空间。年轻对象是指刚刚创建的,或者经历垃圾回收次数不多的对象。分为Eden区间,SurvivorFrom空间和SurvivorTo空间。通常是8:1:1,因为大部分新生对象都是朝生夕灭的,垃圾对象远远多于存活对象。
      在垃圾回收时,eden区的存活对象会被复制到未使用的Survivor空间中(假设是to),正在使用的Survivor空间(假设是From)中的年轻对象也会被复制到to空间中(大对象,或者老年对象会直接进入老年代,如果to空间已满,则对象直接进入老年代)。此时,eden空间和from空间中的剩余对象就是垃圾对象,可以直接清空,to空间则存放此次回收后仍然存货的对象。这种改进的复制算法,既保证了空间的连续性,又避免了大量的内存空间的浪费。
      通过参数SurvivorRadio来设置,即Survivor/Enden ,默认是8 。
      新生代满了或者区域不足触发的是MinorGC。

    • 老年代

      在老年代中,几乎所有的对象都是经过几次垃圾回收后依然得以存活的。因此可以认为这些对象在一定时间内会常驻内存。如果使用复制算法,代价很到,一般使用标记-清除或者标记-整理算法。
      参数:Xms设置初始堆大小,Xmx设置最大堆大小,NewRadio设置老年代与新生代的比例,老年代加新生代的大小之和即堆的大小。
      什么时候进入老年代呢?
      大对象:PretenureSizeThreshold。单位是字节。默认情况下是0,也就是不指定晋升大小,一切由运行情况决定。
      老年对象:MaxTenuringThreshold。默认15,也就是说最多经历15次GC,第16次GC的时候就会进入老年代。
      新生代回收频率高,每次耗时也短。老年代回收频率低,但是会消耗更多的时间。
      老年代满了触发的是FullGC,经常会伴随至少一次的MinorGC,比MinorGC慢10倍以上。

    • 永久代?NO!MetaSpace!

      在JDK1.6和JDK1.7的版本中,方法区可以理解成永久代,准确的说是JVM使用永久代来实现方法区。这里存储类信息和元数据。可以通过参数PermSize和MaxPerSize制定大小。
      但是这样会很明显面临一个问题,就是不够灵活,很容易溢出。
      所以在JDK1.8中,彻底移除了永久代 ,取而代之的是MetaSpace,使用本地内存存储类信息和元数据。
      此处参见下面两个链接的内容:
      Java PermGen 去哪里了?
      Java 8新特性探究(九)跟OOM:Permgen说再见吧

    今天就到这里了,明天总结关于垃圾收集器的知识。
    参考资料:
    《深入理解Java虚拟机》
    《实战Java虚拟机》

  • 相关阅读:
    【转并修改】VS2013 MVC Web项目使用内置的IISExpress支持局域网内部机器(手机、PC)访问、调试
    ASP.NET MVC4 UEditor 的上传图片配置路径
    转:Java图形化界面设计——布局管理器之FlowLayout(流式布局)其他请参考转载出处网址
    转载:win7JDK环境配置
    转载:java保留2位小数
    转载:遍历Map的四种方法
    架构模式: 远程过程调用
    架构模式: 外部配置化
    架构模式: 微服务的基底
    架构模式: 服务部署平台
  • 原文地址:https://www.cnblogs.com/yueyanglou/p/5512868.html
Copyright © 2011-2022 走看看