Java刷题知识点之垃圾回收算法过程、哪些内存需要回收、被标记需要清除对象的自我救赎、对象将根据存活的时间被分为:年轻代、年老代(Old Generation)、永久代、垃圾回收器的分类

简介:

哪些内存需要回收

  其实,一般是对堆内存而言的。

 

 

垃圾回收算法过程

  在Java语言中,GC(Garbage Collection)是一个非常重要的概念。它主要是回收程序中不再使用的内存。

  对对象而言,如果没有任何变量去引用它,那么该对象将不可能被程序访问,因此可以认为它是垃圾信息,可被回收。只要有一个以上的变量引用该对象,该对象就不会被垃圾回收。

复制代码
public class Test {
    public static void main(String a) {
    Integer i1 = new Integer (1);
    Integer i2 = new Integer (2);
    i2 = i1;
   }
}
复制代码

  资源i2所占的内存是不可达的。

 

 

(1)引用计数法

  原理:为每个对象配一个计数器,如果这个对象被引用,则计数器加1,计数器为0,则表示该对象没有任何引用指向它。

  缺点:不可达的对象出现循环引用,它的引用计数器均不为0。

 

 

(2)标记清除算法

  原理:先通过根节点标记所有可达对象,然后清除所有不可达对象,完成垃圾回收。

  缺点:会造成空间碎片。  

    ①  标记出所有需要回收的对象。

    ②  标记完成后,统一回收所有被标记的对象。

  缺点是:

    ①:标记和收集的两个过程效率都不高。

    ②:标记清除后会产生大量的不连续的内存空间,空间碎片多了以后就无法分配大块的内存空间给大的对象使用。

 

 

 

 

 

 

  (3)复制回收算法

  原理:将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。

  把堆内存分成两个大小相同的区域,在任何时刻,只有其中的一个区域被使用,直到这个区域的被消耗完为止,此时垃圾回收器会中断程序的执行,通过遍历的方式把所有活动的对象复制到另外一个区域中,在复制的过程中它们是紧挨着布置的,从而可以消耗内存碎片。当复制过程结束后程序会紧着运行,直到这块区域被使用完,然后采取上面的方法继续进行垃圾回收。

  优点:可确保回收后的内存空间是没有空间碎片的。同时,在进行垃圾回收的同时对对象的布置也进行了安排,从而消耗了内存碎片。

  缺点:将系统内存折半。对于指定大小的堆来说,需要两倍大小的内存空间,同时由于在内存调整的过程中要中断当前执行的程序,从而降低了程序的执行效率。

  复制算法适合用于新生代,因为新生代存活对象少,垃圾对象多。因为在新生代,垃圾对象通常会多于存活对象,复制算法的效果会比较好。

  

 

 

 

 

 

 

 

 

  (4)标记压缩回收算法

  标记压缩算法是一种老年代的回收算法,老年代大部分都是存活对象。

  它对所有可达对象做了一次标记,然后,将堆中所有的存活对象压缩到堆内存的一端,这样就会在堆中另外一端留出很大的一块空闲区域,之后,清理边界外所有的空间。这种方法既避免了碎片的产生,又不需要两块相同的内存空间。

 

 

 

  (5)增量算法

  如果一次性将所有的垃圾进行处理,需要造成系统长时间的停顿,那么就可以让垃圾收集线程和应用程序线程交替执行。每次垃圾收集线程只收集一小片区域的内存空间,接着切换到应用程序线程,依次反复,直到垃圾收集完成。

  优点:由于在垃圾回收的过程中,间断的执行了应用程序代码,所以能减少系统的停顿时间,但是,因为线程切换和上下文切换的消耗,会使得垃圾回收的总体成本上升,造成系统吞吐量的下降。

 

 

 

  (6)分代回收算法(非常重要)

  根据垃圾回收对象的特性,使用合适的算法回收。比如,新生代使用复制算法,老年代使用标记压缩回收算法。

 

 

 

 

 

 被标记需要清除对象的自我救赎

  标记清除算法是如何解决循环引用的问题呢?

  利用可达性分析。枚举根节点,看根节点与对象是否可达,不可达则判断其可被回收,即便有多个对象循环引用,如果不可达则一样要被回收,从此解决循环引用问题。

  可达性分析(Reachability Analysis),这个算法的思想是通过一系列的称为“GC Roots”的对象作为起点,从这些起点开始往下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何的引用链,相连接时,则证明这个对象是不可引用的。

  可以作为GC ROOT的对象可以包括以下几种:

    1、虚拟机栈中引用的对象

    2、方法区中类静态属性引用的变量

    3、本地方法区中常量引用的变量

    4、本地方法栈中JNI(及一般说的Native方法)引用的对象。

   要注意的是,即使没有通过可达性分析的对象,也并非是“非死不可”。

    第一轮筛选:如果对象在可达性分析中没有于GcROOT链相连,那么就需要进行第二轮筛选

    第二轮筛选:对象有没有覆盖(override)过 finalize()函数,对象是否还没运行过finalize()函数。如果这连个条件都满足,

  那么对象就会判断为有必要执行finalize()函数,并把这个对象放入到一个叫做F-Queue的队列中,

  稍后GC会对这个队列中的对象进行第二次扫描,执行每个对象的finalize()函数,那么如果对象要在这里拯救自己——只要在finalize()函数中让自己重新与GcROOT的任何一个对象建立连接即可。

  譬如把自己(this关键字)赋值给某个类变量或者对象的成员变量,那么在这第二次筛选中就会将这个对象移出出“即将回收”的集合。

 

 

 

 

 

 

 

 

 

对象将根据存活的时间被分为:年轻代、年老代(Old Generation)、永久代

  以下是Java堆内存分配:

 

    (1)年轻代

        对象被创建时,内存的分配首先发生在年轻代(大对象可以直接被创建在年老代),大部分的对象在创建后很快就不再使用,因此很快就变得不可达,于是被年轻代的GC机制清理掉,这个GC机制被称为Young GC。

         注意:Young GC并不代表年轻代内存不足,事实上只表示在Eden区上的GC                      

        年轻代可以分为3个区:Eden(伊甸园,亚当和夏娃偷吃禁果生娃娃的地方,用来表示内存首次分配的区域,再贴切不过了)和2个survivor区。

 

       (2)年老代

        对象如果在年轻代存活了足够长的时间而没有被清理掉(即在几次Young GC后存活了下来),则会被复制到年老代,年老代的空间一般比年轻代要大,能存放更多的对象,在年老代上发生GC的次数也比年轻代少。当老年代内存不足时,将执行Full GC

       如果对象比较大(如大数组),Young空间不足,则大对象会直接分配到年老代上。

 

      (3)永久代

      也就是方法区,主要存储Class信息,和存放对象的堆区不同,GC不会再主程序运行期间对永久代进行清理。如果load很多class,就会有可能出现Permanent Generation space错误。

 

 

 

 

 

  以下是GC机制:

  (1)年轻代

           收集算法:

           1)绝大多数刚创建的对象会被分配在Eden区,其中的大多数对象很快就会消亡

           2)当Eden区满的时候,执行Young GC,将消亡的对象清理掉,并将剩余的对象复制到一个存货区survivor0中(此时survivor1是空白的,两个survivor区总是有一个是空白的)

          3)此后,每次Eden区满了,就执行一次Young GC,并将剩余存活的对象都添加到survivor0中

         4)当survivor0也满的时候,会将其中仍然存活的对象放入survivor1中,如果survivor1放不下,会将多出来的放入年老区中。以后Eden区执行Young GC之后,就将剩余的对象添加到survivor1中(此时survivor0是空的)

         5)当两个存货区切换了若干次(默认为15次)之后,仍然存活的对象将被复制到年老代。

 

        (2)老年代

         老年代存储的对象比年轻代多的多,而且不乏大对象,对老年代进行内存清理时,一般采用标记-整理方法,即标记出仍然存活的对象,将存活对象向一端移动,以保证内存的连续。

         在发生Young GC的时候,虚拟机会检查每次进入老年代的大小是否大于老年代的剩余空间大小,如果大于,则触发一次Full GC。

 

         (3)永久代

         包括常量池中的常量回收、无用的类信息回收。

 

  注意:

          1)Young  GC只收集年轻代的对象,Full GC收集所有堆上的对象,包括年轻代、年老代、永久代的对象

          2)总而言之,就是分代分配、分代回收

          3)System.gc()调用的是Full GC

 

 

 

 

 

 

 

垃圾回收器的分类(非常重要)

   如下

 

 

 

垃圾回收器按分代划分

  新生代串行收集器:

    1.复制算法    2.单线程垃圾回收      3.独占式的垃圾回收(也就是,在串行收集器运行时,应用程序中的所有线程都停止工作,进行等待。Stop the world现象。)

  老年代串行收集器:

    1.标记压缩算法 2.单线程垃圾回收 3.独占式的垃圾回收

并行收集器:

  并行收集器是工作在新生代的垃圾收集器,它只是简单的将串行回收器多想线程化。同时,它也是独占式的收集器。

  新生代并行回收收集器:

    1.复制算法     2.多线程     3.独占式

  老年代并行回收收集器:

    1.标记压缩算法     2.多线程    3.独占式

 

  CMS收集器:它关注系统挺停顿时间。并发标记清除。它可以与用户同时运行,从而降低应用程序的停顿时间。

 

  G1收集器:

  G1收集器是目前最新的垃圾回收器,G1收集器的目标是作为一款服务端的垃圾收集器,它在吞吐量和停顿控制上,预期要优于CMS收集器。

  与CMS收集器相比,G1收集器是基于标记-压缩算法的,因此,它不会产生空间碎片,也没有必要在收集之后,进行一次独占式的碎片整理工作。G1收集器还可以进行非常精确的停顿控制,它可以在开发人员在指定长度为M的时间段中,垃圾回收时间不超过N。


本文转自大数据躺过的坑博客园博客,原文链接:http://www.cnblogs.com/zlslch/p/7623016.html,如需转载请自行联系原作者

相关文章
|
1天前
|
安全 Java 编译器
Java对象一定分配在堆上吗?
本文探讨了Java对象的内存分配问题,重点介绍了JVM的逃逸分析技术及其优化策略。逃逸分析能判断对象是否会在作用域外被访问,从而决定对象是否需要分配到堆上。文章详细讲解了栈上分配、标量替换和同步消除三种优化策略,并通过示例代码说明了这些技术的应用场景。
Java对象一定分配在堆上吗?
|
4天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
4天前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
6 0
|
存储 Java API
【Java技术指南】「Java8编程专题」让你真正会用对Java新版日期时间API编程指南
【Java技术指南】「Java8编程专题」让你真正会用对Java新版日期时间API编程指南
129 0
|
XML JavaScript 前端开发
一文告诉你Java日期时间API到底有多烂
一文告诉你Java日期时间API到底有多烂
一文告诉你Java日期时间API到底有多烂
|
4天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
71 38
|
1天前
|
安全 Java
java 中 i++ 到底是否线程安全?
本文通过实例探讨了 `i++` 在多线程环境下的线程安全性问题。首先,使用 100 个线程分别执行 10000 次 `i++` 操作,发现最终结果小于预期的 1000000,证明 `i++` 是线程不安全的。接着,介绍了两种解决方法:使用 `synchronized` 关键字加锁和使用 `AtomicInteger` 类。其中,`AtomicInteger` 通过 `CAS` 操作实现了高效的线程安全。最后,通过分析字节码和源码,解释了 `i++` 为何线程不安全以及 `AtomicInteger` 如何保证线程安全。
java 中 i++ 到底是否线程安全?
|
5天前
|
Java 调度
[Java]线程生命周期与线程通信
本文详细探讨了线程生命周期与线程通信。文章首先分析了线程的五个基本状态及其转换过程,结合JDK1.8版本的特点进行了深入讲解。接着,通过多个实例介绍了线程通信的几种实现方式,包括使用`volatile`关键字、`Object`类的`wait()`和`notify()`方法、`CountDownLatch`、`ReentrantLock`结合`Condition`以及`LockSupport`等工具。全文旨在帮助读者理解线程管理的核心概念和技术细节。
20 1
[Java]线程生命周期与线程通信
|
3天前
|
安全 Java
在 Java 中使用实现 Runnable 接口的方式创建线程
【10月更文挑战第22天】通过以上内容的介绍,相信你已经对在 Java 中如何使用实现 Runnable 接口的方式创建线程有了更深入的了解。在实际应用中,需要根据具体的需求和场景,合理选择线程创建方式,并注意线程安全、同步、通信等相关问题,以确保程序的正确性和稳定性。
|
1天前
|
缓存 Java 调度
Java中的多线程编程:从基础到实践
【10月更文挑战第24天】 本文旨在为读者提供一个关于Java多线程编程的全面指南。我们将从多线程的基本概念开始,逐步深入到Java中实现多线程的方法,包括继承Thread类、实现Runnable接口以及使用Executor框架。此外,我们还将探讨多线程编程中的常见问题和最佳实践,帮助读者在实际项目中更好地应用多线程技术。
9 3