No.4 Java的内存回收(内存回收)

1. Java引用的种类

 内存管理分为:内存分配和内存回收。都是由JVM自动处理的

  • 对象在内存中的状态:可达、可恢复(回收前调用finalize方法)、不可达

    • JVM回收标准:是否还有引用变量引用该对象
    • 有向图理解。线程对象作为根节点,变量、对象作为节点,引用关系作为有向边。在有向图中,从线程节点<当然线程对象也要存在,没有被销毁>可达的对象都是可达状态。
  • 强引用
    • 一般的引用/大部分 都是强引用,被强引用的对象不会被回收,是内存泄露的主要原因之一。
  • 软引用
    • 通过SoftReference类实现(该类的用法)
    • 只有软引用的对象,当系统内存充足时不会被回收,当内存紧张时会被回收
    • 可以用来解决系统内存紧张的难题
  • 弱引用
    • 字符串常量会被系统缓存(会使用强引用来引用),系统不会回收被缓存的字符串常量。
    • 当垃圾回收机制运行(具有不确定性)时,不管系统内存是否紧张,都会被回收(不确定性)。
    • WeakReference类,WeakHashMap更常用
  • 虚引用
    • 主要用于跟踪对象被垃圾回收的状态:程序可以通过检查与虚引用关联的引用队列中是否已经包含指定的虚引用,从而了解虚引用所引用的对象是否即将被回收。
    • 不能单独使用,需要和引用队列联合使用
      • 软引用和弱引用与引用队列联合使用,系统回收被引用对象之后,将会把被回收对象的对应的引用添加到关联的引用队列中去。
      • 而虚引用与引用队列联合使用的时候,在对象被释放之前,将把引用它的虚引用添加到引用队列中去,这使得可以在对象被回收之前采取行动。
    • 无法通过虚引用获取它所引用的对象

2. Java的内存泄露

  • 不在使用的内存却没有被回收,就是内存泄露
  • Java中,可达状态但是不再使用的内存,会引起内存泄露(垃圾回收机制不会回收可达状态的内存)
  • eg:ArrayList中的remove方法elementData[--size] = null;

3. 垃圾回收机制

  • 两件事情:回收不可达对象;清理内存分配、回收过程中产生的内存碎片(不连续的内存空间)
  • 垃圾回收的基本算法
    • 串行回收和并行回收
    • 并发执行和应用程序停止
    • 压缩、不压缩、复制
      • 复制、标记清楚、标记压缩
  • 堆内存的分代回收(堆内存被分为三个代来存放对象)
    • 依据:对象生存时间的长短,然后根据不同代采用不同的垃圾回收策略,充分发挥各自的优势。

      • 基于如下两点事实:①绝大多数对象不会被长时间引用,Young代就会被回收;②很老的对象和很新的对象之间很少存在相互引用的情况
    • Young代:复制算法;可达状态的对象较少,复制成本不大
      • 1个Eden区和2个Survivor区:对象先被分配到Eden区(一些大的对象可能直接分配到Old代),Survivor区中的对象至少经历一次垃圾回收。一次复制算法会将Eden区和1个Survivor区中的可达对象复制到另一个空的Survivor区中,然后进入下一个循环,即两个Survivor区同时间有一个是用来保存对象,另一个是空的
    • Old代
      • Young代多次回收仍然存在的对象会进入到Old代中,其中对象的特点:不容易死;随时间的流逝,其中的对象会越来越多,因此Old代的空间成本要比Young代大
      • 回收特点:垃圾回收的执行频率无需太高,因为死去的对象较少;每次执行需要花费更多的时间完成
      • 标记压缩算法,不会大量地产生内存碎片
    • Permanent代
      • 主要用于装载Class、方法等信息,垃圾回收机制通常不会回收该代中的对象
    • 次要回收:当Young代的内存将要用完的时候,垃圾回收机制会对该代进行垃圾回收,回收频率较高,系统开销小。
    • 主要回收:当Old代的内存将要用完的时候,垃圾回收机制会进行全回收,即对Young代和Old代都要进行回收,此时回收成本较大,因此成为主要回收
    • 通常来说,Young代先被回收,Old代的回收频率要低的多;内存压缩时,每个代都独立的进行压缩
  • 常见垃圾回收器
    • 串行回收器

      • Young代:串行复制算法
      • Old代:串行标记压缩算法,三个阶段:mark、sweep、compact(压缩阶段,执行sliding compaction,将活动对象往Old代的前端移动,尾部保留连续的空间)
    • 并行回收器
      • Young:与串行回收器基本相似,增加多CPU并行的能力,即启动了多个线程来并行回收(并不是与主线程并发,而是多个回收线程并行执行
      • Old:与串行完全相同
    • 并行压缩回收器(将取代并行回收器)
      • Young:与并行完全相同
      • Old:将Old代分成几个固定大小的区域
        • mark:多个回收线程并行标记可达对象(会更新可达对象所在区域的大小及其位置信息)
        • summary:操作Old代区域(而不是单个对象)。从最左边检验区域密度,当某区域的密度达到某个数值时,判定该区域及其右边区域应该回收(进行压缩及回收空间),其左边区域标识为密集区域(不会将新对象移到这里,也不会对该区域进行压缩)。该阶段:串行实现
        • compact:利用上阶段生成的数据识别出需要装填的区域,多个回收线程并行地将数据复制到该区域中。该阶段后,Old代一端密集存储大量的活动对象,另一端则为大块的空闲块。
    • 并发标识-清理 回收器(CMS),适用于实时性要求较高的程序,对Old代回收:并发执行,程序仅仅需要两次很短的暂停
      • Young:与并行回收器完全相同,依然会导致应用程序暂停,
      • Old:并发操作
        • mark:

          • 垃圾回收开始时,短暂的暂停,标识直接引用的可达对象(initial mark);
          • 并发标识阶段(concurrent marking phase),依据初始标识中发现的可达对象来寻找其他可达对象;
          • 再标记阶段(remark),因为并发标识过程中 应用程序可能会重新产生可达对象,为避免漏掉这些,需要再次很短的暂停下,多线程并行地再标识
        • concurrent sweep:并发清理
      • 默认在Old代68%满时就开始回收,因为是并行操作的,如果等到Old代满了,应用程序就没有可用内存了;而其他回收器回收时,不是并行操作的,在回收时,程序会暂停,应用程序不产生新的对象。
      • CMS不会对Old代进行内存压缩,即它的可用空间是不连续的,保存了一份可用空间列表,分配内存效率下降。
      • CMS需要更大的堆内存,因为:并发标识时,此时应用程序也在分配对象,Old代会同时增长;在标识阶段成为垃圾的对象并不能立即被回收,只有等到并行清理阶段时才被回收。(mine:串行,mark之后执行sweep,之间程序暂停,不会再产生新的对象;并发,mark(含有并发mark)之后并发sweep,程序并发执行,仍然可能产生新的对象,待清理的对象和新产生的对象在一起自然要大一点的内存。)(总之,因为并行,可能在清理的时候又产生新的对象,所以需要更大点的内存;而串行时,清理的时候不会产生新的对象,清理完毕后才会产生新的对象,需要的内存没有这么大)
      • 可执行附加选项强制回收permanent代的内存
      • 串行回收和并发回收对比(mine)
        • 串行:应用程序暂停——>回收器执行回收阶段(mark——>....——>sweep...)——>程序继续执行
        • 并发:应用程序短暂暂停1——>CMS执行initial mark(标记直接引用的可达对象)——>程序执行,CMS并发标记concurrent mark(标记通过初始标识标识的可达对象能寻找到的其他可达对象)——>程序短暂暂停2——>remark(标记并发标记时程序新产生的可达对象)——>程序继续执行,并发清理<此时才真正开始清理操作,之前都是标识操作>

4. 内存管理的小技巧

  • 尽量使用直接量

    • 使用字符串以及Byte、Short、Interger...等包装类时,直接使用直接量创建,而不用new 关键字new新的对象
  • 进行字符串拼接时使用StringBuilder/StringBuffer,而不是String(String不可变,用其拼接时会产生大量的临时字符串)
  • 尽早释放无用对象的引用
    • 一般情况下局部变量的作用时间比较段,对其引用值无需 = null;但是若是其所在的方法中执行了耗内存、费时的操作时,应该将对无用对象的引用变量的值赋值null,尽早释放无用对象
  • 尽量少用静态变量,尤其是静态变量引用对象。因为静态变量属于Class类对象,类对象在permanent代中(它的类变量自然也在该代中),其存在时间很长
  • 避免在经常调用的方法、循环中创建对象。
    • 导致不断的为对象分配(创建)、回收(销毁)内存,这些操作都是影响性能的
  • 缓存经常使用的对象(eg:数据库连接池)
    • 避免不断的分配、回收操作
    • 方法:
      • 使用HashMap:控制容器中的key-value对不能太多,否则HashMap占用过大的内存将导致性能下降
      • 使用开源缓存项目
    • 缓存设计:
      • 牺牲空间来换取时间,都是使用容器保存已用过的对象。如何控制容器占用的内存,又保留大部分已用过的对象,是程序设计的关键。(一些缓存算法)
  • 尽量不使用finalize方法
    • 垃圾回收器回收资源前会调用该方法。
    • 回收机制本身已经教严重制约应用程序的性能
    • 本身回收机制的负担就比较重,而且回收Young代内存会导致程序暂停,影响性能;再在finalize方法中执行资源清理会加重回收机制的负担,导致运行效率更差
  • 考虑使用SoftReference
    • 创建长度很大的数组时,考虑使用软引用进行包装数组元素(内存够时引用,不足时释放)
    • 软引用具有不确定性,当获取引用对象时,需要显示判断对象是否为空(避免出现异常),若为空,应该重新创建。
  • 吼吼吼
时间: 2024-10-09 13:56:03

No.4 Java的内存回收(内存回收)的相关文章

Java深入 - Java 内存分配和回收机制-转

Java的GC机制是自动进行的,和c语言有些区别需要程序员自己保证内存的使用和回收. Java的内存分配和回收也主要在Java的堆上进行的,Java的堆中存储了大量的对象实例,所以Java的堆也叫GC堆. Java在垃圾收集的过程中,主要用到了分代收集算法,我会先讲一下常用垃圾收集算法. 常用垃圾收集算法 1. 标记-清除算法 这种垃圾收集算法思路非常简单,主要是首先标记出所有需要回收的对象,然后回收所有需要回收的对象. 但是有一个明显的缺点,采用这种算法之后会发现内存块回收之后就不连续了,这就

二、Java如何分配和回收内存?Java垃圾收集器如何工作?

线程私有的内存区域随用户线程的结束而回收,内存分配编译期已确定,内存分配和回收具有确定性.共享线程随虚拟机的启动.结束而建立和销毁,在运行期进行动态分配.垃圾收集器主要对共享内存区域(堆和方法区)进行垃圾收集回收. Java如何实现内存动态分配和内存垃圾的回收? 1.哪些内存需要回收(垃圾收集器内存回收的对象)?已经"死亡"的对象,那如何判定对象已经"死亡"了? Java堆回收的内存:已经"死亡"的对象 方法区回收的内存:废弃的常量和无用的类 2

转!!Java虚拟机堆的内存分配和回收

Java内存分配和回收,主要就是指java堆的内存分配和回收.java堆一般分为2个大的区域,一块是新生代,一块是老年代.在新生代中又划分了3块区域,一块eden区域,两块surviver区域.一般称为from surviver和to surviver.这些区域的大小可以自己指定.比如:(-Xms20M 表示可用堆内存大小:-Xmx40M 表示最大堆内存,在堆内存大小不够时,会扩展到最大堆内存:-Xmn10M 表示新生代内存大小). 新生代中的对象会在eden区域分配,然后eden区域的内存不够

浅谈java内存分配和回收策略

一.导论 java技术体系中所提到的内存自动化管理归根结底就是内存的分配与回收两个问题,之前已经和大家谈过java回收的相关知识,今天来和大家聊聊java对象的在内存中的分配.通俗的讲,对象的内存分配就是在堆上的分配,对象主要分配在新生代的Eden上(关于对象在内存上的分代在垃圾回收中会补上,想了解的也可以参考<深入理解java虚拟机>),如果启动了本地线程分配缓冲,讲按线程优先在TLAB上分配.少数情况下也是直接在老年代中分配. 二.经典的分配策略 1.对象优先在Eden上分配 一般情况下对

JAVA虚拟机内存分配与回收机制

Java虚拟机(Java Virtual Machine) 简称JVM Java虚拟机是一个想象中的机器,在实际的计算机上通过软件模拟来实现.Java虚拟机有自己想象中的硬件,如处理器.堆栈.寄存器等,还具有相应的指令系统. Java把内存划分成两种:一种是栈内存,一种是堆内存. 在函数中定义的一些基本类型的变量和对象的引用变量都在函数的栈内存中分配. 当在一段代码块定义一个变量时,Java就在栈中为这个变量分配内存空间,当超过变量的作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存

Java内存与垃圾回收调优

本文由 ImportNew - 进林 翻译自 journaldev.欢迎加入翻译小组.转载请参见文章末尾的要求. 要了解Java垃圾收集机制,先理解JVM内存模式是非常重要的.今天我们将会了解JVM内存的各个部分.如何监控以及垃圾收集调优. Java(JVM)内存模型 正如你从上面的图片看到的,JVM内存被分成多个独立的部分.广泛地说,JVM堆内存被分为两部分--年轻代(Young Generation)和老年代(Old Generation). 年轻代 年轻代是所有新对象产生的地方.当年轻代内

java虚拟机(3)--内存分配与回收策略

三.内存分配与回收策略 1.1 Minor GC 和 Full GC Minor GC:发生在新生代上,因为新生代对象存活时间很短,因此 Minor GC 会频繁执行,执行的速度一般也会比较快. Full GC:发生在老年代上,老年代对象其存活时间长,因此 Full GC 很少执行,执行速度会比 Minor GC 慢很多. 1.2 内存分配策略 1.2.1            对象优先在 Eden 分配 大多数情况下,对象在新生代 Eden 区分配,当 Eden 区空间不够时,发起 Minor

最简单例子图解JVM内存分配和回收

一.简介 JVM采用分代垃圾回收.在JVM的内存空间中把堆空间分为年老代和年轻代.将大量(据说是90%以上)创建了没多久就会消亡的对象存储在年轻代,而年老代中存放生命周期长久的实例对象.年轻代中又被分为Eden区(圣经中的伊甸园).和两个Survivor区.新的对象分配是首先放在Eden区,Survivor区作为Eden区和Old区的缓冲,在Survivor区的对象经历若干次收集仍然存活的,就会被转移到年老区. 简单讲,就是生命期短的对象放在一起,将少数生命期长的对象放在一起,分别采用不同的回收

Lua的内存监测和回收

Lua内存是自动收集的, 这点跟Java类似, 不被任何对象或全局变量引用的数据,将被首先标记为回收,不需要开发者做任何事情.但是,正如Java也会有内存泄露一样, Lua也会有, 只不过,跟C++的不同,它是由于代码执行所装载的资源,并没有被彻底销毁而导致,其中,最臭名昭著的就是不小心把局部变量声明成了全局变量(忘了加local修饰符). 类似这样造成的内存泄露, 跟任何其他语言的内存泄露一样,容易产生,却难以察觉, 给开发的应用带来潜在的很大隐患. 那么, 有没有一些有效的解决办法, 来解决

【深入理解JVM】:内存分配与回收策略

Java技术体系中所提倡的自动内存管理最终可以归结为自动化地解决了两个问题:给对象分配内存以及回收分配给对象的内存. 对象的内存分配,往大方向讲,就是在堆上分配,对象主要分配在新生代的Eden区上,如果启动了本地线程分配缓冲,将按线程优先在TLAB上分配.少数情况下也可能会直接分配在老年代中,分配的规则并不是百分之百固定的,其细节取决于当前使用的是哪一种垃圾收集器组合,还有虚拟机中与内存相关的参数的设置. 本文中的内存分配策略指的是Serial / Serial Old收集器下(ParNew /