JVM构架、GC垃圾回收机制的理解

 JVM是Java Virtual Machine(Java虚拟机)的缩写

1.程序计数器

  它的作用可以看做是当前线程所执行的字节码的行号指示器。

每个线程都有一个程序计算器,就是一个指针,指向方法区中的方法字节码(下一个将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。

2、栈区

  栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。

3、堆区

  堆这块区域是JVM中最大的,应用的对象和数据都是存在这个区域,这块区域也是线程共享的,也是 gc 主要的回收区,一个 JVM 实例只存在一个堆类存,堆内存的大小是可以调节的。类加载器读取了类文件后,需要把类、方法、常变量放到堆内存中,以方便执行器执行

 4、方法区

  用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

 5、本地方法栈

  与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。

GC就是垃圾回收机制

  在系统运行过程中,会产生一些无用的对象,这些对象占据着一定的内存,如果不对这些对象清理回收无用对象的内存,可能会导致内存的耗尽,所以垃圾回收机制回收的是内存。同时GC回收的是堆区和方法区的内存。

 

JVM回收特点:(stop-the-world)当要进行垃圾回收时候,不管何种GC算法,除了垃圾回收的线程之外其他任何线程都将停止运行。

被中断的任务将会在垃圾回收完成后恢复进行。GC不同算法或是GC调优就是减少stop-the-world的时间。

垃圾收集算法

1、标记-清除(Mark-Sweep)算法

     这是最基础的算法,标记-清除算法就如同它的名字样,分为“标记”和“清除”两个阶段:首先标记出所有需要回收的对象,标记完成后统一回收所有被标记的对象。这种算法的不足主要体现在效率和空间,从效率的角度讲,标记和清除两个过程的效率都不高;从空间的角度讲,标记清除后会产生大量不连续的内存碎片, 内存碎片太多可能会导致以后程序运行过程中在需要分配较大对象时,无法找到足够的连续内存而不得不提前触发一次垃圾收集动作。标记-清除算法执行过程如图:

2、复制(Copying)算法

      复制算法是为了解决效率问题而出现的,它将可用的内存分为两块,每次只用其中一块,当这一块内存用完了,就将还存活着的对象复制到另外一块上面,然后再把已经使用过的内存空间一次性清理掉。这样每次只需要对整个半区进行内存回收,内存分配时也不需要考虑内存碎片等复杂情况,只需要移动指针,按照顺序分配即可。复制算法的执行过程如图:

     不过这种算法有个缺点,内存缩小为了原来的一半,这样代价太高了。现在的商用虚拟机都采用这种算法来回收新生代,不过研究表明1:1的比例非常不科学,因此新生代的内存被划分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。每次回收时,将Eden和Survivor中还存活着的对象一次性复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden区和Survivor区的比例为8:1,意思是每次新生代中可用内存空间为整个新生代容量的90%。当然,我们没有办法保证每次回收都只有不多于10%的对象存活,当Survivor空间不够用时,需要依赖老年代进行分配担保(Handle Promotion)。

3、标记-整理(Mark-Compact)算法

    复制算法在对象存活率较高的场景下要进行大量的复制操作,效率很低。万一对象100%存活,那么需要有额外的空间进行分配担保。老年代都是不易被回收的对象,对象存活率高,因此一般不能直接选用复制算法。根据老年代的特点,有人提出了另外一种标记-整理算法,过程与标记-清除算法一样,不过不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉边界以外的内存。标记-整理算法的工作过程如图:

4、分代收集算法

根据上面的内容,用一张图概括一下堆内存的布局

     现代商用虚拟机基本都采用分代收集算法来进行垃圾回收。这种算法没什么特别的,无非是上面内容的结合罢了,根据对象的生命周期的不同将内存划分为几块,然后根据各块的特点采用最适当的收集算法。大批对象死去、少量对象存活的(新生代),使用复制算法,复制成本低;对象存活率高、没有额外空间进行分配担保的(老年代),采用标记-清理算法或者标记-整理算法。

思考“GC是在什么时候,对什么东西,做了什么事情?”

  • 什么时候?

  从字面上翻译过来就是什么时候触发我们的GC机制

  ①在程序空闲的时候。这个回答无力吐槽

  ②程序不可预知的时候/手动调用system.gc()。关于手动调用不推荐

  ③Java堆内存不足时,GC会被调用。当应用线程在运行,并在运行过程中创建新对象,若这时内存空间不足,JVM就会强制地调用GC线程,以便回收内存用于新的分配。若GC一次之后仍不能满足内存分配的要求,JVM会再进行两次GC作进一步的尝试,若仍无法满足要求,则 JVM将报“out of memory”的错误,Java应用将停止。就是

  这时候如果你们讲出新生代和老年代的话或许会更细的了解一下Minor GC、Full GC、OOM什么时候触发!

  创建对象是新生代的Eden空间调用Minor GC;当升到老年代的对象大于老年代剩余空间Full GC;GC与非GC时间耗时超过了GCTimeRatio的限制引发OOM。

  • 什么东西?

  从字面的意思翻译过来就是能被GC回收的对象都有哪些特征

  ①超出作用域的对象/引用计数为空的对象。

  引用计数算法:给对象中添加一个引用计数器,每当有一个地方引用它时,计数器就加1;当引用失效时,计数器值就减1;任何时刻计数器都为0的对象就是不可能再被使用的。

  ②从GC Root开始搜索,且搜索不到的对象

  跟搜索算法:以一系列名为 GC Root的对象作为起点,从这些节点开始往下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链的时候,则就证明此对象是不可用的。

  这里会提出一个思考,什么样的对象能成为GC Root : 虚拟机中的引用的对象、方法区中的类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中jni的引用对象。

  ③从root搜索不到,而且经过第一次标记、清理后,仍然没有复活的对象。

  • 做什么?

  不同年代、不同种类的收集器很多,不过总体的作用是删除不使用的对象,腾出内存空间。补充一些诸如停止其他线程执行、运行finalize等的说明。

ok  现在来回答一下我们最上面的问题,上面时候容易发生内存泄露

  ①静态集合类像HashMap、Vector等

  ②各种连接,数据库连接,网络连接,IO连接等没有显示调用close关闭,不被GC回收导致内存泄露。

  ③监听器的使用,在释放对象的同时没有相应删除监听器的时候也可能导致内存泄露。

本文章转自:https://www.cnblogs.com/dk2557/p/9585610.html

posted @ 2018-10-20 19:13  吻泪  阅读(1460)  评论(0编辑  收藏  举报
Live2D