1.什么是JVM
jvm:jvm是java程序的运行环境,其主要功能是将java源代码编译成的二进制字节码转为机器码交给CPU执行。
优点:
① 一次编译,处处运行
② 自动内存管理,垃圾回收功能
JVM是JRE的组成部分 可以理解为 JDK = JRE(JVM+核心类库) + java工具(javac , java...)
2.JVM的类加载机制
Class文件加载到内存中,需要借助java中的类加载机制。jvm类加载机制分为5部分:加载、验证、准备、解析、初始化。
加载:加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的入口。注意这里不一定非得要从一个Class文件获取,可以从ZIP包中读取(jar或war),也可以在运行时计算生成(动态代理),也可以由其它文件生成(比如将JSP文件转换成对应的Class类)。
验证:主要目的是为了确保Class文件的字节流中包含的信息是否符合当前虚拟机的要求,且不会危害虚拟机自身的安全。
(1) 文件格式验证:主要验证字节流是否符合Class文件格式规范,并且能被当前版本的虚拟机处理。
(2) 元数据验证:主要对字节码描述的信息进行语义分析,以保证其提供的信息符合Java语言规范的要求。
(3) 字节码验证:主要是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。在第二阶段对元数据信息中的数据类型做完校验后,字节码验证将对类的方法体进行校验分析,保证被校验类的方法在运行时不会做出危害虚拟机安全的事件。
(4) 符号引用验证:最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段解析阶段发生。符号引用是对类自身以外(常量池中的各种符号引用)的信息进行匹配校验。
准备(分配内存):正式为类变量分配内存并设置类变量的初始值阶段,即在方法区中分配这些变量所使用的内存空间。
解析:解析阶段是指虚拟机将常量池中的符号引用替换成为直接引用的过程,符号引用以一组符号来描述所引用的目标。
初始化阶段:到了初始化阶段,才开始真正执行类中定义的Java程序代码。
3.JVM内存结构
(1)程序计数器(对应物理器件 寄存器):
作用:程序计数器中存放的是当前线程所执行的字节码的行号。jvm工作时,就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。
首先我们知道java源代码编译成二进制字节码(一条条jvm指令)
由解释器将二进制代码解释为机器码 给cpu执行
程序计数器在一条指定被解释器执行后
会记住下一条指令的地址,等到这条指令完成 解释器会从程序计数器中拿到地址,
去执行这个地址对应的指令。
特点:
属于数据私有区 线程私有的
不会存在内存溢出。
(2) jvm虚拟机栈
虚拟机栈其实就是线程运行需要的内存空间。
一个线程运行需要一个虚拟机栈 ,多个线程需要多个虚拟机栈
一个栈由多个栈帧组合而成
栈帧:一个栈帧对应一次方法的调用,每个方法运行需要的内存
栈帧中存在:局部变量,方法返回地址,操作数栈,动态链接。
活动栈帧(栈顶部):
每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
栈执行流程:
我们知道虚拟机栈是线程所需要的运行空间,多个栈帧组成一个虚拟机栈,
一个栈帧对应一次方法的调用,方法调用完后会自动弹出栈。
所以垃圾回收不会涉及栈内存。
线程的局部变量没有逃离方法的作用访问,它就是线程安全的。否则不是
栈内存溢出:
栈帧过大
栈帧过多:例如递归调用,没有结束。
(3)本地方法栈
其实本地方法栈和虚拟机栈相似,都是为方法调用提供的内存空间。只是它是给本地方法提供的内存空间。
本地方法一般c或者c++ 编写的本地方法
java代码间接调用本地方法。
(4)Heap(堆)
堆是我吗使用new 关键字创建对象都会使用的空间。
特点:
它是线程共享的,堆中对象都需要考虑线程安全问题
有垃圾回收机制。
堆空间内存分化如下图:
- JVM内存划分为堆内存和非堆内存,堆内存分为年轻代(Young Generation)、老年代(Old Generation),非堆内存就一个永久代(Permanent Generation)。
- 年轻代又分为Eden和Survivor区。Survivor区由FromSpace和ToSpace组成。Eden区占大容量,Survivor两个区占小容量,默认比例是8:1:1。
- 堆内存用途:存放的是对象,垃圾收集器就是收集这些对象,然后根据GC算法回收。
- 非堆内存用途:永久代,也称为方法区,存储程序运行时长期存活的对象,比如类的元数据、方法、常量、属性等。
在JDK1.8版本废弃了永久代,替代的是元空间(MetaSpace),元空间与永久代上类似,都是方法区的实现,他们最大区别是:元空间并不在JVM中,而是使用本地内存。
分代概念
新生成的对象首先放到年轻代Eden区,当Eden空间满了,触发Minor GC,存活下来的对象移动到Survivor0区,Survivor0区满后触发执行Minor GC,Survivor0区存活对象移动到Suvivor1区,这样保证了一段时间内总有一个survivor区为空。经过多次Minor GC仍然存活的对象移动到老年代。
老年代存储长期存活的对象,占满时会触发Major GC=Full GC,GC期间会停止所有线程等待GC完成,所以对响应要求高的应用尽量减少发生Major GC,避免响应超时。
Minor GC : 清理年轻代
Major GC : 清理老年代
Full GC : 清理整个堆空间,包括年轻代和永久代
所有GC都会停止应用所有线程。
1.8以后由于改成了元空间,它的垃圾回收就不是由java来控制了,元空间的默认情况下内存空间是使用的操作系统的内存空间,所以空间的容量是比较充裕的,不会发生元空间的空间不足问题。
为什么移除永久代?
移除永久代原因:为融合HotSpot JVM与JRockit VM(新JVM技术)而做出的改变,因为JRockit没有永久代。
有了元空间就不再会出现永久代OOM问题了
(5)方法区
定义:
所有java虚拟机线程共享的区域,jdk1.7的时候属于和堆空间连在一起的物理空间。
jdk1.7 永久代。
方法区里存储的是已被虚拟机加载的类信息,常量,静态变量。
4.JVM中的垃圾回收机制和常见的垃圾回收器
(1)gc可达性分析
主要思想就是先确定一系列不可以被回收的对象作为GC root,比如虚拟机栈里面的引用对象,本地方法栈的引用对象等,然后以GC root 作为起始节点,向下搜索,寻找和这些对象有直接引用或者间接引用的对象。如果有对象都没有这些引用,说明不可达,那么就需要进行gc垃圾回收。也就是 stop the wold。
(2)常见的java中jvm垃圾回收器
ParNew GC,很明显是个新生代GC实现,它实际是Serial GC的多线程版本,最常见的应用场景是配合老年代的CMS GC工作,下面是对应参数:
CMS(Concurrent Mark Sweep) GC,基于标记-清除(Mark-Sweep)算法,设计目标是尽量减少停顿时间,这一点对于Web等反应时间敏感的应用非常重要,一直到今天,仍然有很多系统使用CMS GC。但是,CMS采用的标记-清除算法,存在着内存碎片化问题,所以难以避免在长时间运行等情况下发生full GC,导致恶劣的停顿。另外,既然强调了并发(Concurrent),CMS会占用更多CPU资源,并和用户线程争抢
Parrallel GC,在早期JDK 8等版本中,它是server模式JVM的默认GC选择,也被称作是吞吐量优先的GC。它的算法和Serial GC比较相似,尽管实现要复杂的多,其特点是新生代和老年代GC都是并行进行的,在常见的服务器环境中更加高效。
G1 GC这是一种兼顾吞吐量和停顿时间的GC实现,是Oracle JDK 9以后的默认GC选项。G1可以直观的设定停顿时间的目标,相比于CMS GC,G1未必能做到CMS在最好情况下的延时停顿,但是最差情况要好很多。
以上是通过视频学习与阅读各个文章后对jvm的粗略理解,希望对大家有帮助,如有错误的的地方还请指出,不足的地方希望大家多多补充。