Java内存—JVM与GC垃圾回收

JVM

名称 存储目标 线程共享 备注 异常
方法区(静态区) 常量,静态变量,类加载和编译信息 X OutOfMemoryError
堆区 普通java对象 gc和内存泄漏一般发生在堆区 OutOfMemoryError
程序计数器 当前线程所执行的字节码行号指示器 X X X
【栈区】虚拟机栈 基本数据类型,对象的引用,动态链接,函数出口 X X StackOverflowError OutOfMemoryError
【栈区】Native方法栈 类似于虚拟机栈 X 它为native方法服务,可定制(Sun HotSpot) StackOverflowError OutOfMemoryError

注意

虽说gc主要发生在堆区,但并非仅仅只能发生在堆区。这点需要明确。它还可能发生在如下区域。

  • 虚拟机栈(栈桢中的本地变量表)中的引用的对象
  • 方法区中的类静态属性引用的对象
  • 方法区中的常量引用的对象
  • 本地方法栈中JNI的引用的对象

介绍

GC(Garbage Collection):JAVA/.NET中的垃圾回收器。Java是由C++发展来的。它摈弃了C++中一些繁琐容易出错的东西。其中有一条就是这个GC。而C#又借鉴了JAVA。
在老式的C/C++程序中,程序员定义了一个变量,就是在内存中开辟了一段相应的空间来存值。由于内存是有限的,所以当程序不再需要使用某个变量的时候,就需要销毁该对象并释放其所占用的内存资源,好重新利用这段空间。在C/C++中,释放无用变量内存空间的事情需要由程序员自己来处理。就是说当程序员认为变量没用了,就手动地释放其占用的内存。但是这样显然非常繁琐,如果有所遗漏,就可能造成资源浪费甚至内存泄露。当软件系统比较复杂,变量多的时候程序员往往就忘记释放内存或者在不该释放的时候释放内存了。
有了GC,程序员就不需要再手动的去控制内存的释放。当Java虚拟机(VM)或.NETCLR发觉内存资源紧张的时候,就会自动地去清理无用对象(没有被引用到的对象)所占用的内存空间(这里的说法略显粗略,事实上何时清理内存是个复杂的策略)。如果需要,可以在程序中显式地使用System.gc() / System.GC.Collect()来强制进行一次立即的内存清理。Java提供的GC功能可以自动监测对象是否超过了作用域,从而达到自动回收内存的目的,Java的GC会自动进行管理,调用方法:System.gc() 或者Runtime.getRuntime().gc(); ——百度百科

C++下的垃圾回收

Java下的垃圾回收

分代机制

分代机制是jvm对于内存管理的一种机制。它主要是对堆区的一个划分,它通过这种机制来触发内存的回收。它具体分为了新生代,老年代,永生代,具体下面来看看这些概念。

新生代:
新生代分为Eden区,survivor区(然而这个区域又分为来个区域:分别是survivor0,和survivor1这俩个区域(后边简称s0和s1))。他们的大小在hotSpot当中的比例是8:1:1。

  • Eden:
    Eden一词是伊甸园的意思,也是人类诞生之地(亚当,夏娃,偷吃禁果生娃娃)。当我们一个对象new出来的时候首先会进入的地方就是这个Eden区域(一种例外:如果对象过大,比如大数组,将会直接放到老年代)。我们还需要理解一个概念就是minorGc这个概念,minorGc是gc在eden区的一种存在形式,它会快速的清除大部分的Eden区的对象,因为Eden区大多数对象都是短命的。而没有被minorGc杀掉的对象则会进入“幸存区”,什么是幸存区?就是survivor,接着往下看。

  • Survivor:
    刚我们说了幸存区域,这个区域是接纳Eden活下来的对象的,那么它工作的原理是什么呢?每次进行清理时,将Eden区和一个Survivor中仍然存活的对象拷贝到 另一个Survivor中,然后清理掉Eden和刚才的Survivor。这么做的目的是回收掉没用的内存,而有用的内存则进入下一阶段,判断条件是一次内存清理时幸存的对象超过了10%,多余的内存进入老年区。

  • PS:用-XX:SurvivorRatio参数来配置Eden区域Survivor区的容量比值,默认是8,代表Eden:Survivor1:Survivor2=8:1:1.

老年代:
老年代存储的对象比年轻代多得多,而且不乏大对象,对老年代进行内存清理时,如果使用停止-复制算法,则相当低效。一般,老年代用的算法是标记-整理算法,即:标记出仍然存活的对象(存在引用的),将所有存活的对象向一端移动,以保证内存的连续。
在发生Minor GC时,虚拟机会检查每次晋升进入老年代的大小是否大于老年代的剩余空间大小,如果大于,则直接触发一次Full GC,否则,就查看是否设置了-XX:+HandlePromotionFailure(允许担保失败),如果允许,则只会进行MinorGC,此时可以容忍内存分配失败;如果不允许,则仍然进行Full GC(这代表着如果设置-XX:+Handle PromotionFailure,则触发MinorGC就会同时触发Full GC,哪怕老年代还有很多内存,所以,最好不要这样做)。

永久代:
永久代的回收有两种:常量池中的常量,无用的类信息,常量的回收很简单,没有引用了就可以被回收。对于无用的类进行回收,必须保证3点:
类的所有实例都已经被回收
加载类的ClassLoader已经被回收
类对象的Class对象没有被引用(即没有通过反射引用该类的地方)

ps:
永久代的回收并不是必须的,可以通过参数来设置是否对类进行回收。HotSpot提供-Xnoclassgc进行控制
使用-verbose,-XX:+TraceClassLoading、-XX:+TraceClassUnLoading可以查看类加载和卸载信息
-verbose、-XX:+TraceClassLoading可以在Product版HotSpot中使用;
-XX:+TraceClassUnLoading需要fastdebug版HotSpot支持

小结一波:
这里写图片描述

gcRoot-根搜算法

  • 引用计数算法
    在jdk1.2之前被使用的垃圾收集算法—引用计数算法,其主要思想就是维护一个counter,当counter为0的时候认为对象没有引用,可以被回收。缺点是无法处理循环引用。而现在我们jdk1.8默认VM是HotSpot,采用的是根搜算法

  • GC Roots Tracing
    温馨提示:本小段点及finalize,这个在下一点有介绍,
    GC Roots Tracing,根搜算法,它不仅仅用于java(C#,甚至包括前面提到的古老的Lisp),这个算法的基本思路就是通过一系列的名为“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Roots没有任何引用链相连时,则证明此对象是不可用的。
    但是,这个但是很重要,在根搜索算法中不可达的对象,也并非是“非死不可”的,这时候它们暂时处于“缓刑”阶段,要真正宣告一个对象死亡,至少要经历两次标记过程:如果对象在进行根搜索后发现没有与GC Roots相连接的引用链,那它将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。
    如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为F-Queue的队列之中,并在稍后由一条由虚拟机自动建立的、低优先级的Finalizer线程去执行。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。这样做的原因是,如果一个对象在finalize()方法中执行缓慢,或者发生了死循环(更极端的情况),将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。finalize()方法是对象逃脱死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中成功拯救自己—只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或对象的成员变量,那在第二次标记时它将被移除出“即将回收”的集合;如果对象这时候还没有逃脱,那它就真的离死不远了。

  • finalize
    它是个什么鬼呢?他是垃圾回收器回收一个对象的时候第一个要调用的方法。当对象变成(GC Roots)不可达时,GC会判断该对象是否覆盖了finalize方法,若未覆盖,则直接将其回收。否则,若对象未执行过finalize方法,将其放入F-Queue队列,由一低优先级线程执行该队列中对象的finalize方法。执行finalize方法完毕后,GC会再次判断该对象是否可达,若不可达,则进行回收,否则,对象“复活”。

    //复活例子,稍后GC将对F-Queue中的对象进行第二次小规模的标记,如果对象要在finalize()中成功拯救自己—
    //只要重新与引用链上的任何一个对象建立关联即可,譬如把自己(this关键字)赋值给某个类变量或对象的成员变量
    //那在第二次标记时它将被移除出“即将回收”的集合
    @Override
    protected void finalize() throws Throwable {
       super.finalize();
     System.out.println(“finalize mehtod executed!”);
     FinalizeEscapeGC.SAVE_HOOK = this;
    }
    具体更为详细的可以参考这篇文章:https://blog.csdn.net/pi9nc/article/details/12374049

  • PS:
    在JDK 1.2之后,Java对引用的概念进行了扩充,将引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)四种,这四种引用强度依次逐渐减弱。
    具体参考:Java强软弱虚4大引用

总结

分代机制提供了一种规则,一种触发gcroot根搜算法的规则,分代机制包含的一些算法处于gc垃圾回收链的前面部分,它就像是一个警察(MinorGc/FullGc),当搜集到足够的证据(8:1:1)则通知gcroot对超过阈值的内存进行逮捕,而gcroot根搜算法则是垃圾回收链的末端,就像一个法庭,它负责审判和执行kill内存的工作,在此之前罪犯内存都有机会请律师为自己辩护,以求自救(finalize)。

Thanks

Java系列笔记(3) - Java 内存区域和GC机制
Dalvik VM (DVM) 与Java VM (JVM)的区别
JVM内存模型你只要看这一篇就够了
Android DVM
Java GC 介绍
根搜索算法
java finalize方法总结、GC执行finalize的过程

猜你喜欢

转载自blog.csdn.net/user11223344abc/article/details/80337612