JVM GC

什么样的对象会被GC?

超出作用域或者引用计数为空的对象,从gc root开始搜索找不到的对象,并且经历过一次标记、清理后,仍然没有复活的对象。

解释:
对象被引用一次,引用计数+1。但是若出现两个对象互相引用,没有其他对象引用他们,那么他们会造成资源泄露。所以只适合一些简单的引用场景。

GC roots”,或者说tracing GC的“根集合”,就是一组必须活跃的引用,不是对象。

例如说,这些引用可能包括:

  • 所有Java线程当前活跃的栈帧里指向GC堆里的对象的引用;换句话说,当前所有正在被调用的方法的引用类型的参数/局部变量/临时值。
  • VM的一些静态数据结构里指向GC堆里的对象的引用,例如说HotSpot VM里的Universe里有很多这样的引用。 JNI
    handles,包括global handles和local handles
  • (看情况)所有当前被加载的Java类
  • (看情况)Java类的引用类型静态变量
  • (看情况)Java类的运行时常量池里的引用类型常量(String或Class类型)
  • (看情况)String常量池(StringTable)里的引用

Tracing GC的根本思路就是:给定一个集合的引用作为根出发,通过引用关系遍历对象图,能被遍历到的(可到达的)对象就被判定为存活,其余对象(也就是没有被遍历到的)就自然被判定为死亡。注意再注意:tracing GC的本质是通过找出所有活对象来把其余空间认定为“无用”,而不是找出所有死掉的对象并回收它们占用的空间。
GC roots这组引用是tracing GC的起点。要实现语义正确的tracing GC,就必须要能完整枚举出所有的GC roots,否则就可能会漏扫描应该存活的对象,导致GC错误回收了这些被漏扫的活对象。

这就像任何递归定义的关系一样,如果只定义了递推项而不定义初始项的话,关系就无法成立——无从开始;而如果初始项定义漏了内容的话,递推出去也会漏内容。

那么分代式GC对GC roots的定义有什么影响呢?
答案是:分代式GC是一种部分收集(partial collection)的做法。在执行部分收集时,从GC堆的非收集部分指向收集部分的引用,也必须作为GC roots的一部分。
具体到分两代的分代式GC来说,如果第0代叫做young gen,第1代叫做old gen,那么如果有minor GC / young GC只收集young gen里的垃圾,则young gen属于“收集部分”,而old gen属于“非收集部分”,那么从old gen指向young gen的引用就必须作为minor GC / young GC的GC roots的一部分。
继续具体到HotSpot VM里的分两代式GC来说,除了old gen到young gen的引用之外,有些带有弱引用语义的结构,例如说记录所有当前被加载的类的SystemDictionary、记录字符串常量引用的StringTable等,在young GC时必须要作为strong GC roots,而在收集整堆的full GC时则不会被看作strong GC roots。

换句话说,young GC比full GC的GC roots还要更大一些。

什么时候进行GC?

1、直接调用System.gc()进行GC

2、老年代空间不足时触发full GC。
老年代只有在新生代对象转为大对象、大数组时才会出现空间不足的现象,当执行Full GC后空间依然不足时,则会抛出java.lang.OutOfMemoryError: Java heap space 。为避免以上两种状况引起的FullGC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。

3、Permanet Generation(又称方法区、持久代)空间满
Permanet Generation中存放的为一些class的信息等,当系统中要加载的类、反射的类和调用的方法较多时,Permanet Generation可能会被占满,在未配置为采用CMS GC的情况下会执行Full GC。如果经过Full GC仍然回收不了,那么JVM会抛出如下错误信息:java.lang.OutOfMemoryError: PermGen space
为避免Perm Gen占满造成Full GC现象,可采用的方法为增大Perm Gen空间或转为使用CMS GC。

4、统计得到的Minor GC晋升到老年代的平均大小大于老年代的剩余空间
这是一个较为复杂的触发情况,Hotspot为了避免由于新生代对象晋升到旧生代导致旧生代空间不足的现象,在进行Minor GC时,做了一个判断,如果之前统计所得到的Minor GC晋升到旧生代的平均大小大于旧生代的剩余空间,那么就直接触发Full GC。

例如程序第一次触发MinorGC后,有6MB的对象晋升到旧生代,那么当下一次Minor GC发生时,首先检查旧生代的剩余空间是否大于6MB,如果小于6MB,则执行Full GC。

当新生代采用PSGC时,方式稍有不同,PS GC是在Minor GC后也会检查,例如上面的例子中第一次Minor GC后,PS GC会检查此时旧生代的剩余空间是否大于6MB,如小于,则触发对旧生代的回收。

程序员不能具体控制时间,系统在不可预测的时间调用System.gc()函数的时候;当然可以通过调优,用NewRatio控制newObject和oldObject的比例,用MaxTenuringThreshold 控制进入oldObject的次数,使得oldObject 存储空间延迟达到full gc,从而使得计时器引发gc时间延迟OOM的时间延迟,以延长对象生存期

eden满了minor gc,升到老年代的对象大于老年代剩余空间full
gc,或者小于时被HandlePromotionFailure参数强制full
gc;gc与非gc时间耗时超过了GCTimeRatio的限制引发OOM,调优诸如通过NewRatio控制新生代老年代比例,通过
MaxTenuringThreshold控制进入老年前生存次数等

GC 时做了什么?

1、删除不使用的对象,回收内存空间
2、停止其他线程执行、运行finalize。
3、新生代整理内存时,进行复制整理。使用了两个内存空间from survivor、to survivor。
4、老年代进行标记清理、标记清理后进行压缩,清楚碎片。

GC的策略有哪些?优劣势?适用于什么场景?

Heap划分为两大类New Generation(新生代),Old Generation(老年代)。Minor GC是针对New Generation的,而Full GC是则是对新生代和老年代一起做GC, 所以Full GC的开销会非常大,要净量避免。
1、New Generation GC策略:Minor GC

 Serial GC。采用单线程方式,用Copying算法。New Generation会再次被划分成Eden Space和S0、S1,Copying算法所需要的额外内存空间,S0和S1又称为From Space和To Space。
 Parallel Scavenge。将内存空间分段来使用多线程,Copying算法。
 ParNew,比Parallel Scavenge多做了与Old Generation使用CMS GC一起发生时的特殊处理。

2、 Old Generation的GC策略

 Serial GC。采用单线程方式,使用Mark-Sweep和Mark-Compact gc算法。
 Parallel Mark-Sweep、Parallel Mark-Compact。同样也是把Old Generation空间进行划分成regions,只是粒度更细了。
 CMS(Concurrent Mark-Sweep) GC。我承认这个GC我真的没怎么看懂,目的是为了实现并发,结果就造成具体实现太麻烦了。有兴趣的朋友去看书吧,文末我说了是哪本书。这里有个地方可以说一下,就是算法使用的还是Mark-Sweep,对于内存碎片的问题,CMS提供了一个内存碎片的整理功能,会在执行几次Full GC以后执行一次。

GC算法

1、引用计数法
一个对象,每被引用一次计数+1,只要对象不在任何地方被引用,那么他将被回收。但是存在一个问题,两个对象互相引用,但是都不被第三个对象引用,则会造成资源泄露。故该算法只能用于简单的引用场景。
2、跟踪收集法
把系统的整个引用想象成一个允许有环路的树形结构,但根节点只有一个,然后从根节点出发查看对象是否可达。但是这个需要程序暂停,来保证一次扫描的现场不变。
3、复制法Copying
需要两块内存空间,把扫描的可达对象复制到另一个空间,然后把原空间全部清除。适用于存活对象较少的情况。
4、标记清除法Mark-Sweep
把扫描到的可达对象都标记起来,然后把未标记的对象清除。该清除法会有内存碎片。适用于存活对象较多的情况。
5、标记清除压缩法Mark-Compact
把扫描到的可达对象都标记起来,然后把未标记的对象清除,然后进行压缩,让内存连续起来清除碎片。

摘至:
http://blog.youkuaiyun.com/cy609329119/article/details/51771953
http://blog.youkuaiyun.com/u014421556/article/details/52396706
https://www.zhihu.com/question/53613423/answer/135743258

JVM的垃圾回收机制是Java语言的一个核心特性,它负责自动管理程序运行时分配的内存[^1]。通过垃圾回收,JVM能够检测并释放不再使用的对象,从而避免内存泄漏和资源浪费[^2]。 ### 垃圾回收的基本概念 在Java中,垃圾回收主要关注的是堆内存中的对象。当一个对象不再被任何引用变量所指向时,这个对象就被认为是可回收的。JVM使用了多种算法和技术来确定哪些对象是可以安全地回收的,包括标记-清除、复制、标记-整理等[^4]。 #### 标记-清除算法 标记-清除算法是最基础的一种垃圾收集算法。它分为两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收掉所有被标记的对象。这种方法简单有效,但是存在效率问题以及内存碎片问题[^1]。 ```java // 示例代码 - 创建一些临时对象 public class GCDemo { public static void main(String[] args) { for (int i = 0; i < 10000; i++) { Object obj = new Object(); // 模拟创建大量短生命周期对象 } } } ``` #### 复制算法 为了解决标记-清除算法的内存碎片问题,复制算法将可用内存按容量划分为大小相等的两块,每次只使用其中一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上面,然后再把已使用过的内存空间一次清理掉。这样使得每次都是对整个半区进行内存回收,内存分配时也就不用考虑内存碎片等复杂情况了[^2]。 #### 标记-整理算法 标记-整理算法与标记-清除算法似,但不同之处在于,在完成标记之后,存活的对象会被移动到内存的一端,然后直接清理掉边界以外的内存。这有助于减少内存碎片,并且提高了内存利用率[^4]。 ### JVM垃圾回收器 JVM提供了几种不同的垃圾回收器,每种都有其特点和适用场景: - **Serial Collector**:单线程工作,适用于小型应用。 - **Parallel Scavenge**:多线程工作,适合于数据量大、多核处理器的应用。 - **CMS (Concurrent Mark Sweep)**:以最短的停顿时间为目标的收集器,适用于重视响应速度的服务。 - **G1 (Garbage First)**:面向服务端应用的新一代垃圾收集器,可以更高效地利用大内存。 选择合适的垃圾收集器对于优化应用程序性能至关重要。例如,如果您的应用对延迟敏感,则可能倾向于使用CMS或者最新的ZGC/Zoncolic GC;而对于吞吐量要求较高的应用,则可以选择Parallel Scavenge[^1]。 ### 调优方法 为了提升Java应用的性能和稳定性,可以通过以下方式进行调优: - **调整堆大小**:合理设置初始堆大小(-Xms)和最大堆大小(-Xmx),避免频繁的Full GC。 - **选择适当的垃圾收集器**:根据应用的特点选择最适合的垃圾收集器。 - **分析GC日志**:通过开启GC日志记录(-XX:+PrintGCDetails -XX:+PrintGCDateStamps > file.log),可以帮助识别潜在的问题区域。 - **优化代码**:减少不必要的对象创建,及时释放不再使用的资源,利用缓存机制等。 通过对JVM垃圾回收机制的理解及相应的调优实践,开发者可以显著提高Java应用的性能表现和系统的整体稳定性[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值