JVM常见问题汇总

什么是JVM?

jvm全称是java virtual Machine,是java跨平台特性的保障,java程序被编译成为字节码文件之后都会放到虚拟机上面来执行,同时还提供了对java程序的内存管理功能。

那java虚拟机里面内存区域是怎么划分的?

在java中,内存区域会被分为 堆,栈,方法区三个大块以及程序计数器,在堆里面又会分为 年轻代【Eden区,s0区,s1区】老年代,栈又被分为虚拟机栈和本地方法栈。
用一个图来表示就是这样:
在这里插入图片描述
年轻代:
java虚拟机在进行内存分配的时候会首先在Eden区进行内存分配,当Eden区内存不足的时候就会进行一次mirrorGC,存活的对象就会放在s0区,然而,当s0区内存不足的时候就会使用复制算法进行一次垃圾回收,把S0的存活的对象复制到S1,然后清空S0,那么当一个对象在S0或S1里面存活到一定时间后就会被转移到老年代。如果再Eden区创建的一个对象是一个比较大的对象,那么会直接分配到老年代
老年代:
老年代里面所存储的对象一般都是存活时间比较长的或者是占用空间比较大的对象
方法区:
在方法区里面存在常量池,用于存储我们在程序里面创建的常量,同时还存储了所有类的信息,以及方法的信息,还有静态变量等等,一般在这里面进行垃圾回收是回收不了什么空间的
本地方法栈:
各个虚拟机自由实现,调用本地方法的时候会使用
虚拟机栈:
用于存储方法执行的时候的栈帧(包含方法信息,局部变量表,操作数栈,动态链接,方法出口信息等等)
程序计数器:
用于记录当前执行到了字节码的那一行

上面提到了垃圾,那么java里面什么样的对象被称为垃圾?

在Java程序里面,如果一个对象没有被任何其他对象所引用了,那么这个对象就可以称之为垃圾。

那么又是如何找到这些垃圾的呢?

要找到垃圾,那么就需要判断这些对象有没有被其它对象所引用,有如下两种方法:
1、引用计数法
也就是每一个对象都会维护一个值来记录自己被其它对象引用了多少次,每当被引用一次那么这个值就会+1,这种做法实现起来固然简单,但是却存在一个缺陷,即循环引用的问题。
例如有如下代码:

 Object a = new Object(); // 对象a 的引用为1
 Object b = new Object(); // 对象b 的引用为1
 a = b;//对象b的引用+1变为2
 b = a;//对象a的引用+1变为2
 a = null;//对象a的引用-1变为1;
 b = null;//对象b的引用-1变为1

在上面的代码里面我们可以看到引用a 和 b 孙然都已经等于null了,但是对象a和对象b的引用数还不为0,所以对象a和对象b就永远不会被回收,因此,在java里面并没有采取这个方法来进行垃圾的标记。
2、可达性分析算法
由于引用计数法的缺陷,所以在java里面采用了可达性分析算法来进行垃圾标记。
java中的可达性分析算法是基于一系列GCRoots 对象拉进行的,当一个对象自下而上能追溯到GCRoots对象的话那么这个对象就应该是一个存活的对象。

那么什么样的对象被称为是GCRoots对象呢?

1、方法区里面的常量池里面的引用对象
2、方法区里面的静态属性引用对象
3、栈里面的引用对象
4、存活的线程对象

如果一个对象标记为不可达了一定会被回收吗?

这个是不一定的,因为在对象被标记为不可达之后,虚拟机还会判断他是否需要执行finalize方法,如果对象重写了finalize方法,呢么他就会被加入到一个队列里面,交给虚拟机创建的一个低优先级的队列里面去执行,这是候如果在finalize方法对象重新被GCroots对象引用了,那么他就不会被回收了。

前面多次提到了引用,那么java里面都有哪些引用呢?

在java里面一共有四种引用:

  • 强引用
    当我们使用 Object o = new Object(); 创建一个对象的时候这里的o就是一个强引用,在垃圾回收的时候,被强引用引用的对象是不会被回收的。

  • 软引用
    在java里面可以使用 SoftRefrence s = new SoftRefrence()来使用软引用,使用软引用引用的对象会在垃圾回收空间不够的时候被回收

  • 弱引用
    weakRefrence w = new WeakRefrence();被弱引用引用的对象会在下一次垃圾回收的时候被回收。在ThreadLocal里面的Entry与ThreadLocal对象的引用就是使用的弱引用

  • 虚引用
    虚引用又称为幽灵引用,PhatomRenfrence p = new PhatomRenfrence();
    虚引用是最弱的一中引用,被虚引用引用的对象会在被回收的时候收到一个通知

java虚拟机是如何进行垃圾回收的呢?

java虚拟机进行垃圾回收的时候有如下几种算法:

  • 标记清除算法
    标记清除方法会对标记了需要回收的对象进行回收,但是使用标记回收算法进行垃圾回收会很容易产生内存碎片
  • 复制算法
    复制算法是为了解决上面的内存碎片的问题,把内存区域分为两块,每次只有一块可用,在一块内存区域不够的时候会把标记为存活的对象连续复制对另一块内存区域(这时候不会进行垃圾回收),然后把当前的内存区域清空,不过这种算法带来的问题就是可使用的内存区域会变小
  • 标记整理算法
    标记整理算法是为了解决赋值算法的缺陷,先试用标记清除把垃圾回收,然后再对内存进行整理,使其连续,不过这种做法效率比较低下,一般会用于老年代的垃圾回收。
  • 分代收集
    分代收集是现代垃圾回收器主流采用的垃圾回收算法,具体来说是对上面的几种算法的综合使用。jvm把堆内存分为Eden区,S0区,S1区,Old区,在各个区使用不同的垃圾回收算法。由于java里面的大部分对象都是朝生夕死的,所以Eden区的垃圾回收一定要快,因此Eden一般会使用标记清除法,然后把存活的对象给到S0区,若S0空间不够了就使用复制算法进行垃圾回收。当一个对象经历过N次MirrorGC之后仍然存活,那么就会直接进入老年代

那么java中的垃圾回收器又有哪些呢?

java里面的垃圾回收器目前有七种分别如下:

  • serial
    最古老的垃圾收集器,串行运行的,适用于新生代使用,使用复制算法进行垃圾回收
  • serialOld
    与Serial搭配使用的老年代的串行垃圾回收器使用了标记整理算法,回收时间长
  • parNew
    新生代的垃圾回收器,使用了多线程技术,也是采用复制算法,不过线程暂停的时间会短一些,不过只能配合CMS使用
  • parallelOld
    使用了多线程技术的老年代垃圾回收器,也还是使用标记整理算法
  • parallelScavenge
    并行执行的新生代垃圾回收器,也还是使用了复制算法,不过不会暂停用户线程,可以控制吞吐量
  • CMS
    并发的老年代收集器,使用了标记清除法,效率要比标记整理算法高
    步骤分为初始标记,并发标记,重新标记,并发清除。
    由于使用了而标记清除算法,所以容易产生内存碎片,导致分配空间因空间不够出发fullGC。同时还会占用更多的CPU资源,并且还会产生浮动垃圾(在并发清除的时候产生的新的垃圾)
  • G1
    大堆垃圾回收器,不在区分新生代和老年代,会把大堆划分为不同的小的Region

各个垃圾回收器如何配合使用?

如图,其中CMS与SerialOld为什么会相连呢?
是因为CMS在进行垃圾回收的时候在并发清除会产生浮动的垃圾,也就是清除垃圾用户线程又产生了垃圾,所以需要预留一部分的空间来装浮动垃圾,如果预留的内存空间还不够装,那么就会使用SerialOld回收器触发FullGC
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章