是什么
Java Virtual Machine 翻译为Java虚拟机
为什么
-
提供了JAVA程序的运行环境,保证了JAVA运行与平台无关的特性
-
定义了JVM规范,保证了JAVA代码的统一性和规范性
-
JVM在运行期间提供了内存分配和垃圾回收的作用
工作流程
类加载
加载
先通过类的全类名获取到类的二进制文件,封装二进制文件到运行时常量池,再在堆中创建Class对象,用于封装运行时常量池中的数据结构,并对外提供访问接口
类加载器
分类
-
启动类加载器
主要加载核心类,例如以
java.
开头的jar包 -
扩展类加载器
主要用于加载具有扩展功能的jar包
-
应用程序类加载器
主要用于加载ClassPath目录下的jar包
-
自定义类加载
机制
-
双亲委派机制
是什么?
- 在请求类加载时,类加载器接收到请求,并不会直接进行加载,而是会先将这个请求传递给父类加载器,一直传递到启动类加载器,再由启动类加载器判断能否加载该类,若是不行,则将请求原路向下传递,直到碰到了可以加载的类加载器为止
为什么?
-
防止一个JVM中出现多个同类名相等的类的情况
-
问题:同一JVM中可以存在俩个String类吗?
不行,因为存在双亲委派机制,每次类加载的时候会先由启动类加载器加载,而String属于核心类,即使String类多次加载,每一次到启动类加载器,都会被判断加载过,不会再次加载
-
-
破坏双亲委派机制
是什么?
双亲委派问题:例如,DriverManager类在使用时需要使用到Driver类,而DriverManager类是由启动类加载器加载的,而Driver实现类是由应用程序类加载器加载的,所以DriverManager无法直接调用Driver实现类
解决:为了解决类似的问题,Java虚拟机提出使用线程上下文类加载器,例如当DriverManager类需要调用Driver实现类时,就使用线程上下文类加载器来获取Driver实现类,这样就是破坏双亲委派机制
为什么?
双亲委派机制缺点:子类加载器加载的类可以访问父类加载器加载的类,但是父类加载器加载的类是无法访问子类加载器加载的类
原因:双亲委派机制和类加载器的命名空间
连接
-
验证
验证Class对象是否符合规范
-
准备
为静态变量分配内存
-
解析
将符号引用解析为直接引用
初始化
可以说,初始化阶段是执行类构造器 < clinit > 方法的过程。首先说下类构造器 < clinit > 方法和实例构造器 < init > 方法有什么区别。< clinit > 方法是在类加载的初始化阶段执行,是对静态变量、静态代码块进行的初始化。而< init > 方法是new一个对象,即调用类的 constructor方法时才会执行,是对非静态变量进行的初始化。
类被加载之后并不会立刻进行初始化,只有当类首次被主动引用时才会进行初始化
主动引用
- 使用new创建对象
- 使用反射创建对象
- 调用类属性/类方法
- JVM启动类
被动引用
- 定义对象数组
- 调用类常量
引申:对象加载
对象创建过程
-
先判断对象对应的类是否被加载,需要先进行类加载
-
内存分配
-
初始化默认值
-
设置对象头
对象头中包含了是否充当锁,HashCode值,GC年龄等
-
执行初始化方法(init方法)
对象初始化过程
- 初始化父类中的静态成员变量和静态代码块
- 初始化子类中的静态成员变量和静态代码块
- 以上这俩个步骤执行于类加载阶段
- 初始化父类的普通成员变量和代码块,再执行父类的构造方法
- 初始化子类的普通成员变量和代码块,再执行子类的构造方法
- 以上这俩个步骤执行性于对象创建过程
JVM运行时结构
线程共享
堆
-
堆主要用于存储对象,数组
-
堆中包含新生代,老年代和eden区,占用内存比例为8:1:1
新对象会在新生代,大对象和长期存活的对象会存储在老年代
方法区
-
在JDK7中,方法区的具体实现是永久代
在JDK8中,方法区的具体实现为元空间,元空间会直接占用计算机内存,但是在逻辑上属于JVM
-
方法区中包含了常量池,字符串常量池被迁移到了堆中,但是运行时常量池仍然在方法区
线程私有
栈
栈空间中有栈帧,每次调用方法都会使用栈帧封装方法的主要信息,包含方法名,参数,局部变量等等,方法执行完毕,进行出栈
程序计数器
程序计数器主要用于存储计算机下一个指令的地址
垃圾回收
垃圾回收算法
新生代
复制算法
老年代
- 标记清除法
- 标记整理法
垃圾回收器
串行
GC单线程内存回收, 暂停所有用户线程
并行
多个线程进行GC, 暂停所有的用户线程
-
JDK8默认使用并行垃圾回收器
-
缺点:造成STW
-
优点:内存占用小,数据吞吐量大
-
并发
用户线程和GC线程同时执行(不一定是并行, 可能是交替执行), 不需要停顿用户线程(停顿时间很短)
- CMS
- G1
调优
调优就是找平衡点
调优目的
-
让老年代的对象尽可能少
-
让GC次数减少,时间减少
-
解决内存泄漏
调优步骤
- 监控JVM状态
- 分析结果
- 进行调整
- 重复监控
调优方案
- 减少创建对象的数量
- 减少使用全局变量和大对象 ( 很可能会进入老年代 )
- 调整新生代和老年代的大小到最合适
- 选择合适的GC收集器