Java基础四之垃圾回收机制总结

Java中的垃圾回收机制

目录:

  • 垃圾回收机制概念

  • Java中垃圾判定与回收托管特征

  • Java的内存泄漏

  • finalize方法

  • 对象复活隐患

  • 强、软、弱、虚引用

  • 不同类型引用类型的垃圾回收特征

  • 引用队列

  • 实现对象内存缓存的方法

  • 常见MemeryCache工具介绍

  • 垃圾回收机制的概念(补充内容在末尾)

    • JVM架构中,堆内存和垃圾回收器与垃圾回收相关。

      • 堆内存:运行时用来存储实例对象的数据空间
      • 垃圾回收器运行在堆内存上
    • Java内存模型中,主要是了解堆内存的概念。运行时的java实例对象存储在堆内存空间。当一个对象不再被引用,就变成可被从堆内存回收空间。垃圾回收过程中,这些对象被从堆内存中清除,然后空间也被回收

    • Java堆内存中的对象分代存储(依据对象本身的存活时限)

      • 年轻代 eden(伊甸园) S0 S1
        • a-Eden区:所有实例在运行时最初都被分配在堆内存空间的eden区中
        • b-S0 :老一些的对象被从eden区移动到S0区,其实是eden区中对象经过一次对eden区的Young GC还存活的对象被移动到S0
        • c-S1:再老一些的对象被从S0区移动到S1区,其实是Young GC过程中S0区已满,则会将eden区还存活的对象和S0区还存活的对象移动到S1区。
      • 老年代 tenured(年老代)
        • 经过S0、S1几轮迭代后还存活的对象被提升到老年代
      • Major GC:java垃圾回收过程中实例生命周期的最后一个阶段。会扫描属于老年代部分的堆内存。如果实例没有被任何引用关联,将会被标记-清除;如果有关联,则保留。
      • 永久代 permanent java8已经把永久代去除=>元数据区。
        • 包含一些元数据类,方法等等
        • 永久代空间在JDK8中已经被移除。
      • 结论:生存时限越长的对象,被垃圾回收处理机制扫描的频率越低。
      • Fragmentation:碎片
        • 在GC过程中,一旦实例被堆内存删除,这些空闲空间很容易在内存空间产生碎片。所以需要对内存做去碎片化操作。根据不同垃圾回收器策略,被回收的内存将在回收过程或GC另外独立的过程中压缩整合。
    • Java垃圾回收

      • 概念:一个自动运行的管理程序运行时使用的内存的进程。通过GC的自动执行将程序员从申请和释放内存的繁重操作中解放出来。
      • 但是:虽然作为自动执行的进程,程序员不需要在代码中主动初始化GC。java还是提供System.gc()和Runtime.gc()两个hook(钩子)来请求GC进程。
      • 但是:尽管java给程序员提供调用GC的机会,实际上这是由JVM负责决定的,JVM基于内存堆空间的eden区的使用情况作决定。JVM可以选择拒绝启动GC的请求。因此并不保证这些请求会真的调用垃圾回收。
    • Java四种类型的垃圾回收器(GC)

      • 1.Serial Garbage Collector
        • 串行垃圾回收器控制所有的应用线程。
        • 为单线程场景设计,只使用一个线程执行垃圾回收工作。暂停所有应用线程来执行垃圾回收工作的方式不适用于服务器的应用场景。最适用的是简单的命令行程序。
        • 使用-XX:+UseSerialGC JVM参数来开启使用串行垃圾回收器
      • 2.Parallel Garbage Collector
        • 并行垃圾回收器,也被称为基于吞吐量的回收期。是JVM的默认垃圾回收器。
        • 与Serial不同,使用多个线程来执行垃圾回收工作。与SerialGC一样,执行垃圾回收工作时需要暂停所有的应用程序。
      • 3.CMS Garbage Collector
        • 并发标记清除(Concurrent Mark Sweep)垃圾回收器
        • 使用多个线程来扫描堆内存并标记可被清除的对象,然后清除标记的对象。只在下面给两种情形下暂停工作线程:
          • 在老年代中标记引用对象的时候
          • 在做垃圾回收的过程中堆内存有变化发生。
        • 与并发GC相比,CMSGC使用更多的CPU保证更高的吞吐量。如果我们有更多的CPU用来提升性能,那么CMS比并发GC更好
        • 使用-XX:+UseParNewGC JVM参数开启使用CMSGC
      • 4.G1 Garbage Collector
        • G1垃圾回收器应用于大的堆内存空间。将堆内存空间划分为不同的区域,对各区域并行地做回收工作。G1回收内存空间后还立即对堆空闲空间做整合工作减少碎片。CMS则是在全部停止时执行内存整合工作。对不同区域G1根据垃圾数量决定优先级。
        • 使用-XX:UseG1GC JVM参数开启使用
        • 使用G1垃圾回收器时:开启使用-XX:+UseStringDeduplacationJVM参数。会将重复的String值移动到同一char[]数组来优化堆内存占用。
      • 每种GC都有各自的优点和缺点,在不同的应用场景下的效率会有很大差异。我们可以选择JVM使用哪种类型的垃圾回收器。通过传递不同的JVM参数来设置使用哪一个。选择取决于应用场景、硬件配置、和吞吐量要求。
    • 与Java垃圾回收相关的JVM选项:

      	JVM运行参数			描述
      -XX:+UseSerialGC 	使用串行GC
      -XX:+UseParallelGC 	使用并行GC
      -XX:+ParallelGCThread= 	并行GC使用的线程数
      -XX:+UseParNewGC 	使用CMSGC
      -XX:+UseConcMarkSweepGC 	缩短mjor收集的时间
      -XX:+UseG1GC 	使用G1GC
      
      -Xms				初始堆内存大小
      -Xmx				最大堆内存大小
      -Xmn				年轻代的大小
      -XX:PermSize		初始永久代的大小
      -XX:MaxPermSize 	最大的永久代大小
      
      JVM运行参数实例:
      java -Xmx12m -Xms3m -Xmn1m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar java-application.jar
      表示最大堆内存为12m 初始堆内存为3m 年轻代大小为1m 初始永久代为20m 最大永久代带为20m
      
  • Java中的垃圾判定与回收托管特征

    • GC处理对象回收操作,对象回收触发:

      • 对象没有引用
      • 作用域发生未捕捉异常
      • 程序在作用域正常执行完毕
      • 程序执行System.exit()
      • 程序发生意外终止(被杀进程等)
    • 了解JDK1.2之前的引用计数器算法:类加载到内存,产生方法区,堆栈,程序计数器等,创建对象的时候,分配堆栈空间给它,同时产生一个引用计数器,同时计数器+1,有新的引用继续+1,引用销毁-1.计数器为零,标志无引用,可被回收。

      • JDK1.2引用计数器的问题:

        objA.obj=objB;
        objB.obj=objA;//内存泄漏
        //objA指向objB,而objB又指向objA,即使所有引用都消失,objA和objB还有相互的引用,也就是两个对象的引用计数器各自为1,实际上两个对象都没有额外的引用,已经是垃圾了。
        
        这种情形下,引用计数器无法适应。
        
    • 了解 根搜索算法 :即程序把所有的引用关系看作一张有向图,从一个节点GC ROOT开始,寻找对应引用节点,找到这个节点以后,继续寻找该节点的引用节点,当所有的引用节点寻找完毕后,剩余节点则被认为是没有被引用的节点。

      • java中可作为GC ROOT的对象有
        • 虚拟机栈中引用的对象(本地变量表)
        • 方法区中静态属性引用的对象
        • 方法区中常量引用的对象
        • 本地方法栈中引用的对象(Native对象)
  • Java的内存泄露

    • 内存泄漏:对象已经没有被应用程序使用,但是垃圾回收器没办法移除它们,因为还被引用着。
    • 无用对象:未被引用对象、以及被引用对象(这个就是A含有B的引用,但是A的生命周期比B长,所以B用完后可以被回收,但A仍然有B的引用,所以不能回收B)
    • 注意!!!帮助开发人员防止内存泄漏的发生:
      1. 不再使用的对象将其引用置空指向null
      2. 集合特别注意HashMap、ArrayList的使用
      3. 集合,当原有对象属性发生改变(hashCode变化),remove方法可能失效,导致内存泄漏
      4. 特别注意系统各种事件监听和回调。当监听器在使用的时候被注册,但不在使用之后却并没有反注册
      5. 如果一个类自己管理内存,通常成员变量引用其他对象,初始化的时候置空。
        • subString方法。
  • finallize方法

    • 提供对象被回收时调用以释放资源,默认情况不执行任何动作
    • 如果必须重写finalize方法,请记住使用super.finalize()调用父类清除方法,否则对象清理过程可能不完整。
    • 每个对象只能被GC自动调用finalize方法一次。如果finalize执行时产生异常,则该对象仍被GC收集。
    • Java语言允许为任何方法添加finalize。不要过分依赖该方法对系统资源进行回收和再利用。
    • finalize()方法尚未被调用,System.runFinalization()方法可以用来调用finalize()方法。
  • 对象复活隐患

    • 谨慎对待finalize方法:因为还有可能阻断垃圾回收器对本对象的回收:称为对象复活,造成逻辑混乱和内存泄漏。

    • 垃圾回收器。。。。

    • 对象只能调用finalize方法,所以对象只能复活一次。

      //对象复活隐患
      public class ReviveByFinalize{
        public static ReviveByFinalize caseForRevive;
        protected void finalize() throws Throwable{
          	super.finalize();//调用父类清除方法
          	System.out.println("满血复活");
          	caseForRevive=this;//这里重新引用本对象,对象复活
          	System.out.println("关闭资源");
        }
      }
      
      public class ReviveTest{
        public static void main(String[] args)throws Exception{
          System.out.println(ReviveByFinalize.caseForRevive);
          ReviveByFinalize.caseForRevive =new ReviveByFinalize();
          System.out.println(ReviveByFinalize.caseForRevive);
          ReviveByFinalize.caseForRevive=null;//取消对对象的引用,使其成为垃圾
          System.gc();
          Thread.sleep(1000);
         //垃圾回收后再次输出再次回收。 System.out.println(ReviveByFinalize.caseForRevive);
          ReviveByFinalize.caseForRevive=null;
          System.gc();
        }
      }
      

  • 强引用、软引用、弱引用及虚引用

    引用类型	垃圾回收时机			用途			生存时间
    强引用		 从来不会	 		对象的一般状态 JVM停止运行时终止
    软引用		 JVM虚拟机内存不足时 对象缓存		内存不足时终止
    弱引用		垃圾回收机制发现		对象缓存	GC运行后终止
    虚引用		unknown		监控对象回收		unknow
    
    • 强引用

      ClassName object=new ClassName();
      我们使用的大部分引用实际上都是强引用。
      如果一个对象具有强引用,那么GC绝不会回收它,内存不足时JVM宁愿抛出OutOfMemoryError错误,让程序异常终止,也不会随意回收该类对象。
      
    • 其他引用类型的生命周期:

      ​ 软引用:内存不足时失效

      ​ 弱引用:GC后失效

      ​ 虚引用:unkown

    • 软引用

      String str=new String("abc");
      SoftReference<String> softRef=new SoftReference<String>(str);
      
    • 弱引用

      String str=new String("abc");
      WeakReference<String> weakRef=new weakReference<String>(str);
      
    • 虚引用

      //虚引用示例
      String str=new String("abc");
      ReferenceQueue<String> queue=new ReferenceQueue<String>();//创建引用队列对象。
      PhantomReference<String> phantomRef=new PhantomReference<>(str,queue)//这里绑定引用队列
      虚引用特点:绑定引用队列后市finalize方法的理想替代品,一旦虚引用被加入引用队列,就没有任何办法获取虚引用指向的对象,防止了对象复活的隐患。
      
      • 主要用来跟踪对象被垃圾回收的活动。
      • 与软引用和弱引用的区别:虚引用必须和引用队列联合使用。当垃圾回收器准备回收一个对象,如果发现它还有虚引用,就会在回收对象之前,把虚引用加入到与之关联的引用队列中。而程序可以通过判断引用队列是否已经加入虚引用,来了解该对象是否将被垃圾回收。如果发现某虚引用已经被加入到引用队列,那么就可以在所引用对象的内存被回收之前采取必要的行动。
  • 不同类型引用类型的垃圾回收特征

    • 强引用不会被回收、软引用在内存不足时会被回收,弱引用在GC后会被回收、虚引用迷之不知道什么时候就会被回收。
  • 引用队列

    • 软引用、弱引用、虚引用都可以和一个引用队列绑定使用
    • ReferenceQueue:作为JVM GC与上层Reference对象管理之间的一个消息传递方式,是我们对所监听的对象引用可达发生变化时做一些处理。当一个obj被gc掉后,其相应的引用对象(软、弱、虚),即ref对象会被放入queue中,然后我们可以从queue中获取相应对象信息,同时进行额外的处理。比如反向操作,数据清理。
    • 例如:我们希望当一个对象被gc掉的时候通知用户线程,进行额外处理时。
    • 引用队列实现:一个队列的入队和出对,内部元素Reference自身的链表结构实现的就是泛型的Reference
      • 引用队列入队操作由垃圾回收器完成,当发现回收的对象具备软、弱、虚引用,会自动将对象的引用对象入队
      • 我们只在必要时执行出队操作即可监控到那些对象被回收并执行相关的资源操作。
  • 实现对象内存缓存的方法

    • 好的缓存不是存的东西越多越好
      • 无限向缓冲写入大量数据会导致OOM,例如直接使用列表类型保存对象数据
      • 内存敏感的缓存应该在虚拟机内存不足时能够自动清除缓存中不常用的对象以保证应用的稳定性。
    • 引用类型刚好满足我们对内存敏感缓存的需要
      • 软引用适用于保存数据相对较多,对象声明周期相对较长的缓存
      • 弱引用适用数据交换较为频繁的高速缓存体系。JDK为我们提供WeakHashtable实现这种功能。
    • 利用软引用实现内存缓存
  • 常见的MemeryCache工具介绍

    • EhCache
      • 广泛使用的开源Java分布式缓存。主要面向通用缓存,JavaEE和轻量级容器。
      • 具有内存和磁盘存储、缓存加载器、缓存扩展、缓存异常处理程序、一个gzip缓存servlet过滤器、支持REST和SOAP api等特点
    • Redis
    • memcached
  • 补充:

  • JVM内存划分 这里与多线程也是有关系的

    -每个线程私有

    • 程序计数器:
      1. 线程私有
      2. 一块小的内存空间:用于存储当前线程正在执行的Java方法的JVM指令地址(字节码的行号)
      3. 如果执行Native方法,则计数器为空
      4. JVM中唯一没有规定任何OOM情况的内存区域
    • Java虚拟机栈
      1. 线程私有 每个线程创建的时候都会创建一个JVM栈 生命周期与线程一致
      2. 存储局部变量表,编译器可知的各种基本数据类型、对象引用、方法出口等信息。
      3. JVM栈中保持一个个的栈帧:每次方法调用会进行压栈。且对栈帧只有出栈和压栈两种操作。方法调用结束会进行出栈操作。
    • 本地方法栈
      1. 线程私有 每个线程都有一个本地方法栈
      2. 类似JVM栈,不过是在调用本地方法时使用的栈

    -所有线程公有

      1. 线程共享
      2. 几乎所有被创建的Java对象实例都被直接分配到堆上。
    • 方法区
      1. 线程共享
      2. 存储被JVM加载的元数据(类信息、常量、静态变量、等)
      3. jdk8前被称为永久代,现在被移除,并增加元数据区
    • 运行时常量池
      1. 方法区一部分,受方法区内存限制
        1. 扩展:每个Class文件头4个字节(Magic Number):作用:确定是否是一个可被JVM接受的文件;接着四个字节存储的是Class文件的版本号。紧接版本号的就是常量池的入口:
        2. 主要存放两大类常量:
          1. 字面量:如字符串、final常量
          2. 符号引用:存放与编译相关的一些常量。
      2. 静态常量池:class文件中的常量池
      3. JVM完成类加载操作后,会将静态常量池加载到内存中,存放在运行时常量池
    • 直接内存(以及堆外内存)
      1. 线程共享
      2. 其他内存:Java的NIO可以使用Native方法直接在java堆外分配内存,使用DirectByteBuffer对象作为堆外内存的引用
  • OOM可能发生的区域

    • 除程序计数器外的都有可能发生OOM
  • 堆内存结构分析

    • 借助工具来了解JVM的内存内容,具体到特定的内存区域
      • 图形化工具:
        • 优点:直观,连接到Java进程后,可以显示堆内存、堆外内存的使用情况,类似的工具有JConsole、VisualVm等
      • 命令行工具:
        • 可以在运行时进行查询,包括jstat、jmap等,可对堆内存、方法区等进行查看。使用Eclipse MAT进行分析,也可以使用jhap分析。
          • jmap:可以生成堆转储文件
  • ReferenceQueue:队列,其实不然,内部并非有实际的存储结构。理解为一个链表结构,节点就是reference本身,而ReferenceQueue相当于存一个当前的head节点,后面的reference节点自己通过next保持。head节点:当前队列中最新要被处理的节点。

  • 常用的性能监控与问题定位工具有?

    • 系统性能分析中
      1. 关注点:
        • CPU、
          • top命令
        • 内存、
        • IO
    • 性能监控与问题定位工具
      • CPU监控
        1. top命令查看
      • 这里有一张图自行去搞定这个
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值