JVM是JAVA虚拟机,将程序文件转变为.class字节码文件,然后通过JVM处理到各个操作系统平台。
class文件加载过程
加载:查找和导入class文件
验证:验证里面的字节码文件是否符合要求
准备:给字节码文件里面的对象变量分配空间
解析:将符号引用变为直接引用
初始化操作
类加载器
类加载器-启动类加载器-拓展类加载器
双亲委派机制
双亲委派:当一个类收到类加载器加载请求时,它会将这个类加载请求给他的父类加载器,只有当父类加载器不能完成时,子类加载器才会进行类加载。
双亲委派的作用时保证不同类加载器加载的都是同一个对象。
JVM内存模型
- 程序计数器:是非常小的内存空间,保存着当前线程JVM指令执行的位置。由于JAVA虚拟机多线程是通过线程轮流切换并分配各线程执行时间来实现的,因此每个线程都应该有其私有的程序计数器。该内存区域是JAVA虚拟机规范中唯一没有OOM的。
- 虚拟机栈:与程序计数器一样,虚拟机栈也是线程私有的,生命周期与线程相同。虚拟机栈是描述JAVA方法执行过程的内存模型。每个方法被执行时都会产生一个栈帧,用来存储方法局部变量表(包含编译期间的基本数据类型以及对象引用等),方法出口等信息。入栈以及出栈即对应方法的调用到执行完毕的过程。
在JAVA虚拟机规范中,一个栈对应的栈帧数量数固定的,但是每一个栈帧所对应的堆内存是可以动态拓展的。
当栈帧的数量超过限制是会造成stackoverflow:递归调用
当栈帧对应分配的内存太大时会造成outofmemory:在方法执行过程中不断new出来对象且没有被垃圾回收 - 本地方法栈:与虚拟机栈一样,只不过本地方法栈是针对native方法。
- 堆内存:对大多数应用来说,堆内存是最大的一部分,几乎所有new出来的对象都要在堆上分配内存。是各个线程共享的内存区域。
- 方法区:与堆内存一样,是各个线程共享的内存区域。存储了类信息,运行时常量池,静态变量等。
永久代与元空间
在JAVA8之前方法区的实现是采用永久代实现的,永久代的垃圾回收与老年代是绑定的,对其中一个进行垃圾回收都会触发另外一个的垃圾回收。JAVA8时方法区采用元空间进行实现。元空间属于本地内存,将永久代中的类信息放在了元空间中,将字符串常量池,静态变量放在了堆内存中。
为什么要移除永久代
1.字符串存放在永久代中容易产生内存溢出问题。
2.对于一些类信息难以确定其大小,对于永久代大小的指定比较困难。
3.永久代与老年代的垃圾回收时绑定的,比较麻烦。
JVM如何判断一个对象能否被GC,可以被视为root的都有哪几种类型?
- 引用计数法。即当对象被引用一次就+1,如果失效了就-1,变成了0之后就可以判断可以被垃圾回收。但JVM并没有采用这种方式,因为不能很好的解决循环利用。比如A与B互相引用,但程序已经没有再引用A和B了。
- 引用链法。通过一种GC ROOT对象来进行判断,GC ROOT代表那种不能够删除的对象。如果对象有一条链能够到达该GC ROOT则判断该对象不能进行回收,反之则可以回收。
可以作为GC ROOT的对象:
System Class:由系统类加载器(System Class Loader)加载的类。自定义类加载器加载的类不一定是 GC ROOT对象。
方法区中的类静态属性引用对象
方法区中的常量引用对象
强软弱虚引用的区别以及GC对他们执行怎样的操作?
强引用:在程序中普遍存在,一般为直接引用。永远都不会对其进行垃圾回收。
bject object = new Object();
String str = "hello";
软引用:用来描述一些还有用但非必须的对象。内存不足时会对其进行回收。
弱引用:用来描述非必须对象。会被垃圾回收。
虚引用:虚引用的存在不会对对象的生存时间构成任何影响,为一个对象设置虚引用的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。
垃圾回收算法?
1.标记清除算法。从GC ROOT 开始采用引用链法标记引用链上对象,然后清除未标记的对象。
优点:没有产生额外的内存消耗,内存利用率高。
缺点:回收的垃圾对象并不一定是连续的,因此导致存在不连续的空间,产生内存碎片。
2.标记整理算法。开始同样从GC ROOT开始采用引用链法标记引用链上的对象,然后将标记的对象移动到内存一侧。将剩下的空间回收。
优点:解决了标记清除算法产生的内存碎片问题。
缺点:效率较低。
3.复制算法。把内存空间一分为二,每次只使用其中的一块。当进行GC时,将存活着的对象复制到另一块上。然后把已使用过的这一块内存清理。
优点:解决内存碎片问题,效率较高。
缺点:将内存的可用大小压缩了一半。
** 4.分代回收。详情见下。**
堆内存为什么要分为新生代与老年代?
堆内存被分为新生代与老年代。新生代又被细分为Eden和Survivor区,最后Survivor由FromSpace和ToSpace组成。发生在新生代的GC为Minor GC 。在老年代中的GC则为Major GC。
新生代一般存放很快就要被GC(垃圾回收)的对象,新生代采用的复制算法。在Minor GC时会将还存活的对象放到一个Survivor区中,然后对Eden与另外一个Survivor进行清理。
老年代一般存放经历了多次GC依然存活的对象。老年代一般采用标记清除或者标记整理算法。
新生代各区的比例?
Eden-Survivor 8 :1 :1。 对象先放在Eden区中,当快满了触发Minor GC时。采用复制算法将Eden区与Survivor中from区的内存复制到to区。按照这个比例和复制算法可以比较高效的利用内存与解决内存碎片的问题。
新生代怎么进入老年代?
1.分配担保机制。当Minor GC时,一个Survivor区存放不了那么多时就会进入老年代。
2.超过所设置的内存大小的对象会进入老年代。
3.当进行一次Minor GC后,还存在的元素就会+1,一般当到所设置的临界点时会进入老年代。
垃圾收集器?
https://www.cnblogs.com/chenpt/p/9803298.html
单线程垃圾收集器:采用单线程进行垃圾回收,当在垃圾回收过程中,会暂停所有工作线程,也是就说是一直STW的。
多线程垃圾收集器:采用多个线程进行垃圾回收,提高效率,但是同样在垃圾回收过程中,其他线程一直是STW。
多线程并发垃圾收集器:比如CMS垃圾收集器。在进行垃圾回收过程中,其他线程一样的进行工作,降低了STW时间。
到现在是G1垃圾收集器。
CMS垃圾收集器:
老年代收集器,采用标记-清除算法与多线程并发的垃圾回收方式降低了STW时间。
垃圾回收过程:
初步标记:标记GC ROOT直接可达对象,这是STW的。耗时较短
并发标记:垃圾回收线程与其他工作线程一起工作。进行可达性分析,耗时较长
重新标记:标记并发过程中的可达对象,这也是STW的
并发清除:与其他线程一起工作清除垃圾。
缺点:
使用标记-清除算法,可能会产生碎片。
不能处理浮动垃圾。因为采用并发清除 在标记后 清除前 产生的垃圾在这一次垃圾回收过程中不能被回收。
G1收集器:
克服了CMS收集器产生垃圾碎片的缺点,其次他可以精确的控制停顿时间,也就是STW时间。
G1垃圾收集器引入了分区的思想,弱化了分代的概念。它将内存空间分成很多区,将这些区域分成新生代Eden区与survivor区,老年代,还有一个区单独存放那些内存很大的对象,一般是大于二分之一个region区。G1收集器将对象从一个区复制到另外的区,避免了内存碎片这个问题。然后引入了一个remember set 记录引用信息,这样就不必进行整堆扫描了。最后G1垃圾收集器会判断哪一个区最具有回收价值,对其进行回收。
初始标记
并发标记
重新标记
筛选清除:选择最具有垃圾回收价值的分区进行回收。
G1垃圾回收器为什么可以控制停顿时间。
因为垃圾回收器在最后进行回收时会对每个分区进行排序,选择最具有回收价值的分区回收。因为其他垃圾收集器都是回收整个垃圾因此时间是不可控的,而G1垃圾收集器进行筛选回收的话,可以设定一定的垃圾回收时间。
ZGC垃圾回收器
JDK11新加入的垃圾收集器,采用动态Region内存布局,使用了读屏障、染色指针、多重映射等方式实现可并发的标记-整理算法垃圾收集器。最大的特点是停顿时间短,将停顿时间控制在了10ms内。可回收TB级的内存(4TB)。
- 动态Region:分为小型region、中型region、大型region存放不同大小的对象。
- 染色指针:和以往的标记算法不同,CMS和G1会在对象的对象头进行标记,而ZGC采用指针来标记对象。并且在并发的可达性分析过程中采用三色标记来标记对象是否被收集过。
- 多重映射:将不同的虚拟内存地址映射到同一物理地址。
- 并发标记:遍历对象图做可达性分析,与G1,CMS一样在标记开始与标记结束阶段都会停顿。
- 并发预备重分配。在这个阶段会根据特定条件判断需要被垃圾回收的region,将这些region组成重分配集。
- 并发重分配。把重分配集中的存活对象复制到其他region区,并维护一个forward table来记录旧对象与新对象之间的转向关系。
- 并发重映射。修正整个堆中指向所有重分配集中旧对象的引用。
优点
低停顿:几乎所有线程都是并发的,STW时间很短。
内存小:没有写屏障,或者remember set表类似的东西。
并发的标记整理算法,不会产生内存碎片。
缺点
浮动垃圾
ZGC目前只在Linux/x64上可用。
解决方案
从根本上还是只能通过引入分代思想。针对某一区域进行更频繁更快的垃圾回收。
Minor GC 与Full GC
当eden区内存不足的时会Minor GC Minor GC一直是STW的。
当老年代内存不足时会Full GC
当新生代中进入老年代的内存很大时可能会Full GC
调用System.gc Full GC
调优参数
- Xms:初始堆大小
- Xmx:最大堆大小
- Xmn:新生代大小