JVM体系结构及垃圾清理

一、JVM体系结构

1.类加载子系统
在这里插入图片描述
首次运行类时进行加载—连接—初始化。

  • 加载(Loading):加载类文件。通过引导类加载器(Bootstrap)、扩展类加载器(Extension)、应用类加载器(Application)来完成类的加载

1.引导类加载器:在源码中没有这个类加载器,是空。仅仅是给coder看的
2.扩展类加载器:必须存在。有三层继承关系,最上层是ClassLoader抽象类,定义了一些加载模板,采用模板方法模式。核心类在于两个方法:defineClass、loadClass。给子类复写的是findClass方法
把类加载解析后生成运行时常量池,放在方法区。
@Deprecated
protected final Class<?> defineClass(byte[] b, int off, int len)
throws ClassFormatError
{
return defineClass(null, b, off, len, null);
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}

  • 连接(Linking):连接操作,验证(字节码是否正确)准备(为静态变量分配内存空间)解析
  • 初始化:初始化所有静态变量和静态代码块

2.运行时数据区(Runtime Data Areas)

在这里插入图片描述

运行时数据区包括:

  • 方法区:存储所有类级别的数据,是多个线程共享的。
    JDK1.8之前使用永久带代实现,1.8之后使用元空间实现
  • Heap堆区:所有的对象、变量、数组都存在该区域,只有一个且是线程共享的。
    堆在逻辑上分为新生代和老年代,是垃圾回收器管理的主要区域。
  • 虚拟机栈:每个线程运行时都会创建一个运行时栈,每个方法是一个栈帧,栈帧中包括局部变量表、操作数栈、动态链接(指向常量池的指针)、方法返回值等信息。线程私有的
    每个线程只有一个活动栈帧,当前执行的方法就是活动栈帧。

有关栈帧

  1. 局部变量表:对于基本类型直接存储值,引用类型存储它的对象引用。局部变量表的大小在编译器就可以决定,因此在运行期局部变量表的大小是不会改变的。
  2. 操作数栈:当一个方法开始执行时,操作数栈是空的。随着操作的进行会从局部变量中复制所需要的数据放入操作数栈,计算完成后将栈中的结果出栈给局部变量表或者返回给调用者。
  3. 动态链接:指的是运行时常量池的引用。在class文件中,描述一个方法调用或访问其他成员变量时通过符号引用来表示。动态链接的作用是将这些符号引用所表示的方法转换为实际方法的直接引用。
  4. 方法返回值:调用方法的返回,包括正常返回和异常返回。在方法退出后都需要返回到方法调用的位置,程序才能正常执行。
  • 程序计数器:每个线程都有一个单独的计数器,用于保存当前要执行的指令号。一旦指令执行,就会移动到下一条指令号。线程私有的。

1.程序计数器是运行时数据区唯一一个不会发生内存泄漏的区域。
2.JVM通过指令 javap -v test.class反解析获得。
3.执行的过程
计数器读入要执行的指令号
CPU获取计数器中的指令号
CPU根据获取到的指令号执行对应的指令
计数器更新要执行的JVM指令号

  • 本地方法栈:线程安全的,执行非java的方法,比如c/c++。每个线程都有一个自己的本地方法栈,用于保存本地方法信息。
    虚拟机栈服务的是java方法,本地方法栈服务的是native方法。

3.执行引擎
在这里插入图片描述
执行引擎是用来执行字节码,对字节码进行优化,然后执行GC进行垃圾清理

二、Java中的四种引用

在JDK1.2之后,将引用分为强引用、软引用、弱引用和虚引用四种,这四种引用的强度依次递减。

1.强引用(Reference)

强引用是代码中普遍存在的,类似于我们在代码中使用new创建对象。

如果一个对象具有强引用,那垃圾回收器是不会回收它的。当内存空间不存时,虚拟机宁愿抛出 OutOfMemoryError 错误,使程序异常终止,也不会靠随意回收具有强引用的对象来释放内存空间。

2.软引用(SoftReference)

用来描述一些有用但不是必须的对象。如果一个对象只具有软引用,内存空间充足的话不会回收它;如果内存空间不足的话,就会回收这么对象的内存。只要垃圾回收器没有回收它,该对象就可以继续被程序使用。

JDK1.2之后,使用java.lang.ref.SoftReference来实现软引用。

3.弱引用(WeakReference)

如果一个对象只具有弱引用,那么他只有短暂的生命周期,只能存活在垃圾回收之前,不管内存是否足够,他都会被回收空间。JDK1.2之后,使用java.lang.ref.WeakReference来实现软引用。

4.虚引用((PhantomReference)

形同虚设,虚引用不会绝对对象的声明周期。如果一个对象只具有虚引用,那它随时会被垃圾回收器回收。没有办法通过虚引用获得一个实例对象;设置虚引用关联的唯一目的是:在这个对象被垃圾回收器时收到一个系统通知或下一步指示。

Java中允许使用finalize方法在对象清理前做一些清理工作。

注意:如果持有虚引用的对象重写了finalize方法则表示要在对象被 垃圾回收器回收之前做清理工作,此时虚引用的对象直接被清理不会进入引用列表。

如果持有虚引用的对象没有重写了finalize方法则表示要在对象被 垃圾回收器回收之后做清理工作,此时虚引用的对象会进入引用列表。

JDK1.2 以后使用 java.lang.ref.PhantomReference 类来实现虚引用。

三、垃圾回收算法

1.标记—清除算法

最基础的算法。分为标记和清除两个步骤。

先标记完所有需要回收对象,在标记完成后统一回收所有被标记的对象(可达性分析算法实现)

缺点:①效率不高;②会产生大量的内存碎片,在需要存放大内存的数据时,无法找到连续的内存空间存储而不得不提前触发另一次垃圾回收。

2.复制算法

解决了"标记清除"的大量内存碎片的问题。它将内存按着容量大小划分为大小相等的两块区域,每次只使用其中的一块,当这块内存需要垃圾回收时,把存活的对象复制到另一块内存区域上,然后把使用过的这块内存进行清理。

优点:每次只对半个区域进行垃圾清理,解决了大量内存碎片的问题,实现简单运行高效。

缺点:每次都要预留50%的空间不能存储对象,内存利用率低,空间浪费大。

3.标记—整理算法

标记过程和"标记清除"的过程一致,然后把所有存活的对象移到一端,直接清理另一端的对象。

四、JVM分代垃圾回收机制

JVM的堆内存是进行分代管理的。

堆内存分为年轻代和老年代。

年轻代分为Eden区(新生区)和幸存区(Survivor)。

幸存区(Survivor)又分为From区和To区。

1.新生代垃圾回收

新生代使用复制算法进行垃圾回收,由于在新生代中98%的对象都是"朝生夕死"的,所以不需要按照1:1来进行空间划分。

而是将新生代内存划分为一块较大的Eden(伊甸园)和两块较小的Survivor(幸存者)空间,每次使用Eden区和Survivor中的一块区域。当进行垃圾回收时,将Eden区和其中一块Surivor区的存活对象复制到另一块幸存区,再将使用过的幸存区进行垃圾清理。

当Survivor空间不足时,JVM使用内存担保机制将对象转移到老年代

HotSpot默认Eden区和Survivor的区域划分为8:1:1,也就是说有80%的内存空间用来存放新生对象,还有10%的空间用于存放清理之后还存活对象。

HotSpot实现的复制算法流程如下:

当Eden区存满时,触发一次Minor GC,把还存活的对象存放到Survivor的From区;当Eden区再次触发Minor GC时,会扫描Eden区和From区,经过两次垃圾清理之后,会把还存活的对象放入到Survivor的To区 ,并将Eden区和From区清空。

当后续Eden区再触发Minor GC时,会对Eden区和To区进行垃圾清理,把存活的对象放入From区,将Eden区和To区清理。

部分对象在From区和To区来来回回清理15次之后,最终如果还存活,会放入到老年代。

2.老年代垃圾回收

老年代存放长期存活的对象,当存满时,会触发MajorGC或FullGC对整个堆内存空间进行清理,在GC期间会停掉所有线程任务等待GC完成。所以对响应要求高的应用尽量减少MajorGC,避免响应超时。

必须采用"标记-整理"算法。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值