程序员

JVM基础之内存

2017-10-29  本文已影响35人  简xiaoyao

Java内存介绍


Java运行时数据区分为下面几个部分:

heap_1.png

第二种是先通过栈的 reference指向堆里对象实例句柄池,这个句柄池里包含了指向实例池的对象实例数据和指向方法区的对象类型数据;这两种方式各有优点,第一种reference直接就指向了对象,减少了一次指针指向,而第二种reference指向的句柄池指针不会随着对象的移动而改变(Java垃圾回收时会移动对象的位置),而第二种就需要改变reference的指向

heap_2.png

可以通过-Xms-Xmx来设置堆内存的大小,可以通过设置-XX:+HeapDumpOnOutOfMemoryError实现出现OutOfMemoryError异常时将异常信息保存至磁盘中

memory.png

除了上面的5个部分以外,还有一类JVM相关的内存空间称之为:直接内存;直接内存并不是虚拟机内存的一部分,也不是Java虚拟机规范中定义的内存区域。如JDK1.4中新加入的NIO,引入了通道与缓冲区的IO方式,它可以调用Native方法直接分配堆外内存,这个堆外内存就属于直接内存,直接内存的分配不会影响堆内存的大小

Java内存管理


这里说的Java内存分配主要指代的是对象的内存分配,也就是堆的运作机制;在JVM中,一般从对象存活时间角度上将存储空间分为

all.png

对象的生存或死亡

与内存分配对应的是内存的回收,Java的内存回收依靠的是垃圾收集器,它主要负责将已经没有存在意义的内存占用进行回收,以保证后续其他的内存使用,这里涉及到两点知识:

  1. 如何判断对象已经没有存在意义
  2. 如何进行内存回收

首先来看垃圾收集器是如何判断对象的生或死的,一般有两种方式判断一个对象是否应该继续存活:

  1. 引用计数算法,即有地方持有该对象的引用就在该对象的引用计数器上加1,反之,当引用失效时就减1;那么该引用计数器为0的对象就是不可能再被使用的
  2. 可达性分析算法,这个算法的基本思路是通过一系列称为「GC Roots」的对象作为起始点,从这些节点开始往下搜索,搜索所走过的路径称为引用链,当一个对象到「GC Roots」没有任何引用链相连(就是「GC Roots」到这个对象不可达)则证明此对象是不可用的,如下图,Object 5,6,7皆为不可达对象


    image.png

    在Java中作为「GC Roots」的有四类对象

Java使用的就是可达性分析算法,其实对于引用计数算法有一个比价明显的问题,就是对于两个或多个对象的循环引用,它是无能为力的;那么被可达性算法标记为不可达的对象是否就一定会被回收掉呢?其实也不是,要真正宣告一个对象的死亡,至少要经历两次标记过程:

  1. 如果对象在经过可达性分析发现没有与「GC Roots」相连,那么它将被第一次标记并进行一次筛选,筛选的条件是该对象是否有必要执行finalize()方法,如果有必要(如果重写了就需要执行)就执行,此时是可以在finalize()中将对象进行最后的挽救的(重新对其进行引用),但是不建议这样做,一是因为这种垂死挣扎的方式没必要,二是因为Java本身并不保证finalize()何时执行或一定能执行完成
  2. 经过第一步后,会再次对这些对象进行标记,若此时没有在finalize()中对对象进行重新引用,那么就可以标记为「确定死亡」了

最后说说方法区(或永久代)的回收,方法区的垃圾收集主要针对两部分内容:废弃常量和无用的类。回收废弃常量与回收Java堆中的对象很类似,以常量池中的字符串回收为例,如果字符串常量"abc"在当前的系统中没有一个对应的String对象对其进行引用,那么这个常量将允许被回收;而判断一个类是否是"无用的类"则较苛刻,它需要满足几个条件:

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

只有满足这三个条件,该类才允许被回收,但需要注意的是方法区的回收不是一定会执行的,而且JVM的规范里也没有强制性的要求说需要实现这一部分区域的回收机制,对于一些运用运行时代码生成技术如反射,动态代理,CGLib等这种频繁自定义ClassLoader的场景需要虚拟机具备类卸载的功能,以保证方法区不会溢出

垃圾收集算法

垃圾收集算法主要有下列几种:

  1. 标记-清除算法,正如它的名字一样算法分为"标记"和"清除"两个阶段,这个算法是最基础的收集算法,后续的收集算法都是基于这种思路并对其不足进行改进,它的效率不高,同时会产生大量不连续的内存碎片


    image.png
  2. 复制-收集算法,它将可用内存分为两块,每次使用其中一块,当这块内存用完了,就将还存活的对象复制到另外一块上面,然后把已使用的内存空间一次性清理掉;它的主要优点是不用考虑内存碎片的问题,但是也存在着实际使用内存变小的不足


    image.png
  3. 标记-整理算法,这种算法的过程与"标记-清除算法"类似,但后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存


    image.png
  4. 分代收集算法,这是当前主流JVM采用的算法,这种算法没有什么新思想,只是根据对象存活周期不同将内存划分为几块(前面已有介绍),在新生代中,每次垃圾收集时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量的复制成本就可以完成收集,而老年代中因为对象存活率高,没有额外空间进行分配担保,就采用"标记-清除算法"或"标记-整理算法"进行回收

最后,推荐两篇关于JVM内存相关性能调优的文章

上一篇 下一篇

猜你喜欢

热点阅读