JVM相关知识点

JVM知识点很多也很杂也很深也很抽象,但是基础知识点需要清除而且清晰,总体思路不能乱,这样才能循序渐进。

1. JVM内存区域模型

这边要说下,内存区域模型和内存模型有区别的。内存区域模型就是大家一般说的堆区,方法区什么的,内存模型指的是Java内存模型,是指主内存,工作内存方面的。

内存区域模型

简单概括下:

线程私有区域:

程序计数器(当前线程执行的字节码的行号指示器),(生命周期和线程相同,存储局部变量,操作,动态链接等信息),方法栈(native方法)

线程共有区域:

(存放对象实例,用的最多的一个区域),具体细分为新生代和老年代,具体不细说,后续会解释。

方法区(存储加载的类的信息,常量,静态变量,编译后的代码等数据),是堆的一个逻辑部分,以前称为永久代,不过貌似1.7之后没有永久代这个概念了。具体不清楚1.7之后是怎么实现的了。

运行时常量池(包含在方法区内,存放的编译期间生产的字面量和符号引用,这部分在类加载后存放到常量池,直接引用也会存储于此)

2. 类加载

类加载

类加载有几点:

1. 过程:加载,链接(验证,准备,解析),初始化,使用,卸载。

    这边把准备标红是有原因的,准备阶段做了一个非常重要的事情,就是静态变量的初始值分配,什么?难道 static int a = 1; a的初始值就是1??不是的,a的初始值是0,初始值分配就是指分配“零值”,可以是0,也可以是0L,也可以是null,null就是针对引用了。真正给它赋值为1的时候是在初始化的步骤内,这点区别很重要。很多人会忽略。

2. 加载时机

    加载时机也是一个比较重要的点,主要分为主动引用和被动引用。

主动引用:

1. new,static变量的put和get或者static方法的调用。(此处有个特殊的final static 变量的访问不会进行类的初始化,这是因为final类型的变量在编译期的时候已经放在常量池了,后续的访问直接访问常量池的地址。

2. 反射

3. 初始化子类时去初始化父类

被动引用:

除此之外的均叫被动引用,典型的,通过子类去访问父类的静态变量。

3. 类加载器

第二点贴的文章中的类加载中的加载步骤规定加载是外部实现的,这是虚拟机规范要求的。其中步骤中的“通过一个类的全限定名获取此定义此类的二进制流”。这句话的实现就是类加载器。

类的唯一性:由加载它的类加载器和这个类本身决定,比较两个类是否相等也要在同一个类加载器下比较才是正确的。

在此说下代表类的class对象的equals(),isInstance()方法均返回true。

类加载器模型:双亲委派模型(一个类加载器收到类加载请求,它首先不会尝试自己加载,而是把这个请求委派给父类记载器完成,每一层都是如此。只有当父类反馈自己无法完成这个加载请求,子类才会尝试自己去加载。)

这个模型的好处最典型的就是Object类,它存在于rt.jat中,因此无论哪一个类加载器去加载这个类,最终都是Bootrap Classloader去加载。

类加载器

4. 垃圾回收算法

垃圾回收处的概念也比较多,分为以下几点梳理:

1. 判断对象已死

怎么判断对象已死?或者怎么判断这些对象是需要被回收的。

引用计数法(Java未采用,不能解决循环引用的问题)可达性分析算法(当一个对象到GC Roots没有任何引用链时,也就是GC Roots到这个对象不可达事,此对象证明是不可用对象,可以回收),具体GC Roots可参见博客。

在判断对象是否不可用时需要注意JVM中存在两次标记:第一次会被标记和删选(标记时会筛选是否有必要执行finalize())。如果有必要执行的话,会放入F-Queue队列中。在F-Queue中会进行第二次标记。如果成功执行finalize(),那么可以不用回收,剩余的就会被回收。

另外还有一个知识点就是4大引用被回收的场景。

对象已死吗?

2. 垃圾回收算法

标记-清除(比较垃圾,效率不高,内存碎片严重)

复制算法(分两块内存,每次使用一块,将其中一块移到另一半上,然后清理可回收对象),当前新生代采用的是复制算法,不过内存的分配比例为Eden区和Survivor的比例为8(Eden):1(Survivor):1(Survivor)。可用内存是8+1=90%。Survivor不够时需要老年代的支持,另外大对象会直接分配在老年代上。

复制算法之所以适用新生代,是因为新生代对象死的快,需要复制的很少。

标记-整理(存活率高,整理出存活对象,直接清理边界外的内存),适用于老年代。因为老年代对象存活多,所以不适合复制算法。

分代收集器(上述结合)

垃圾回收算法

5. 垃圾收集器

垃圾收集器是垃圾回收算法的具体实现。以HotSpot为例子。简单介绍下:

新生代:

Serial收集器:复制算法,单线程收集器,需要一次STW,运用于Client模式下。

ParNew收集器:复制算法,多线程收集器,需要一次STW,运用于Server模式下。

Parallel收集器:复制算法,多线程收集器,需要一次STW,运用于Server模式下,注重吞吐量

老年代:

Serial old收集器:标记-整理,单线程收集器,需要一次STW,运用于Client模式下。

Parallel old收集器:标记-整理,多线程收集器,需要一次STW,运用于Server模式下。

CMS收集器:标记-清除,多线程收集器,需要两次STW(初始标记处,重新标记处),注重相应速度,追求停顿更短时间。

新老代:

G1收集器:整体标记整理,局部复制。多线程收集器。需要一次STW(初始标记处)。

最前沿的垃圾收集器,主要是针对用户要求的时间内实现价值最大的回收。

参考

6. GC触发时机

我们基于GC的学习还是仅仅基于存在新生代和老年代的内存布局上。

Minor GC:发生于新生代中,本质上是两个Survivor之间的不断复制。

Major/Full GC:老年代满了之后触发Full GC,针对老年代和新生代和方法区。

system.gc(),不保证能触发垃圾回收。

7. JVM中对象的创建

对象的产生

1.检查常量池是否存在类的符号引用。

2.分配内存(指针碰撞,空闲列表),线程安全采用CAS的方式和TLAB。

3. 内存分配结束(类似初始化为零值)

4. 设置对象必要信息(对象头,元数据信息等)

5. 构造函数。

8. java内存模型,先行发生原则

天然的先行发生原则

从几个方面讲起:程序层面(同个线程内)、管程层面(基于锁的层面)、线程层面(启动,终止,中断等待)、volatile原则(唯一的一个关键字)、传递性(基于已有的情况)。

9. 内存溢出

内存溢出

内存溢出分析工具

10. Metaspace

Metaspace

猜你喜欢

转载自blog.csdn.net/qq_32924343/article/details/80675628