目录
1.概述
作用:把一套程序可以在不同的平台上运行,可以实现自动内存管理,自动的垃圾回收。
jvm的主要组成部分:
jvm整体结构分为四大类:
类加载器(ClassLoader):
负责从硬盘上加载字节码文件,
运行时数据区(Runtime Data Area):
用来存储运行时数据的,5大区域:方法区,堆,栈,本地方法栈,程序计数器,
执行引擎(Execution Engine):
负责将字节码解释/编译为真正的机器码
本地方法接口(Native Interface):
负责调用操作系统的本地方法
2.类加载过程
1.类加载系统
什么是类加载?
字节码存储在硬盘上需要运行的时候,由类加载系统负责将类的信息加载到内存中(方法区),为每一个类创建一个class对象,最哦用是classloader进行加载时,充当一个快递员的角色。
2.类加载过程
2.1加载
class.forname();将硬盘字节码读入内存中,把硬盘上的结构转为内存结构
2.2连接
验证:1.验证字节码格式,是否被污染(修改)。
2.验证语法,例如类是否继承final类。
准备:在准备阶段为类中静态的变量赋初值,例如static int num=10,在准备阶段num的值初始为0,因为int的默认值为0,在后面的初始化阶段。static int num=10,则num的值为10。
在准备阶段不为静态的常量赋值。
解释:将符号引用(文件中的逻辑引用)转为直接引用(内存中的实际地址)
2.3初始化
对类中静态成员进行赋值。
有一个特例,在调用一个方法的静态常量,类不会被加载,调用静态变量的时候,类才会被加载。
类初始化:new对象,--->使用类的静态成员,反射,动态加载类class.forname(),子类被加载。
3.类加载器
类加载器:负责加载类的类。
1.启动类加载器(引导类加载器)这部分不是用java语言写的。
2.其他类加载器(这部分指的是用java语言编写的类加载器)
从程序员的角度来看:类加载器分为三层:
1.启动类加载器,复杂加载java的核心类库,比如说string类。(BootStrap classloader)
2.拓展类加载器,负责加载jre/lib/ext目录下的包 ,包含应用程序类加载器(Extension classloader)
3.应用程序类加载器,负责加载自己写的程序中的类(Application classloader)
4.双亲委派机制
为了确保类加载的正确性,安全性,类在加载的时候,采用双亲委派机制。
当需要加载程序中的一个类时,会先让加载器的父级去加载,直到最顶级的启动类加载器,如果父级找到了则返回使用,如果依然没找到,那么就委派给子级去加载,找到了就返回,如果所有的类加载器都没有找到,则报类找不到的异常。
优点:安全,避免了自己写的类替换了系统中的类,避免类的重复加载
5.双亲委派机制能否被打破?如何?,重写?
java中提供了一个ClassLoader类,这个类中定义了通过哪些方法去加载那些类。
loadClass(String classpath)//建议用这个,底层使用的是双亲委派机制加载类。
findClass(String path)//如果需要自定义,可以重写findclass()方法。
defineClass()将读到的class文件的数据,构造出一个Class对象。
再有就是像tomcat这种服务器软件,里面也是会自己定义加载类的。
6.被动/主动使用
主动使用:将类加载的整个过程完成,
new对象,使用类的静态成员,反射,动态加载类的class.forname(),子类被加载。
被动使用:不会初始化加载
访问类中的静态变量,将类作为类型,例如创建数组,用类作为类型使用。
3.运行时数据区
程序计算器:记录线程运行的位置(行号),线程需要执行切换,所以需要记录执行的位置。
虚拟机栈:运行java方法的区域,为每一个方法生成一个栈帧。
本地方法栈:java经常要调用一些本地方法(操作系统的方法 native修饰的方法,比如hashcode,read(),start(),arraycopy())
堆:存放程序中产生的对象,也是虚拟机中内存占比最大的一块
方法区:存放类信息。
堆,方法区是线程所共享的,虚拟机栈,本地方法栈,程序计数器是线程私有的,每个都有的。
堆,方法去,栈会存在内存溢出的错误
3.1程序计数
是一块内存很小的区域,主要用来记录每个线程中执行方法的位置,便于线程在切换执行时记录位置,是线程私有的,生命周期于线程一样,运行速度快,不会出现内存溢出。
3.2虚拟机栈
栈是运行单位,存储一个一个的方法,当调用一个方法时,创建一个栈帧,将方法中的信息存储到栈帧中,操作只有2个,调入方法就是入栈,方法执行完了之后,出栈,是后进先出的结构。
运行速度非常快,仅次于程序计数器。当入栈的方法过多时,可能会出现栈溢出(内存溢出)
线程独立的,不同线程之间是无法互相调用的。
栈帧的内部结构:
局部变量表:方法内生命的局部变量,方法参数。
操作数栈:运算区域例如a+b;
动态链接:调用的方法地址,字面量地址
方法返回地址:
3.3本地方法栈
本地方法:native修饰的方法,没有方法体,hashcode,read(),start(),arraycopy()都是,
不是用java语言写的,例如操作系统的方法。
如果调用的是本地方法,那么本方法在本地方法栈中运行,也会出现内存溢出。
3.4堆
创建对象,对象引用
堆空间是jvm内存中一块空间,主要用来存储对象,是jvm空间中最大的快,是线程共享的。
jvm在启动的时候,堆就创建了,大小也就确定了,但是可以通过参数来改变大小,也就是jvm调优。
在物理上是不连续的,逻辑上是连续的,堆是垃圾回收的重点区域,
堆的分区:
新生代:伊甸园区,幸存者区2个,
老年代,
新生代和老年代是1:2,伊甸园区和幸存者区是8:1:1,默认是这样,可以修改参数修改这个比例。
为什么要分区:
不同的对象,他的生命周期不同,这样可以将不同的对象存储在不同的区域,不同的区域采用不同的垃圾回收算法进行处理,扬长避短。
创建的对象在这些区域里面如何分布:
当一个对象刚被创建了,会被存放在伊甸园区,当垃圾进行回收时,会把伊甸园区中存活的对象移动到幸存者区。
幸存者区有2个额区域,幸存者1区和幸存者2区。
首先吧伊甸园区存活的对象当下一次垃圾回收来的时候,会将伊甸园区和幸存者1区的对象移动到幸存者2区,清空幸存者1。之后再次回收的时候,会将伊甸园区和幸存者2区的对象移动到幸存者1区,清空2区,交替执行。
什么时候对象去老年区:
当垃圾回收每次对对象进行标记的时候,在对象头中有一块空间会来记录被标记的次数, synchronized(obj){}
在对象头中记录的分代年龄只有4个bit位的空间,也就是说年龄最大15次。一个对象经过15次垃圾回收依然存活,就会去老年区。
分代收集思想:
jvm垃圾回收可以分为不同的区域进行回收,针对新生代会频繁的进行垃圾回收,成为young gc,对老年代较少的回收垃圾,成为old gc。
当调用system.gc(),老年代内存不足,方法区内存不足,会触发full gc(整堆收集)尽量避免整堆收集,整堆收集的时候,其他用户线程暂停的时间较长。
3.5方法区
存储类信息
类信息:方法,属性,静态常/变量,即时编译器,编译后的代码,运行时常量池(字面量的值)
方法区是线程共享的,也可能出现内存溢出,也会涉及到垃圾回收,
方法区的生命周期也是虚拟机启动就创建,虚拟机关闭销毁。
方法去 java栈 java堆
preson per=new person();
方法去大小的设置:-xx:MaxMataspaceSize,windows中方法区默认最大值是21MB,到达21mb则会触发full gc,值也可以设置为-1,没有上限,占用整个计算机内存。
方法区是会涉及到垃圾回收的:
主要回收的是静态常量,类的信息。
类信息何时被卸载,满足3个条件:
1.该类所产生的对象都被回收了,
2.该类对应的class对象,不再被其他地方引用。
3.该类对应的类加载器也被回收了。
4.本地方法接口
什么是本地方法?
使用native修饰的方法,不是java语言实现的,是操作系统实现的。
为什么要使用native methed?
1.例如需要获取硬件的一些信息,如内存地址,启动线程,IO,调用本地方法接口就很方便。
2.jvm底层进行字节码解释或编译部分也有用到c语言。
5.执行引擎
1.概述
作用:负责装载字节码文件到执行引擎中,字节码不是机器码,只是jvm规范中定义的指令码,执行引擎需要将字节码解释编译为不同平台识别的机器码
hello.java--->jdk编译工具javac--->.class文件称为前端编译,---->执行引擎编译为机器码,称为后端编译。
2.解释器:jvm运行程序时,逐行堆字节码指令进行翻译,效率低,JIT(即时编译器)堆某段代码整体编译后执行,效率高,但是整体编译需要时间。
3.为什么是半解释型半编译型
起初java中只提供了解释执行的方式,但是解释执行效率低,后来引入了编译器可以对程序执行中的热点代码进行编译,并把编译后的内容缓存起来,后期执行效率高,热点代码用计数器方式来记录。
程序启动可以通过解释器立即对代码进行解释执行,不需要等待编译,提高响应速度,之后对热点代码采用编译器编译执行,从而提高后续效率。
6垃圾回收 GC
6.1概述
java语言是提供自动垃圾回收的,c++没有自动垃圾回收,垃圾回收也不是java首创的,Java在垃圾回收这块一直在不断更新升级。
什么是垃圾?
没有被任何引用指向的对象是垃圾。
为什么要垃圾回收?
垃圾对象如果不回收,他就会一直占用空间,垃圾对象越积越多,可能导致内存溢出,对内存空间中的内存碎片进行整理,也方便给别的对象分配空间。
早期是怎么回收的?
早期像c++代码,需要程序员手动销毁垃圾对象。
不足:麻烦,程序员手动删除,有时候可能忘记删除,那么可能导致造成内存泄漏。
内存溢出:内存不够用,报内存溢出错误OOM
内存泄漏:有些对象已经不再被使用,但是垃圾回收对象又不能回收它,这种悄悄的占用内存资源的被称为内存泄漏。
现在的语言引进了自动的内存管理。
优点:降低了程序员的工作量,降低了内存溢出和内存泄漏的风险。
自动内存管理的担忧:自动的垃圾回收,降低了程序员对内存管理的能力,一旦出问题了则无法解决。
哪些区域进行垃圾回收?
主要是堆和方法区。频繁的垃圾回收新生代,较少的回收老年代,基本不回收方法区。
6.2垃圾回收算法
标记阶段算法:标记那些对象已经是垃圾。
引用计数算法(已经不再使用),对象内部有一个计数器,每当有一个引用指向,则计数器+1,每当有一个==null,则计数器-1。
实现简单,缺点:不能解决循环引用问题,需要维护计数器的空间,复制后对计数器进行更新有开销。
根可达算法(可达性分析算法)
从一组GCRoots对象(一组活跃的对象,当前栈帧中使用的对象)开始向下查找,如果与GCRoots对象相关联,那么就不是垃圾对象,否则判断为垃圾对象。
那些对象可以用来作为GCRoots对象?
1.栈帧中所使用的对象,2.静态的成员变量所指向的对象3.synchronized同步锁对象,4.jvm系统内部对象。
可达性算法可以避免对象循环引用的问题
final,finally,finalize()
finalize()是Object类中定义的方法,子类可以重写,但是自己不要主动去调用它,finalize()在对象被回收前由垃圾回收线程调用,只会被调用1次。
一般情况下不需要重写此方法,如在对象销毁前需执行一些释放资源的操作,可以重写此方法,但是需要注意,不要将对象复活或者出现死循环。
从垃圾回收角度来讲,对象可以分为三种状态:
可触及的:从根节点开始,可以到达这个对象。
可复活的:已标记为垃圾finalize()方法还没被执行(对象可能在执行finalize()后被复活)
不可触及的:当对象的finalize()方法已经被执行了,而且对象没有被符合,那么对象就进入了不可触及的状态。
对象回收的一个细节:如果一个对象第一次被标记为垃圾且finalize()没有执行,将这些对象放到一个队列中,调用他们的finalize(),如果在finalize()方法对象与GCRoots中的某个对象关联上了,从队列中移出,当第二次对象被标记为垃圾对象,那么直接就是不可触及的状态,等待被回收掉。
回收阶段算法:
复制算法:
使用到两块内存空间(对标2个幸存者区),将正在使用的区域中的存活对象,复制到另一个区间,清除原来的空间。
优点:内存碎片少
缺点:使用两块内存空间,G1垃圾回收期每个区域又分成多个小的区域,需要记录地址
标记清除算法:
清除并非是直接将垃圾对象清理,而是将垃圾对象的地址记录在一个空闲列表里面,如果有新的对象需要分配空间,那么就从空闲列表判断空间是否够用,那么就把新对象替换成垃圾对象。
优点:实现简单,不需要移动对象
缺点;会产生内存碎片
复制算法是针对于新生代,对象较少的情况下,需要移动的对象,效率高,不会产生内存碎片,但是对于老年代,对象较多的情况不适应。
标记清楚算法是针对于老年代存货对象较多,不需要移动对象。
标记压缩算法:
对于标记清楚的不足,将存活的对象进行整理,然后清除垃圾对象,这样内存就不会产生内存碎片。
标记清楚是不移动对象,产生内存碎片。标记压缩是会移动对象,不会产生内存碎片。
分代收集算法:
新生代存活对象的生命周期较短,需要频繁回收,复制算法效率高,适合于年轻代。老年代对象生命周期长,不需要频繁回收,使用标记清除和标记压缩算法。
STW:stop the world 在垃圾回收线程标记时,需在一个时间点上,让所有的用户现场暂停一下,保证判断对象是否垃圾时的准确性。
性能好的垃圾回收器发生的SWT的次数少。
6.3垃圾回收器
:是对垃圾回收的落地实现。
jvm中分为不同的垃圾回收器,根据场景选择
按照线程数量分为:
单线程垃圾回收器:垃圾回收线程只有一个,适用于小型场景。
多线程垃圾回收器:垃圾回收线程有多个同时执行,效率高
按照工作模式分为:
独占式:垃圾回收线程工作时,用户线程全部暂停STW
并发式:垃圾回收线程工作时,可以不用暂停用户线程。
按照内存工作区域分为:
年轻代区域的垃圾回收器
老年代区域的垃圾回收区
CMS:
cms之前不管是单线程还是多线程的垃圾回收器都是独占式的。
并发标记清除首创用户线程可以和垃圾回收线程并发执行,追求低停顿。
初始标记:单线程独占标记对象,是需要stw的,初始标记只是仅仅标记一下gcroots能关联到的对象,速度很快
并发标记:垃圾回收线程和用户线程并发执行,查询gcroots的各个关联对象
重新标记:使用多线程独占标记对象,,导致原先标记产生变动,需要对原先标记的记录进行修正,这个阶段也会stw
清除:垃圾回收线程和用户线程并发执行,清除垃圾
cms优点:可以做到并发收集,缺:用户和垃圾线程并发执行导致吞吐量低,无法处理浮动垃圾(垃圾回收线程并发标记时,用户线程不暂停,标记后随时有新的垃圾无法处理),只能等下一次垃圾回收处理
三色标记算法:
将对象分为不同的状态,黑色,灰色,白色
黑色:已经标记过了,不是垃圾对象,且该对象下面关联属性也标记过了.
灰色:已经被垃圾回收器标记过,但是还有没被标记过的
白色:没有被垃圾回收器扫描过,标记是垃圾。
1.刚开始确定为GCRoots的对象为黑色
2.将GCRoots直接管理的对象置为灰色
3.遍历灰色对象,下面如果还有管理的对象,灰色变黑色,黑色下面是灰色。
4.重复标记
5.将白色对象清除。
可能有漏标和错标的问题
漏标:A关联B,B关联C,B是灰色,A,B断开,但是B是灰,B和C就是浮动垃圾,需要等到下次灰色
错表:A关联B,B关联C,B是灰,B,C断开,AC连接,A是黑,不会再从A开始扫描,就会清理C
解决:将发生变化的关系进行记录,重新标记
G1
也是使用并发标记和清除的,
在g1垃圾回收器中,堆被划分成了许多个连续的区域,每个区域大小相同,在1mb到32mb之间,jvm最多支持2000个区域,区域的大小在jvm初始化的时候就确定了,可以用heapreginsize来设置。
将整个堆的每个区域划分为更小的空间,回收时可以根据每个区间的优先级(由里面的垃圾数量决定)先回收优先级高的区间
降低了用户线程的停顿,提高了吞吐量。
堆整堆进行同意的管理,没有年轻代和老年代。
回收流程:包含四部分
stab:在gc之前对堆内存的对象做一次快照,此时活的对象就认为是活的。
remerberset:每个区都有一个set,记录进入该区块的对象引用。就不用扫描区,扫描set。提升效率
1.新生代gc:和其他垃圾回收器是一样的。伊甸园区和幸存者2区的移动到幸存者1区,往复收集或者晋升到老年代。
2.并发标记周期:主要是标记可回收的对象,回收完全空闲的内存:
初始标记:标记gcroots可达的对象,会有一次younggc和stw。
根区域扫描:因为进行了一次younggc,伊甸园区已经没有对象了,扫描幸存者区到老年代的引用
并发标记:扫描并查看整个堆的存活对象,做好标记
重新标记:stw,使用satb,在标记之初为存活对象创建一个快照,加快重新标记速度
独占清理:计算各个区域存活对象和回收比例进行排序,识别混合回收的区域,更新remberset,给需要混合回收的区域做标记,会发生stw
并发清理:识别并清理空闲空间。
3.混合回收:进行younggc,回收之前标记垃圾最多的区域。
1.说一下堆栈的区别?
1、申请方式的不同。栈由系统自动分配,而堆是人为申请开辟;
2、申请大小的不同。栈获得的空间较小,而堆获得的空间较大;
3、申请效率的不同。栈由系统自动分配,速度较快,而堆一般速度比较慢;
4、存储内容的不同。栈在函数调用时,函数调用语句的下一条可执行语句的地址第一个进栈,然后函数的各个参数进栈,其中静态变量是不入栈的。而堆一般是在头部用一个字节存放堆的大小,堆中的具体内容是人为安排;
5、底层不同。栈是连续的空间,而堆是不连续的空间。
2.内存泄漏和内存溢出
内存溢出:内存不够用,报内存溢出错误OOM
解决办法:资源性对象在不使用的时候,应该调用它的close()函数将其关闭掉,就是及时的释放不需要使用的资源
内存泄漏:有些对象已经不再被使用,但是垃圾回收对象又不能回收它,这种悄悄的占用内存资源的被称为内存泄漏
解决办法:增加内存的容量,可以在配置里面改。
3.讲一下对象的创建过程
1)类加载检查:具体来说,当 Java 虚拟机遇到一条字节码 new 指令时,它会首先检查根据 class 文件中的常量池表(Constant Pool Table)能否找到这个类对应的符号引用,然后去方法区中的运行时常量池中查找该符号引用所指向的类是否已被 JVM 加载、解析和初始化过
如果没有,那就先执行相应的类加载过程 如果有,那么进入下一步,为新生对象分配内存 2)分配内存:就是在堆中给划分一块内存空间分配给这个新生对象用。具体的分配方式根据堆内存是否规整有两种方式:
堆内存规整的话采用的分配方式就是指针碰撞:所有被使用过的内存都被放在一边,空闲的内存被放在另一边,中间放着一个指针作为分界点的指示器,分配内存就是把这个指针向空闲空间方向挪动一段与对象大小相等的距离 堆内存不规整的话采用的分配方式就是空闲列表:所谓内存不规整就是已被使用的内存和空闲的内存相互交错在一起,那就没有办法简单地进行指针碰撞了,JVM 就必须维护一个列表,记录哪些内存块是可用的,在分配的时候从列表中找到一块足够大的连续空间划分给这个对象,并更新列表上的记录,这就是空闲列表的方式 3)初始化零值:对象在内存中的布局可以分为 3 块区域:对象头、实例数据和对齐填充,对齐填充仅仅起占位作用,没啥特殊意义,初始化零值这个操作就是初始化实例数据这个部分,比如 boolean 字段初始化为 false 之类的
4)设置对象头:这个步骤就是设置对象头中的一些信息
5)执行 init 方法:最后就是执行构造函数,构造函数即 Class 文件中的 () 方法,一般来说,new 指令之后会接着执行 () 方法,按照构造函数的意图对这个对象进行初始化,这样一个真正可用的对象才算完全地被构造出来了
4.java程序的运行过程
java代码经过编译器被编译成字节码文件后,通过类加载器加载到内存中,才能被实例化,然后到jvm中编译执行,最后在操作系统操作cpu后得到结果
5.讲一下年轻代和老年代
年轻代和老年代都是java虚拟机堆中的区域,年轻代分为伊甸园区和2个幸存者区,暂且先叫做幸存者1区和幸存者2区吧。 年轻代和老年代的比例是1比2。年轻代中伊甸园区和幸存者区的比例是8比1比1。 年轻代中通常存放的是新创建的对象,有的比较大的新创建的对象会直接进去老年代的。对象头中有一个对象的年龄标记。每次被垃圾回收一次存活之后,年龄标记+1,最大是15。因为对象头只有4个比特位。意味着。年轻代的对象最多经过15次垃圾回收就会进入老年代。 年轻代中的垃圾回收第一次创建的对象在伊甸园区,经过垃圾回收之后还存活着,就把这些对象和幸存者2区原本的对象都移动到幸存者1区,清空幸存者2区。下一次都移动到2区,清空1区。依次循环。这样是减少内存碎片。 年轻代中的垃圾回收是younggc,垃圾回收的频率高。老年代的垃圾回收是oldgc 垃圾回收的频率少。
6.为什么2个幸存者区。
两个幸存者区,每次只有一个有东西,另一个是空的,这样减少了内存碎片化。如果分为更多的幸存者区,这些幸存者区的容量会变小,容易导致幸存区满了。2个刚合适。
7.如果我们不想用双亲委派模型怎么办?(打破双亲委派)
为了避免双亲委托机制,我们可以自己定义一个类加载器,然后重写 loadClass() 即可。
1)继承ClassLoader 表明这是一个类加载器 2)重写findClass()方法 重写找到类的方法 3)调用defineClass()方法 把字节码转化为Class
8.gc算法有哪些
标记阶段算法,根可达性算法。回收阶段的复制算法,标记清除算法,标记压缩算法。 可达性算法是判断是不是垃圾的算法。 从一组gcroots对象(一组活跃的对象,当前栈桢中使用的对象)开始想下找,如果与gcroots相关联,这就不是垃圾。否则就是垃圾。 gcroots对象可以是1.栈桢中使用的对象2.静态的成员变量所指向的对象3.synchronized修饰的对象4.jvm系统内部的对象。 可达性算法可以避免对象的循环引用问题。 复制算法,有两块空间,对标的是两个幸存者区。将正在使用的区域的存货对象复制到另一个空间,清空原来的空间。 优点,没有内存碎片 缺点,使用两块内存,g1垃圾回收器每个区域又分为多个小的区域,需要记录这些地址。 标记清除算法,算法分为标记和清除两部分,先标记出所有的垃圾对象,然后清理,或者标出所有的存活的对象。统一回收未标记的对象。 缺点,堆中的对象很多,需要进行大量的标记和清除步骤。效率会低。还会产生内存碎片。 优点,实现简单,不需要移动对象。 标记压缩算法,标记的过程和标记清除一样。标记完了之后,会把存活都移动到一端,然后清理这部分以外的内存。就不会产生内存碎片了。
9.fullgc
老年代和新生代空间都满了,会出发fullgc,整堆收集垃圾。fullgc会出发stw,短暂的暂停所有的用户线程
10.如何判断对象可以回收
是不是垃圾的标准就是没有任何引用指向他。 jvm标记垃圾时有两个算法,第一个是引用计数算法,还有一个是根可达性算法。 引用计数算法是每个对象自带一个计数器,每当有一个引用指向这个对象,计数器就+1,每次有引用指向null,则计数器-1。在计数器为0的时候可以认为这是个垃圾。 引用记数法已经不常用了,他不能解决循环引用问题,就是a指向b,b指向a,但是他们都没用,却不能都被回收。 跟可达性算法是从一组gcroots对象,开始往下找,如果和这些对象有链接,则就不是垃圾,如果没有链接,就认为是垃圾。 gcroots是一组活跃的对象,可以是,被synchronized锁住的对象,虚拟机栈中引用的对象,方法区中类静态属性引用的对象,方法区中常量引用的对象,jvm中使用的对象。
根可达性算法中的不可达对象并不是立即死亡的,对象拥有一次复活的机会。对象被系统宣告死亡至少要经历两次过程,第一次是根据跟可达性算法判断他是不可达对象,第二次对象在销毁之前会执行finalize方法 在方法内如果重新拥有和gcroots对象的连接,他就又是可达的,就被复活了。如果没有执行finalize方法,则对象就死了。 finalize方法不建议去重写容易出事情。
11垃圾回收器
串行的垃圾收集器有两种,Serial与Serial Old,一般两者搭配使用。新生代采用Serial,是利用复制算法;老年代使用Serial Old采用标记-整理算法。
并行的有:ParNew,是Serial收集器的多线程版本,默认开启的收集线程数和cpu数量一样,运行数量可以通过修改ParallelGCThreads设定。用于新生代收集,复制算法。
Parallel Scavenge: 关注吞吐量,吞吐量优先,吞吐量=代码运行时间/(代码运行时间+垃圾收集时间),也就是高效率利用cpu时间,尽快完成程序的运算任务
Parllel Old:Parallel Scavenge的老年代版本。
cms
g1(garbage first)
12.介绍一下cms
cms之前不管是单线程还是多线程的垃圾回收器都是独占式的。
并发标记清除首创用户线程可以和垃圾回收线程并发执行,追求低停顿。
过程:
1.初始标记过程,耗时短,仅仅标记GCRoots能直接关联到的对象,会发生stw 2.并发标记过程,耗时长,不会发生swt,从初始标记的对象中遍历整个对象的联系图。 3.重新标记,耗时短,会发生stw,修正并发标记期间用户线程导致的标记变动的记录4.并发清理,会产生浮动垃圾,不会发生stw,并发失败,启用serial old ,停顿的时间更长了
cms优点:可以做到并发收集,缺:用户和垃圾线程并发执行导致吞吐量低,无法处理浮动垃圾(垃圾回收线程并发标记时,用户线程不暂停,标记后随时有新的垃圾无法处理),只能等下一次垃圾回收处理 为什么CMS的GC线程可以和用户线程一起工作。是三色标记算法 CMS为了让GC线程和用户线程一起工作,回收的算法和过程比以前旧的收集器要复杂很多。究其原因,就是因为GC标记对象的同时,用户线程还在修改对象的引用关系。因此CMS引入了三色算法,将对象标记为黑、灰、白三种颜色的对象,并通过「写屏障」技术将用户线程修改的引用关系记录下来,以便在「重新标记」阶段可以修正对象的引用。
13.介绍一下g1
也是使用并发标记和清除的
将整个堆的每个区域划分为更小的空间,回收时可以根据每个区间的优先级(由里面的垃圾数量决定)先回收优先级高的区间
降低了用户线程的停顿,提高了吞吐量
堆整堆进行同意的管理,没有年轻代和老年代。
G1 回收过程,G1 回收器的运作过程大致可分为四个步骤:
1.初始标记(会STW):仅仅只是标记一下 GC Roots 能直接关联到的对象,并且修改TAMS指针的值,让下一阶段用户线程并发运行时,能正确地在可用的Region中分配新对象。这个阶段需要停顿线程,但耗时很短,而且是借用进行Minor GC的时候同步完成的,所以G1收集器在这个阶段实际并没有额外的停顿。
2.并发标记:从 GC Roots 开始对堆中对象进行可达性分析,递归扫描整个堆里的对象图,找出要回收的对象,这阶段耗时较长,但可与用户程序并发执行。当对象图扫描完成以后,还要重新处理在并发时有引用变动的对象。
3.最终标记(会STW):对用户线程做短暂的暂停,处理并发阶段结束后仍有引用变动的对象。
4.清理阶段(会STW):更新Region的统计数据,对各个Region的回收价值和成本进行排序,根据用户所期望的停顿时间来制定回收计划,可以自由选择任意多个Region构成回收集,然后把决定回收的那一部分Region的存活对象复制到空的Region中,再清理掉整个旧Region的全部空间。这里的操作涉及存活对象的移动,必须暂停用户线程,由多条回收器线程并行完成的。
14.常用的jvm参数
xms4g设置堆内存大小 printgcdetails打印gc日志 newsize年轻代最小空间 maxnewsize年轻代最大空间 newratio年轻代和老年代比例默认1比2 surviceratio伊甸园区和幸存者区的比例8比1比1 permsize永久代最小空间大小 mazpermsize永久代最大空间。
15.对象一定在堆中吗
大部分新生成的对象都在堆中,很少特例在栈中,jvm通过逃逸分析 分析新对象的使用范围,就可能将对象分配到栈里。在栈里分配可以快速的在栈桢上创建和销毁对象 会提升性能。听过,没仔细了解
16.设计一个垃圾回收器
并发执行,充分利用cpu的多核的优势,提升性能。 有高的吞吐率 吞吐量 很短的停顿时间 垃圾回收的频率恰当。 吞吐量和停顿时间是成反比的。