文章目录
- 一、JVM内存结构
- 二、垃圾回收算法
- 三、哪些对象可以作为gcroots
- 四、类加载过程
- 五、对象的创建过程
- 六、强软弱虚引用?
- 七、常见的垃圾收集器
- [八、详细介绍 CMS 垃圾回收器](#八、详细介绍 CMS 垃圾回收器)
- 九、类加载器分类
一、JVM内存结构
jvm虚拟机在执行java程序的过程中他会把自己管里的内存,划分为5个不同的区域。jvm内存结构可以分为五个部分:
- 程序计数器: 是一块内存很小的单元,是当前线程所执行的字节码行号指示器。分支循环跳转,线程恢复异常处理都需要依赖这个计数器。是线程私有的。如果线程执行的是java方法,则计数器记录这在执行的虚拟机字节码指令的地址如果是执行本地(Native)方法则这个计数器为空undefined,jvm内存中唯一一个没有oom以及gc的区域。
- java虚拟机栈: 他也是线程私有的,生命周期和线程一样。每个方法的执行jvm都会与之对应创建一个栈帧用于存放局部表量表,操作数栈,动态链接,方法返回地址,以及附加信息。方法的执行对应的进栈操作,方法结束对应出栈操作。
- 本地方法栈: 与java虚拟机栈执行原理相同,不同的是他们执行的方法不同,java虚拟机执行的是java方法,本地方法栈执行的是本地方法。
- java堆: 它是jvm所管理的内存中最大的一块,是线程共享的区域,该区域的目的就是用于存放对象实例,几乎所有的对象都在堆上分配内存,逃逸分析技术日益强大,栈上分配,标量替换优化等手段导致一些微妙变换,在堆上分配内存存放对象不那么绝对。堆也是垃圾收集器管理的内存区域,基于分代收集理论,java堆被分为新生代,老年代(默认比例大小1:2),永久代(1.8元空间,脱离虚拟机管理,使用本地内存),新生代分为伊甸园区,from survivor to survivor默认比例8:1:1。堆内存可以实现固定大小也可以扩展(参数:-Xmx和-Xms)。
- 方法区: 与堆一样也是线程共享的内存区域,它用于存储已被jvm加载的类的信息,常量,静态变量,即时编译器编译后的代码缓存等数据,jvm规范中把方法区作为堆的一个逻辑部分。
二、垃圾回收算法
-
标记清除算法(老年代):最基础最早出算的垃圾收集算法,和他的名字一样该算法分为两份阶段
1.标记阶段:标记存活对象通过gcroots根节点出发标记所有从根节点开始的可达对象。未被标记的对象就是未被引用的垃圾对象。标记过程就是对象是否为垃圾对象的一个判定过程。
2.清除阶段:回收未被标记的对象。
主要缺点:1.执行的效率不稳定,当java堆中包含大量对象的时候,而且其中大部分对象是要被回收的。这是必须进行大量的标记和清除动作导致标记和清除这两个过程随着对象数量的增加而效率降低。
2.内存会产生碎片化问题,标记清除之后java堆内存会产生大量的不连续的内存碎片,空间碎片太多导致程序在运行的过程中需要分配较大的对象时,无法找到足够连续的的内存空间而触发另一个gc。
3.进行垃圾回收时需要停止用户线程Stop The World现象,导致用户体验差。
优点:算法简单 -
复制算法(新生代):复制算法的诞生是基于标记清除算法面对大量对象回收时效率低的问题。它的思想就是:将可用的内存按容量大小划分为大小相同的两块,每次只使用一块,当另一块用完时,就将存活的对象复制到另一块内存,然后在清理已使用的那一块。
主要缺点:1.将可用内存一分为二,每次只使用一块造成空间的浪费。
2.在对象存活率较高(如老年代的对象)时复制算法需要进行大量复制操作,效率会降低。所以老年代不适用复制算法,使用标记整理算法
主要优点:1.没有标记清除阶段,实现简单,运行高效。
2.复制过去能保证内存空间的连续性,不会出现碎片化问题。
-
标记整理算法(老年代):标记阶段还是和标记清除算法的标记阶段一样,整理阶段让所有存活对象都向内存空间一端移动,然后直接清除边界以外的内存。标记清除算法与标记整理算法的本质区别:前者是非移动式的后再是移动式的回收算法
优缺点: 是对标记清除算法和复制算法的一种折中方案。 -
增量收集算法:如果一次性将所有垃圾进行收集处理,那么将造成系统长时间的停顿。我们可以让垃圾收集线程和用户线程交替执行,每次进行一部分的gc,然后切换到用户线程反复执行,直到gc完成。
优点: gc和用户线程并行,减少系统的停顿时间。
缺点: 上下文切换消耗时间,使得gc的总成本上升,造成系统吞吐量下降。 -
分代算法:按照对象的存活时间即生命周期将堆内存分为新生代,老年代。采用分代收集理论进行垃圾对象的回收操作。而不用整个堆进行回收。
三、哪些对象可以作为gcroots
- 虚拟机栈(栈帧中的本地变量表)中的引用对象,如各个线程被调用的方法堆栈中使用到的参数,局部变量,临时变量。
- 在方法区中类静态属性引用对象,如java类的引用类型静态变量。
- 方法区中常量引用的对象,如字符串常量池里的引用。
四、类加载过程
所谓类加载过程就是将我们的java原文件通过javac编译之后的产生的字节码文件。字节码文件(.class文件)加载到jvm内存的过程称为:类加载过程,(加载到内存的类称为运行时类,运行时类就成为Class的一个实例)该过程让jvm去执行字节码指令的过程。
jvm中类加载的过程包括:加载,链接(验证,解析,准备),初始化这五个阶段。
- 加载:是类加载的第一个阶段,他要完成三件事:
1.通过一个类的全限定名来获取该类的二进制字节流。
2.将这个字节流所代表的静态存储结构转化为方法区的运行时结构。
3.内存中生存一个这个类的Class对象,作为方法区这个类的数据访问入口。 - 验证:验证时链接的第一个步骤,这个阶段目的是确保Class文件的字节流包含的信息符合java虚拟机规范,保证这些信息不会危害虚拟机自身的安全。其中包括:文件格式检验,元数据检验,字节码检验,符号引用检验。cafe baby魔术。
- 准备:该阶段是正式为类中定义的变量(静态变量被static 修饰的变量)分配内存并设置类变量的初始值。public static int a = 1;那么准备阶段他会把a赋值为0而不是1;
- 解析:该阶段时java虚拟机将常量池内的符号引用替换为直接引用的过程。
- 初始化阶段:是类加载过程的最后一个阶段,初始化阶段就是执行类构造器clinit方法的过程比如说刚才的a这个类变量就会复制为1。
五、对象的创建过程
- 当jvm遇到一条字节码new指令时,首先会检查这个指令的参数是否能在常量池重定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载,解析,初始化。如果没有则先进行类的加载过程。
- 类加载检查通过后 ,jvm为新生对象分配内存。对象所需的内存在类加载完成后可确定。为对象分配内存空间大小实际上是那一块确定大小的内存块从java堆划分出来。(1指针碰撞,2空闲列表 )两种方式选取合适的空间大小是根据java堆是否规整来决定的。java堆是否规整又由垃圾收集器是否带有压缩整理的能力决定。
并发问题的处理:例如:用指针碰撞时,如果F和G同时需要分配内存,F和G的大小不同,他们同时获取到了指针位置,同时移动指针为对象分配内存空间这样就会出现并发问题。
空闲列表也会有并发问题: jvm当然有方法解决并发问题,两种方法:
1.cas+重试机制:就是通过cas操作移动指针,只有一个线程可以移动成功,移动失败的线程重试,直到成功为止。
2.TLAB(thread local Allocation buffer)本地线程分配缓冲(默认)。
本地线程分配缓冲:这种方式设计思想很简单,就是当线程开启时,就为每个线程分配一块较大的空间,然后线程内部创建对象的时候就从自己的空间分配,这样就不会有并发分配问题了。如果线程自己的空间用完了,那就要从堆中内存分配了,从而转为cas+重试机制解决并发问题。以上是对象想创建的第二步,内存分配。 - 初始化: 就是对分配的这一块内存初始化为零值,也就是给实例对象的成员变量赋值为零值,引用类型为null,int类型赋值为0等等操作。
这样的话,对象就可以在没有赋值情况下使用了,只不过访问对象的成员变量都是零值。 - 设置对象头: 在说道对象头这一块,先来看看对象的结构。对象分为对象头,实例数据区,对齐填充位。设置对象头,就是设置对象头中对象的hashcode,分代年龄,锁状态,类元数据指针等信息。对象头设置好了之后就可以执行对象的一些初始化方法了。
- 执行初始化方法: 这一步,jvm会给对象的成员变量设置程序员指定值的初始值,并且会执行构造方法。
六、强软弱虚引用?
JDK1.2之后java对引用这个概念进行扩充,将引用分为强软弱虚引用,引用强度依次减弱。
- 强引用:平时new出来的对象,只要有引用在即使发生GC也回收不了。
- 软引用:用来描述一些还有用但是非必须的对象。空间不够就回收,软引用适合做缓存,空间足够就放在那里,不够用就回收。
- 弱引用:用来描述那些非必须对象,gc开始工作无论内存是否足够,都会回收弱引用关联的对象 在jdk1.2之后提供WeakReference类来实现弱引用。
- 虚引用:也称幽灵引用和幻影引用是最弱的引用关系。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统的通知。
七、常见的垃圾收集器
- Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点 是简单高效;
- ParNew收集器 (复制算法): 新生代收并行集器,实际上是Serial收集器的多线程 版本,在多核CPU环境下有着比Serial更好的表现;
- Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效 利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高 效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不 高的场景;
- Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;
- Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;
- CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集 器,以获取最短回收停顿时间为目标的收集器,具有高并发低停顿的特点,追求最 短GC回收停顿时间。
- G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是 JDK1.7提供的一个新收集器,G1收集器基于标记-整理算法实现,也就是说不会 产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。
八、详细介绍 CMS 垃圾回收器
CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。在启动 JVM 的参数加上"- XX:+UseConcMarkSweepGC"来指定使用 CMS 垃圾回收器。
CMS 使用的是标记-清除的算法实现的,所以在 gc的时候回产生大量的内存碎 片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。
九、类加载器分类
- 启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一部分,用来加载 Java_HOME/lib/目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库;
- 其他类加载器:
-
- 扩展类加载器(Extension ClassLoader):负责加载\lib\ext目录或Java. ext. dirs系统变量指定的路径中的所有类库;
- 应用程序类加载器(Application ClassLoader)。负责加载用户类路径 (classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
双亲委派模型:如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。
当一个类收到了类加载请求时,不会自己先去加载这个类,而是将其委派给父类,由父类去加载,如果此时父类不能加载,反馈给子类由子类去完成类的加载。