目录
JVM相关知识整理
个人理解之后简单整理,除去了一些不是很重要的部分,把语句整理的很简洁。
类加载
-
加载
把
class
文件变为二进制流加载到内存里,变为动态的Class
实例 -
链接
-
验证
验证字节码格式是否符合虚拟机规范
-
准备
对于静态变量分配内存和赋值零值的过程。
-
解析
将常量池的符号引用替换为直接引用的过程。
解析类和方法确保类和类之间相互引用的正确性
-
-
初始化
收集类中静态变量的赋值操作自动生成
clinit
方法并执行。
类加载时机
- 遇到
new
关键字,会先检测是否这个类被加载 - 通过反射创建对象时
- 创建子类对象时如果父类没有被加载会先加载父类。
- 引用类的静态变量或静态方法
那么通过子类来引用父类的静态字段,会初始化子类和父类吗?
会初始化父类,但是不会初始化子类。因为是子类引用父类,只有在用到子类时才会初始化子类。
如果创建一个对象的数组类型,会初始化这个类吗?比如Person[] arr = new Person[10]
不会。只有在实际用到这个类时才会初始化这个类。
双亲委派模型介绍一下
双亲委派模型主要是这样的。
一个类收到加载请求后会先看
- 待加载的类是否已经被加载过,如果已经被加载过,返回
- 如果没有被加载过,会先询问上层的类是否可以加载
- 上层类收到加载类的请求也是做相同的处理,直到根类加载器
- 如果上层的类可以加载,就由上层的类加载该类,返回加载好的
Class
对象,否则,抛出异常,由下层类捕获异常并且尝试加载这个类。
双亲委派模型可以保证:
- 类加载的一致性,不会出现已经加载的类被别的类加载器再加载一遍
- 核心类的加载的正确性,因为核心类都是由启动类加载器或者扩展类加载器去加载的。
不同类加载器加载的Class
对象是同一个Class
对象吗?
不是的,由不同类加载器加载的Class
对象被分为不同的Class
对象。
对象加载步骤
-
检查类是否加载
-
分配内存
-
分配内存两种方式:
-
指针碰撞
通过指针分为两部分,一部分为空闲,另一部分为已经被使用的对象。
每次要分配对象,把指针进行移动即可。
-
空闲列表
通过空闲列表记录所有空间的空闲情况。
-
-
分配内存时保证并发安全:
前提:如果线程A发现
地址0X0001
可用,线程A准备占用时,被中断了,线程B发现地址0X0001
可用,直接创建对象。此时如果又回到线程A,那么线程A就会继续在这个地址上创建对象,会把B创建的对象覆盖掉,解决方法在下面。
- 给每个线程分配一块空间TLAB(线程本地分配缓存空间),创建对象先在这里创建,用完了就去堆中剩余的地方创建
- 堆中剩余地方可能发生上述提到的线程不安全情况,所以通过CAS来重试确保不会出错。
-
-
赋值零值
比如
int
赋值为0,Object
赋值为null等。保证对象属性不赋值就能用,方便点
-
创建对象头
记录一些数据,比如
hashcode
- GC年龄
- 锁标志
- 对象的
Class
对象
等等
-
执行对象构造函数
JVM内存结构
堆
-
几乎存储着所有的实例对象。
-
被线程共享。
-
分为新生代和老年代。
堆大小设置参数:-Xms256M -Xmx1024M(-X表示是运行参数,ms是memory start,mx是memory max)
元空间
存储的内容:类元信息,字段,静态属性,方法,常量。字符串常量已经被移动到堆
比如:Object类信息,System.out属性,整形常量560等
虚拟机栈
- 线程私有
- 用于支持方法调用
- 进行方法调用会进行栈帧的入栈和出栈
栈帧:
-
局部变量表
-
存放方法参数和局部变量的地方
-
变量不会自动初始化,必须显式初始化。
-
-
操作数栈
- 一个小型栈
- 存放计算过程中的临时变量
-
动态链接
指向常量池中,该方法的引用。为了支持运行中方法的动态链接。
-
方法返回地址
方法退出。
- 正常退出
- 异常退出
垃圾回收器
-
Serial
串行垃圾回收器
每次回收垃圾会触发 STW(Stop The World),即长时间暂停去回收垃圾,在此期间不能执行用户线程。
-
CMS
目的:实现短停顿。
可以实现和用户线程并发。
步骤:
- 初始标记
- 并发标记
- 重新标记
- 并发清除
13会出现STW,24不会。
老年代采用的垃圾回收算法是:标记-清除和标记整理混合。先允许部分的空闲空间不连续,等到分配时候空间不够了,再统一挪动一次。
-
G1
把待回收区域分为四部分
年轻,幸存者,老年,大对象。
整个堆都会被分为这几部分。
哪部分垃圾多清理哪部分。