Java的自动内存管理主要指的是针对对象的回收和分配。堆是垃圾收集器管理的主要区域,也被称为GC堆。
现阶段,收集器基本都采用分代垃圾收集算法,所以Java堆被划分为了几个不同的区域,可以根据这些区域选择适合的垃圾收集算法。
大多数情况下,对象在Eden区分配,当Eden区中没有足够的空间分配时,虚拟机将发起Minor GC(新生代GC)。找出Eden区和S0区活跃的对象,将它复制到S1区。并将S0区域和Eden区的对象清空。之后将S0区和S1区交换。(Eden—> Survivor区后对象的初始年龄变为1)
如果Servivor空间满了,会通过分配担保机制,把新生代的对象提前转移到老年代去。
在存储大量连续内存空间的对象时,直接进入老年区。
大部分情况,对象首先在Eden区域分配,如果对象在Eden出生并经过第一次Minor GC后依然存活,并且能够被Survivor容纳,将被移动到Survivor空间,对象年龄 + 1.
对象在Survivor中每熬过一次Minor GC,年龄就增加1岁。当年龄增加到一定程度,就会晋升到老年代中。
针对HosSpot VM 的实现,它里面的GC准确分类只有两大类:
部分收集(Partial GC):
新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集。
老年代收集(Major GC / Old GC): 只对老年代进行垃圾收集。
混合收集(Mixed GC): 对整个咸亨带和部分老年代进行垃圾收集。
整堆收集(Full GC):收集整个Java堆和方法区。
对象进入老年代的情况:
这时老年代就会进行空间分配担保机制。
JDK 6 Update 24 之后:在发生Minor GC 之前,虚拟机必须先检查老年代最大可用的连续空间大于新生代对象总大小或者历次晋升的平均大小,就会进行Minor GC ,否则进行Full GC。来确保Minor GC 之前,老年代本身还有容纳新生代所有对象的剩余空间。
对堆内存回收前的第一步就是要判断哪些对象已经死亡。
给对象添加一个引用计数器,有地方引用就加1,计数器为0,就说明不再被使用。
这个方法实现简单,效率高,但是目前主流的虚拟机中并没有选择这个算法来管理内存,其最主要的原因是它很难解决对象之间相互循环引用的问题。
假如两个对象相互循环引用,无法通过引用计数算法通知GC 回收器回收。
垃圾回收器通过GC Roots 的对象作为起点,从这些节点开始向下遍历对象引用链,如果一个对象没有任何引用与之相连,判断对象已经死亡。GC Roots 包括以下几种:
引用类型是Java垃圾回收机制的重要组成部分,它们可以帮助开发者更好地控制对象的生命周期,有效避免内存泄漏和OutOfMemoryError等问题。
JDK1.2 以后,Java 对引用的概念进行了扩充,将引用分为强引用、软引用、弱引用、虚引用四种(引用强度逐渐减弱)
在Java中,字符串常量池中的字符串常量是共享的,一旦创建,就会一直存在于字符串常量池中,直到JVM退出。如果一个字符串常量没有任何对象引用它,也就是说它没有被使用,那么这个字符串常量就会被认为是无用的,称为废弃常量。
当JVM执行垃圾回收时,它会检查字符串常量池中的所有字符串常量,如果发现某个字符串常量没有任何对象引用它,就会将它从常量池中清除。
我们无法直接判断一个字符串常量是否是废弃常量。只有当JVM执行垃圾回收时,才会对所有字符串常量进行检查和清理。
判断一个类是否是无用的类,也就是判断一个类是否可以被垃圾回收器回收,可以通过以下两个角度:
在实际开发中,我们无法直接判断一个类是否为无用的类。JVM会在垃圾回收时自动判断类是否为无用的类,并对其进行回收。但是,我们可以通过一些手段来促使JVM更早地回收无用的类,例如关闭某个类所在的ClassLoader,或者强制将某个类的实例置为null。
标记清除算法分为两个阶段:标记和清除阶段。首先标记所有不需要回收的类,标记完成后,统一回收没有被标记的类。
标记清除算法的主要优点在于可以处理任意的内存分配模式,而不会浪费任何内存空间。但是,它也存在一些缺点,例如容易产生内存碎片,导致内存利用率降低。
这是一种将内存空间分成两块的算法,每次只使用其中一块。当这一块内存用完后,将其中还存活的对象复制到另一块空间中,然后清除原来的内存空间。
复制算法的主要优点在于可以避免内存碎片的产生,并且不需要进行标记和清除的操作。但是,它也存在一些缺点,例如需要耗费额外的空间来存储复制后的对象,以及复制对象的操作可能会影响程序的性能。
标记整理算法是一种结合了标记清除算法和复制算法的算法。首先,标记整理算法会遍历所有的存活对象,并标记这些对象。然后,它会将所有的存活对象向一端移动,然后将这一端的空闲内存空间清空。
标记整理算法的主要优点在于可以避免内存碎片的产生,同时也不需要耗费额外的空间来存储复制后的对象。但是,它也存在一些缺点,例如需要对存活对象进行移动操作,可能会影响程序的性能。
当前虚拟机的垃圾收集都采用分代收集算法,一般将 java 堆分为新生代和老年代,这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。
在Java中,分代收集算法主要分为两代:年轻代和老年代。年轻代是指存活时间较短的对象,通常采用复制算法进行垃圾回收;老年代是指存活时间较长的对象,通常采用标记整理算法进行垃圾回收。
分代收集算法的主要优点在于可以更加高效地进行内存管理,并且可以根据对象的存活时间采用不同的垃圾收集算法。但是,它也存在一些缺点,例如需要额外的空间来存储对象代的信息。
Serial(串行)收集器是最基本、历史最悠久的垃圾收集器了。大家看名字就知道这个收集器是一个单线程收集器了。它的 “单线程” 的意义不仅仅意味着它只会使用一条垃圾收集线程去完成垃圾收集工作,更重要的是它在进行垃圾收集工作的时候必须暂停其他所有的工作线程( “Stop The World” ),直到它收集结束。
新生代采用标记-复制算法,老年代采用标记-整理算法。
ParNew 收集器其实就是 Serial 收集器的多线程版本,除了使用多线程进行垃圾收集外,其余行为(控制参数、收集算法、回收策略等等)和 Serial 收集器完全一样。
新生代采用标记-复制算法,老年代采用标记-整理算法。
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。它非常符合在注重用户体验的应用上使用。
CMS(Concurrent Mark Sweep)收集器是 HotSpot 虚拟机第一款真正意义上的并发收集器,它第一次实现了让垃圾收集线程与用户线程(基本上)同时工作。
G1 (Garbage-First) 是一款面向服务器的垃圾收集器,主要针对配备多颗处理器及大容量内存的机器. 以极高概率满足 GC 停顿时间要求的同时,还具备高吞吐量性能特征.