深入理解JAVA虚拟机学习笔记(二)垃圾回收策略

简介:  上篇文章介绍了JVM内存模型的相关知识,其实还有些内容可以更深入的介绍下,比如运行时常量池的动态插入,直接内存等,后期抽空再完善下上篇博客,今天来介绍下JVM中的一些垃圾回收策略。        一、finailize()方法                           在介绍GC策略前,先介绍下GC中的finailize方法。

 上篇文章介绍了JVM内存模型的相关知识,其实还有些内容可以更深入的介绍下,比如运行时常量池的动态插入,直接内存等,后期抽空再完善下上篇博客,今天来介绍下JVM中的一些垃圾回收策略。

       一、finailize()方法              

            在介绍GC策略前,先介绍下GC中的finailize方法。当对象没有任何引用的时候,通常这个对象会被回收掉,但如果我们想在对象被回收前进行一些操作,比如关闭一些资源,或者让这个对象复活,不让他被回收怎么办?这时候就要用到finailize方法了。finailize方法是Object类中定义的方法,意味着任何一个对象都有这个方法。但这个方法只会调用一次,如果把这个对象复活后再次让这个对象死亡,那第2次回收该对象的时候是不会调用finailize方法的,而且优先级比较低,并不能保证一定会被执行,因此不建议使用finalize方法。总结起来就是3个特性: ①、GC之前被调用 。②、只会被调用一次。③、不可靠,不能保证被执行,不建议使用。关于finalize使用方法,参考如下代码:

复制代码
 1 public class FinalizeTest {
 2 
 3     private static FinalizeTest test;
 4     /**
 5      * VM参数:-XX: +PrintGCDetails -Xmx=1M -Xms=1M
 6      *
 7      * @param args
 8      */
 9     public static void main(String[] args) {
10         //先对test对象赋值
11         test = new FinalizeTest();
12         int _1m = 1024 * 1024;
13         //将test置为null,便于回收
14         test = null;
15         try {
16             System.gc();
17             //模拟睡眠5s,finalize优先级较低,保证finalize能执行
18             Thread.sleep(5000);
19         } catch (InterruptedException e) {
20             e.printStackTrace();
21         }
22         if (test != null) {
23             System.out.println("first,i am alive");
24         }else{
25             System.out.println("first,i am dead");
26         }
27         //由于test在finalize方法里复活了,再次将test置为null
28         test = null;
29         try {
30             System.gc();
31             Thread.sleep(5000);//模拟睡眠5s,让GC回收
32         } catch (InterruptedException e) {
33             e.printStackTrace();
34         }
35         if (test != null) {
36             System.out.println("second,i am alive");
37         }else{
38             System.out.println("second,i am dead");
39         }
40 
41     }
42     @Override
43     protected void finalize() throws Throwable {
44         test = this ;
45         System.out.println("finalize excuted");
46         super.finalize();   //调用父类的finailize方法
47     }
48 }
复制代码

         该代码运行结果如下:

         

         可以看到,finalize方法执行后,test对象又被重新激活了,因此打印了first,i am alive。但是第二次GC的时候,finalize方法并未被执行,因此打印了second,i am dead。前面提到finalize是优先级低不可靠的,那如果没有Thread.sleep(5000),再来看下代码和结果:

 
复制代码
 1 public class FinalizeTest {
 2 
 3     private static FinalizeTest test;
 4     /**
 5      * VM参数:-XX: +PrintGCDetails -Xmx=1M -Xms=1M
 6      *
 7      * @param args
 8      */
 9     public static void main(String[] args) {
10         //先对test对象赋值
11         test = new FinalizeTest();
12         int _1m = 1024 * 1024;
13         //将test置为null,便于回收
14         test = null;
15         try {
16             System.gc();
17             //模拟睡眠5s,finalize优先级较低,保证finalize能执行
18             //不执行睡眠操作,Thread.sleep(5000);
19         } catch (Exception e) {
20             e.printStackTrace();
21         }
22         if (test != null) {
23             System.out.println("first,i am alive");
24         }else{
25             System.out.println("first,i am dead");
26         }
27         //由于test在finalize方法里复活了,再次将test置为null
28         test = null;
29         try {
30             System.gc();
31             //不执行睡眠操作,Thread.sleep(5000);//模拟睡眠5s,让GC回收
32         } catch (Exception e) {
33             e.printStackTrace();
34         }
35         if (test != null) {
36             System.out.println("second,i am alive");
37         }else{
38             System.out.println("second,i am dead");
39         }
40 
41     }
42     @Override
43     protected void finalize() throws Throwable {
44         test = this ;
45         System.out.println("finalize excuted");
46         super.finalize();   //调用父类的finailize方法
47     }
48 }
复制代码
 

        运行结果如下:  

       

       这里可以很清楚地看到,finalize方法的优先级是比较低的。

       关于这个例子的反思:这个例子中第一段代码是参考《深入理解java虚拟机》里的代码实现的,但是总感觉有2点疑问:为什么test对象是以static修饰的成员变量方式存在?如果是static修饰,那就是存在方法区了,而方法区的GC通常效果不太好的。另一个是以成员变量的方式存在,这样finalize回收的时候,体现不出是对当前对象本身的回收,所以感觉这个例子并不是很好。

      二、引用计数法

      引用计数法是一种比较早的GC回收算法,目前一般不采用,其主要思想是:每个对象都维持一个引用计数器,初始值为0,当一个对象被引用的时候,该对象的引用计数器就加1,当不被引用的时候,该对象的引用计数器就减1,如果一个对象的引用计数器变为了0,则该对象被认为是可以回收的。采用这种方式的优缺点都很明显,优点是实现简单,效率高,缺点是可能存在循环引用,导致内存溢出。

     三、标记-清除法 

         标记-清除法按名字分为“标记”和“清除”2个阶段,其基本思想是:首先标记出所有存活的对象,标记完成后,统一清除所有需要被回收的对象。那怎么判断某个对象是可以回收的呢?GC时,从一系列GC Roots根节点开始遍历,遍历时走过的路径即称为引用链,如果一个对象和GC Roots没有任何引用链相关,那么这个对象就不可用,就会被判定为可回收,这种算法也叫根搜索算法那么哪些对象可以成为GC Roots对象呢?在java语言里,可以作为GC Roots的对象包括下面4种: 

            虚拟机栈中的引用变量 

            方法区中的类静态属性引用的对象 

           方法区中的常量引用的对象 

           本地方法栈中JNI(即native方法)的引用的对象

           标记-清除法的算法示意图如下:

  

     注:本文的GC回收算法图片转自一个网友的文章(点这里),该网友的图片内容也与原著一致,只是颜色不同。

     四、新生代的复制法

           复制法的基本思想是:将内存分为大小相等的2块,每次只使用其中一块,GC时每次将所有存活的对象复制到另一块区域,然后清理该内存

           这几种都是方法区和栈中的引用对象。复制法的优点是:实现简单,回收速度快,且不会产生内存碎片。但由于每次只使用其中一块,导致内存利用率较低。复制算法的示意图如下:

        

        现在的商业虚拟机都采用复制法来回收新生代,由于新生代的对象98%以上都是朝生夕死的,所以并不需要按照1:1来分配,而是将内存分为较大的Eden区和2块较小的Survivor区(通常Eden和Survivor区大小的比值为8:1:1,可以根据SurvivorRationJVM内存参数来设置比值),每次使用Eden区和其中一块Survivor区类分配对象,GC时,将Eden区和Survivor区中的存活对象复制到另一块Survivor区域,这样一来,内存利用率就高了,而且运行速度也很快。

       五、老年代的标记-整理法

           复制法在对象存活率较高时,回收效率就变低了,而在老年代中,大部分的对象都是存活期较高的对象,因此就不适宜采用复制法进行老年代的GC。根据老年代的特点,并结合标记-清除法的思路,于是提出了标记-整理法。其主要思路是:标记过程与标记-清除法一致,只是标记完成后,不直接对未存活进行清除,而是将所有存活的对象都向一端移动,然后清理掉端边界以外的所有内存区域。这种方法的优点是不会产生内存碎片。标记-整理法的算法示意图如下:        

    

目录
相关文章
|
3月前
|
存储 Java
Java学习笔记 List集合的定义、集合的遍历、迭代器的使用
Java学习笔记 List集合的定义、集合的遍历、迭代器的使用
|
25天前
|
存储 算法 Java
Java虚拟机(JVM)的内存管理与性能优化
本文深入探讨了Java虚拟机(JVM)的内存管理机制,包括堆、栈、方法区等关键区域的功能与作用。通过分析垃圾回收算法和调优策略,旨在帮助开发者理解如何有效提升Java应用的性能。文章采用通俗易懂的语言,结合具体实例,使读者能够轻松掌握复杂的内存管理概念,并应用于实际开发中。
|
10天前
|
Kubernetes 监控 Java
如何设置 Kubernetes的垃圾回收策略为定期
如何设置 Kubernetes的垃圾回收策略为定期
|
10天前
|
Kubernetes Java 调度
Kubernetes中的Pod垃圾回收策略是什么
Kubernetes中的Pod垃圾回收策略是什么
|
1月前
|
存储 算法 Java
深入理解Java虚拟机(JVM)及其优化策略
【10月更文挑战第10天】深入理解Java虚拟机(JVM)及其优化策略
41 1
|
2月前
|
存储 安全 Java
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(基础篇)
从Java环境的搭建到实际代码的编写,从基本用法的讲解到底层原理的剖析,深度解析Java基础知识。本文是《Java学习路线》专栏的起始文章,旨在提供一套完整的Java学习路线,覆盖Java基础知识、数据库、SSM/SpringBoot等框架、Redis/MQ等中间件、设计模式、架构设计、性能调优、源码解读、核心面试题等全面的知识点,并在未来不断更新和完善,帮助Java从业者在更短的时间内成长为高级开发。
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(基础篇)
|
2月前
|
存储 安全 Java
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(进阶篇)
本文是Java基础的进阶篇,对异常、集合、泛型、Java8新特性、I/O流等知识进行深入浅出的介绍,并附有对应的代码示例,重要的地方带有对性能、底层原理、源码的剖析。适合Java初学者。
Java修仙之路,十万字吐血整理全网最完整Java学习笔记(进阶篇)
|
2月前
|
存储 算法 Java
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
本文介绍了 JVM 的内存区域划分、类加载过程及垃圾回收机制。内存区域包括程序计数器、堆、栈和元数据区,每个区域存储不同类型的数据。类加载过程涉及加载、验证、准备、解析和初始化五个步骤。垃圾回收机制主要在堆内存进行,通过可达性分析识别垃圾对象,并采用标记-清除、复制和标记-整理等算法进行回收。此外,还介绍了 CMS 和 G1 等垃圾回收器的特点。
112 0
深入解析 Java 虚拟机:内存区域、类加载与垃圾回收机制
|
1月前
|
Java 数据安全/隐私保护
java学习笔记(基础习题)
java学习笔记(基础习题)
33 0
|
1月前
|
Java 程序员 开发工具
java学习笔记
java学习笔记
35 0