目录
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简介
2.JVM 和《Java虚拟机规范》
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.初始化
2.双亲委派模型
- 启动类加载器:加载 JDK 中 lib 目录中 Java 的核心类库,即$JAVA_HOME/lib目录。
- 扩展类加载器。加载 lib/ext 目录下的类。
- 应用程序类加载器:加载我们写的应用程序。
- 自定义类加载器:根据自己的需求定制类加载器。
优点:1.避免类重复加载2.保证Java核心的API不会被篡改,保证程序的安全性
虽然有优点,但在一定情况下也存在一些问题,比如Java中SPI基质中的JDBC的实现
SPI 全称 Service Provider Interface ,是 Java 提供的一套用来被第三方实现或者扩展的接口,它可以用来启用框架扩展和替换组件。 SPI 的作用就是为这些被扩展的 API 寻找服务实现。
2.JVM运行时数据区
1.方法区(线程共享)
方法区用来存储被虚拟机加载的类信息,常量,静态变量,即时编译器编译过后的代码等数据
JDK 1.8 元空间的变化
运行时常量池
2.堆(线程共享)
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) 引用计数算法
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收集器(唯一一款全区域的垃圾回收器)