【Java】JVM运行流程以及垃圾回收处理

目录

1.JVM简介

2.JVM 和《Java虚拟机规范》

3.JVM运行流程

1.类加载器

1.一个类的生命周期

2.双亲委派模型

2.JVM运行时数据区

1.方法区(线程共享)

JDK 1.8 元空间的变化

运行时常量池

2.堆(线程共享)

2.1演示OOM异常

3.Java虚拟机栈(线程私有)

3.本地方法栈(线程私有)

4 程序计数器(线程私有)

2.执行引擎

3.本地方法库

4.垃圾回收相关

1.死亡对象的判断算法

a) 引用计数算法

b) 可达性分析算法

 2.垃圾回收的过程(复制算法)

3.垃圾回收算法(尽量加快扫描的速度)

a) 标记-清除算法

b) 复制算法

 c) 标记-整理算法

 4.垃圾收集器

1.Serial,也就是串行执行的垃圾收集器

2.Serial Old收集器(老年代收集器,串行GC)

3.ParNew收集器(新生代收集器,并行GC)

4.Parallel Scavenge收集器(新生代收集器,并行GC) 

5.Parallel Old收集器(老年代收集器,并行GC)

6. CMS收集器(老年代收集器,并发GC)​编辑

 7.G1收集器(唯一一款全区域的垃圾回收器)


1.JVM简介

JVM Java Virtual Machine 的简称,意为 Java虚拟机。
虚拟机是指通过软件模拟的具有完整硬件功能的、运行在一个完全隔离的环境中的完整计算机系统。

2.JVM 和《Java虚拟机规范》

以上的各种 JVM 版本,比如 HotSpot J9 JVM,都可以看做是不同厂商实现 JVM 产品的具体实现,而它们(JVM)产品的实现必须要符合《Java虚拟机规范》,《Java虚拟机规范》是 Oracle 发布 Java 领域最重要和最权威的著作,它完整且详细的描述了 JVM 的各个组成部分。

3.JVM运行流程

JVM是Java运行的基础,也是实现一次编译到处执行的关键,那么JVM是如何执行的呢?

程序在执行前先要把Java源代码编译成字节文件(class文件),通过一定的方式类加载子系统加载到运行时数据区中,因为字节码是JVM的一套指令集规范,操作系统并不能识别,所以要通过执行引擎将字节文件翻译成底层系统的指令再交给CPU去执行,在这个过程中有时会需要调用其它语言的接口来实现整个程序的功能

1.类加载器

1.一个类的生命周期

1.加载:是整个类加载的第一个阶段

就是读取我们的.class文件

  • 1)通过一个类的全限定名来获取定义此类的二进制字节流。
  • 2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  • 3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口。

2.验证:验证是连接的第一步,是为了确保class文件中的字节流符合Java虚拟机的规范

 3.准备工作

为类中的静态变量分配内存和初始值

4.解析:

将常量池中的符号引用替换为直接引用

5.初始化

初始化阶段,Java 虚拟机真正开始执行类中编写的 Java 程序代码将主导权移交给应用程序。初始化阶段就是执行类构造器方法的过程

2.双亲委派模型

如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最 终都应该传送到最顶层的启动类加载器中,只有当父加载器反馈自己无 法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去完成加载。
  • 启动类加载器:加载 JDK lib 目录中 Java 的核心类库,即$JAVA_HOME/lib目录。
  • 扩展类加载器。加载 lib/ext 目录下的类。
  • 应用程序类加载器:加载我们写的应用程序。
  • 自定义类加载器:根据自己的需求定制类加载器。

接收到类加载的请求-->向上逐层去询问父类是否已经加载-->到顶级父类还没有加载-->就逐层向下尝试是否能加载
优点:
1.避免类重复加载
2.保证Java核心的API不会被篡改,保证程序的安全性

虽然有优点,但在一定情况下也存在一些问题,比如Java中SPI基质中的JDBC的实现

SPI 全称 Service Provider Interface ,是 Java 提供的一套用来被第三方实现或者扩展的
接口,它可以用来启用框架扩展和替换组件。 SPI 的作用就是为这些被扩展的 API 寻找服务实现。
DriverManager 位于 rt.jar 包,由 BootStrap 类加载器加载,
而其 Driver 接口的实现类是位于服务商提供的 Jar 包中,是由子类加载器(线程上下文加载器
Thread.currentThread().getContextClassLoader )来加载的,这样就破坏了双亲委派模型了(双亲委派模型讲的是所有类都应该交给父类来加载,但 JDBC 显然并不能这样实现)。它的交互流程图如下所示:

2.JVM运行时数据区

JVM 运行时数据区域也叫内存布局,但需要注意的是它和 Java 内存模型( (Java Memory Model ,简称JMM)完全不同,属于完全不同的两个概念,它由以下 5 大部分组成:

1.方法区(线程共享)

方法区用来存储被虚拟机加载的类信息,常量,静态变量,即时编译器编译过后的代码等数据

中存放的是类对象,可以理解为对象的模板、
JDK 1.8 元空间的变化
1. 对于 HotSpot 来说,JDK 8 元空间的内存属于本地内存,这样元空间的大小就不在受 JVM 最大内存的参数影响了,而是与本地内存的大小有关。
2. JDK 8 中将字符串常量池移动到了堆中
运行时常量池
运行时常量池是方法区的一部分,存放字面量与符号引用
字面量 : 字符串 (JDK 8 移动到堆中 ) final 常量、基本数据类型的值。
符号引用 : 类和结构的完全限定名、字段的名称和描述符、方法的名称和描述符

2.堆(线程共享)

堆的作用:程序中New出来的所有对象都在保存在堆中
2.1演示OOM异常

Java堆用于存储对象实例,只要我们不断的创建对象,并且保证GC Roots到对象之间有可达路径来避免GC清除掉这些对象,那么当对象在到达最大堆容量后就会产生内存溢出异常

上面我们提到,可以使用JVM参数-Xms设置堆的最小值,-Xmx设置堆的最大值,下面我们来设置一下:

我们设置代码不停的创建新的对象,然后运行程序,发现报错了

 可以看到提示堆内存被占满了,此时我们还需要对文件进一步分析看是内存泄漏还是内存溢出

内存泄漏:泄露对象无法被GC

内存溢出:内存对象确实还应该存活,那么我们该如何修复呢?

就是在配置堆内存的时候将他调大就可以了,比如写到两千,或者检查一下对象的生命周期是否过长

3.Java虚拟机栈(线程私有)

Java虚拟机栈的生命周期和线程相同,每个线程都有对应的一个虚拟机栈,每调用一个方法都会以一个栈帧的形式加入栈中,方法执行结束后就会被调出栈

栈溢出:

栈容量只需要由- Xss参数来设置。

关于虚拟机栈会产生的两种异常:
  • 如果线程请求的栈深度大于虚拟机所允许的最大深度,会抛出StackOverFlow异常
  • 如果虚拟机在拓展栈时无法申请到足够的内存空间,则会抛出OOM异常

如果是因为多线程导致的内存溢出问题,在不能减少线程数量的情况下,只能通过减少最大堆和减少栈容量的方式来换取更多的线程

当递归调用过多时,可能就会出现栈溢出,所以我们尽量要避免

Java虚拟机栈描述的是Java方法执行的内存模型,

3.本地方法栈(线程私有)

记录的是本地方法调用的关系

4 程序计数器(线程私有)

记录当前线成的方法执行到哪一行
  • 程序计数器是一块比较小的内存空间,可以看做是当前线程所执行的字节码的行号指示器。
  • 如果当前线程正在执行的是一个Java方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址:如果正在执行的是一个Native方法,这个计数器值为空

2.执行引擎

Java字节码----->CPU指令的转换过程 

针对不同的CPU使用不同的转换方式(Java语言可以跨平台的原因)

3.本地方法库

调用系统的API 

4.垃圾回收相关

1.死亡对象的判断算法

a) 引用计数算法

计数器为0的对象就是不能再被使用对象
但是最主要的原因就是引用计数法无法解决对象的循环引用问题

b) 可达性分析算法

JVM中判断对象是否死亡的算法

标记->扫描->清除

 2.垃圾回收的过程(复制算法)

 1,所有new出来的对象全都放在新生代的Eden区

2.当Eden区满了之后,会触发一次垃圾回收,然后将还存活的对象移动到s0区域

3.当Eden区再次满之后,再进行一次垃圾回收,将存活的对象全部移动到s1区域,将S1和s0区域进行交换

老年代存放满了之后,会对老年代进行一次垃圾回收

新生代放不下之后,也会放到老年代去

每次垃圾回收时,程序都会进入到暂停状态STW

3.垃圾回收算法(尽量加快扫描的速度)

a) 标记-清除算法

b) 复制算法

 c) 标记-整理算法

 4.垃圾收集器

收集算法是内存回收的方法论,那么垃圾收集器就是内存回收的具体实现

以下这些收集器是 HotSpot 虚拟机随着不同版本推出的重要的垃圾收集器JVM中一共有七种

  • 并行(Parallel) : 指多条垃圾收集线程并行工作,用户线程仍处于等待状态
  • 并发(Concurrent) : 指用户线程与垃圾收集线程同时执行(不一定并行,可能会交替执行),用户程序继续运行,而垃圾收集程序在另外一个CPU上。
  • 吞吐量:就是CPU用于运行用户代码的时间与CPU总消耗时间的比值
  • 吞 吐 量 = 运 行 用 户 代 码 时 间 / (运 行 用 户 代 码 时 间  + 垃 圾 收 集 时 间)
  • 例如:虚拟机总共运行了 100 分钟,其中垃圾收集花掉 1 分钟,那吞吐量就是 99%

    1.Serial,也就是串行执行的垃圾收集器

    2.Serial Old收集器(老年代收集器,串行GC)

3.ParNew收集器(新生代收集器,并行GC)

4.Parallel Scavenge收集器(新生代收集器,并行GC) 

5.Parallel Old收集器(老年代收集器,并行GC)

6. CMS收集器(老年代收集器,并发GC

 7.G1收集器(唯一一款全区域的垃圾回收器)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值