JavaPro _JVM 知识点速记 JVM大全

JVM

Java Virtual Machine,Java虚拟机

为什么叫Java虚拟机 .顾名思义,我们编写java代码,编译java代码,不是让他在windows上跑,在linux上跑,或者是在MacOS上跑, 而是在Jvm上跑

jvm作用

jvm负责把编译后的字节码转换为机器码

JVM的组织架构

1.类加载器(ClassLoader)

2.运行时数据区(RuntimeData Area)

3.执行引擎(Execution Engine)

4.本地库接口(Native Interface

jvm内部构造

  1. 类加载部分 : 负责把硬盘上字节码加载到内存中(运行时数据区)

  2. 运行时数据区: 负责存储运行时产生的各种数据 类信息,对象信息,方法信息...

  3. 执行引擎: 负责将字节码转为机器码

4.本地方法接口:

5垃圾回收部分

类加载


类加载系统,负责将硬盘上的字节码文件加载到jvm中,生成类的Class对象,存储在方法区.

类就是一个模版

类加载过程

类从被加载到 JVM,分别是加载、验证、准备、解析、初始化。其中验证、准备和解析这三个阶段统称为连接。

1.加载

以二进制文件流进行读取

在内存中为类生成Class对象

2.链接

验证 验证字节码的结构是否正确

准备为类的静态属性进行初始化赋值

static int a = 123; 在准备阶段a的值为0,在初始化阶段才赋值为123

解析 :将类中的符号引用替换成直接引用(符号引用是Class 文件的逻辑符号,直接引用指向的方法区中某一个地址).

3.初始化

初始化阶段主要是为类中静态成员进行赋值.

因为类加载执行完初始化阶段,才说明类加载完成了.

类在哪些情况下会被加载 (类只要被用到了就会被加载)

1调用类中静态成员(变量,方法) 类名,静态变量 类名,静态方法

2new 类的对象

3在类中执行main()

4反射加载类 Class.forName("地址")

5子类被加载

类在以下两种情况下,是不会被加载的

复制代码
1. 类作为数组类型
Demo demo[] = new Demo[10]; //new 的数组对象   不是Demo对象
​
2.只是访问类中的静态的常量
System.out.println(Demo.P);//优化   不加载整个类了,  只获取到用到的静态常量

类加载器 (加载类的实现者)

类加载器就是实际负责读取类的功能.

类加载器分类:

1 按照java语言分类,分为 用java语言实现的和非java语言实现

1>引导类加载器

不是用java语言写的使用c/c++实现的,用来加载java系统的中的类 如String

2>扩展类加载器

此加载器,由java语言实现,用于加载java8\jre\lib\ext下的类

3>应用程序加载器

此加载器,由java语言实现,用于加载我们自己开发的程序类

双亲委派机制

当加载一个类时,先让上级的类加载器去加载,优先加载系统中的类,直到启动类加载器,

如果到达顶部的类加载,找到了,那就返回, 如果顶部类加载器找不到,再次向下委派,让子类加载器去加载.

在哪一级找到了,就返回即可,如果向下,都没有找到,那么就抛出类找不到异常.

为什么使用双亲委派机制

优先加载系统中的类,防止我们的类替换了系统中的类.

如何打破双亲委派机制

在 ClassLoader 类中涉及类加载的方法有两个,loadClass(String name), findClass(String name),这两个方法并没有被 final 修饰,也就表示其他子类可以重写. 重写 findClass 方法

我们可以通过自定义类加载重写方法打破双亲委派机制,

运行时数据区------方法区,堆,栈,虚拟机栈,本地方法栈,程序计数器


运行时数据区就是用来存储各种运行时产生的数据

程序计数器


程序计数器,用来记录每个线程执行指令的位置,因为cpu要切换执行

特点:

1程序计数器占用内存小,速度快,

2线程私有,每一个线程都有一个程序计数器用来记录每个线程执行到的位置.

3声明周期与线程相同

4此区域不存在内存溢出的问题

虚拟机栈


虚拟机栈是运行的结构,主要用来执行java语言写的方法

栈是私有的,每个线程运行,都有一个自己的栈空间

栈运行的特点,先进后出/

栈空间是会有内存溢出的(比如说递归调用时)

一个方法被调用后,在栈中称为一个栈帧,保存局部变量,操作数栈,方法返回地址

本地方法栈


本地方法栈中,主要用来运行调用本地方法(native关键字修饰的方法,是由操作系统实现的方法,C/C++)

堆 ---java产生的对象都存储在堆中


特点:

堆空间是虚拟机中空间最大的一块区域,而且大小可以调整,(运行时数据区,除了程序计数器区域不能调整大小,其他四个区域都可以调整大小)

堆空间被所有线程共享

堆空间是会出现内存溢出

堆空间是垃圾回收的重点区域

堆空间区分

1>年轻代/新生代

  1. 伊甸园区(刚new出来的对象存储)

2.幸存者0 区(from) 3.幸存者1区(to)

2>老年代/老年区

为什么分区

可以把存活时间不同的对象放在不同的区域,对不同的区域进行垃圾回收

频繁的回收新生代,较少的回收老年代 减少及 GC 频率。

对象在堆空间的分配过程

新创建的对象都存储在伊甸园区当垃圾回收时 把伊甸园区的对象转移到幸存者0区,当后面继续回收垃圾时,再把伊甸园区和幸存者0区存活的对象转移到幸存者1区

每次保证有一个幸存者区是空闲的,减少内存碎片 当一个对象经过15次垃圾回收时,依然存活,则就将此对象移动到老年区

还有当一个对象比较大,也可以直接放入老年区

补充:对象的回收的次数最大上线时15 在对象头有4bit位来记录回收次数,

可以通过-XX:MaxTenuringThreshold=<N> 设置次数

设置堆空间的参数

-Xms:初始堆空间内存

-Xmx:最大堆空间内存

-Xmn:设置新生代的大小

-XX:MaxTenuringTreshold:设置新生代垃圾的最大年龄

具体的可以参考官方文档, 这些参数的设置,也是jvm调优的一部分 根据实际使用情况,设置各参数的大小

方法区


特点:

方法区主要存储加载到内存中的类信息,

方法区物理上与堆属于一个空间,但是在一般逻辑上进行区分,称为元空间(方法区)

方法区的大小可以设置 通过-XX:MetaspaceSize=<N>参数方法区的空间一但空间不足,就会触发 FULL GC (整堆收集) ,会影响应用程序的线程 一般情况下,可以把方法区设置较大一些

方法区会出现内存溢出的

方法区存在垃圾回收的

方法区什么时候会被卸载 (方法区的垃圾回收)

1 该类以及该类的子类的所有的对象都不存在了

2 加载该类的加载器不存在了

3 加载该类的CLass对象不存在了

由于条件比较苛刻 一般情况下 可以认为类被加载后就不再释放

本地方法接口


用于对接调用的本地方法

什么是本地方法,

被native修饰的方法就是本地方法

// new Object().hashCode(); //public native int hashCode(); 获取内存地址

// new FileInputStream("").read(); private native int read0() 读硬盘数据

//new Thread().start();// private native void start0(); 把线程注册到操作系统

为什么要使用本地方法

java属于应用程序语言,没有权限直接操作硬件设备 使用本地方法与硬件进行交互

例如获取内存地址,读取硬盘数据,这是就需要对操作系统中的本地方法进行实现

执行引擎


1作用

可以将字节码指令解释/编译成机器码指令

注意提到了两次编译,

第一次编译是指将.java文件编译为.class文件. 属于开发阶段,与运行无关,也可以成为前端编译

第二次变异是在运行时,在虚拟机中通过执行引擎将字节码编译为机器码,称为后端编译

2解释器和编译器

解释器采用不编译,是逐行解释的方式,无需编译,可以实现立即的解释执行的操作,但是效率略低

编译器将字节码整体编译后执行,编译需要花费一段时间,但是编译后的执行效率高

3为什么这样设计 ---Java 是半编译半解释型语言

解释执行,虽然效率低,但是在程序开始后,可以立即投入使用

编译执行,虽然将一些热点代码编译后缓存起来执行那个效率高,但是花费时间

所以采用开始时,使用解释器执行,等编译完成后采用编译执行

垃圾回收


相关概念

1.什么是垃圾

在运行过程中,如果一个对象没有被任何引用所指向,那么这个对象就称为垃圾对象.

如果垃圾对象不清理,后来的对象就可能没有地方存放,进而导致内存溢出.

2.早期和现代的垃圾回收

早期c/c++都是手动的申请和释放内存,

好处: 可以精确的管理内存, 用的时候申请,不用的时候可以立即释放.

缺点: 给程序员带来负担, 一旦忘记释放,一直存在.

现代java,python,c#....语言都采用自动垃圾回收

好处: 解放了程序员,不需要关心什么时候释放垃圾对象

缺点: 降低了程序员对内存管理的能力

3.垃圾回收的区域

运行时数据区中的堆和方法区是回收的区域

堆是回收的重点区域

频繁的回收新生代

较少回收老年代

基本不回收方法区(在full gc 整堆回收时 ,才会对方法区进行回收)

4.内存溢出与内存泄漏

内存溢出: 内存不够用了,还继续有新的对象产生,此时回报出内存异常错误.

内存泄漏: 一些对象已经不再使用了,但是垃圾收器,却不能回收它,例如数据库连接对象,网络SOcket对象,

还有IO流对象等,提供close()进行断开,但是没有调用,那么垃圾回收器认为此对象还在使用中.这些对象就悄悄的占用着内存资源, 这种现象称为内存泄漏, 久而久之会引发内存溢出问题.

垃圾回收相关算法


标记阶段算法

标记阶段主要就判断哪些对象是垃圾对象,把没有引用指向的对象标记出来,

在垃圾回收时进行回收.

标记阶段提到两种算法:

1.引用计数算法(目前没有被使用)

在对象中提供一个计数器,有引用指向对象,那么计数器就加一,当计数器为0时,表名该对象没有被任何引用指向.

实现简单,

但是有一个致命的缺陷,无法解决循环引用问题.

多个对象之间相互引用,计数器不为0 ,但是此时这几个对象,与外界已经没有联系了,使用不到了,

他们相互又有引用,垃圾收集器不能回收他们,会造成内存泄漏问题.

2.可达性分析算法

可达性分析算法,可以解决引用计数算法中的循环引用问题.

思想是从一些活跃对象开始查找,与活跃对象相连接的对象,都是被使用的,

如果没有与与这些活跃对象相连接的对象,就可以判断为垃圾对象

哪些对象可以称为活跃对象(GCRoots):

1.虚拟机栈中引用的对象 比如:各个线程被调用的方法中使用到的参数、局部变量等。

2.方法区中类静态属性引用的对象,比如:Java 类的引用类型静态变量

3.所有被同步锁 synchronized 持有的对象 4.Java 虚拟机内部的引用。 基 本 数 据 类 型 对 应 的 Class 对 象 , 一 些 常 驻的异常对象(如:NullPointerException、OutofMemoryError),系统类加载器。

finalize机制

java中的Object类中提供一个finalize()方法,

这个方法在对象被判定为垃圾后,在垃圾回收器回收这个对象时,会调用finalize().

finalize()规定只会被调用一次. 假如一个对象第一被回收时,调用finalize(),在finalize()中又被使用到了,复活了,

当第二次再被判定为垃圾,回收时,不再调用finalize()方法.

我们可以重写finalize(),去执行一些对象回收前的最终操作, 但是我们不要自己主动的去调用该方法.

还有此方法中内容需要慎重,否则,影响垃圾收集器的性能.

由于finalize()存在

把对象分为三种状态:

可触及: 使用中的对象,没有被判定为垃圾

可复活: 被判定位垃圾对象,但是还没有调用finalize(),有可能在finalize()中复活.

不可触及: 第二次被判定为垃圾,finalize()已经调用过了.

回收阶段算法

标记-复制算法:

可以有多块内存,每次回收时,将正在使用的内存块中的存活对象,复制到另一块未使用的内存块中,

然后清除原来的内存块.

特点: 使用到多块内存

回收时需要移动对象,适合内存小,存活对象少,垃圾对象多的场景 (适合新生代)

回收后,内存碎片少.

标记-清除算法:

只需要一块内存空间,把存活对象是不用进行移动,直接清除垃圾对象.

特点: 只需要一块内存,

不需要移动存活对象, 只清除垃圾对象

回收后,会产生内存碎片问题

适合于存活对象多且对象较大的场景 (适合老年代)

标记-压缩算法

标记压缩算法,就是在标记清除算法的基础上,对存回对象进行移动,将存回对象移动到内存一端,按顺序排放,

清除边界之外的区域,

是一种移动式的垃圾回收算法,回收后,内存中没有内存碎片

适合老年代回收

分代收集

年轻代的对象,存活的少,垃圾多,使用标记复制算法进行回收

老年代的对象大,且存活时间长,采用标记清除和标记压缩算法进行回收.

优化垃圾回收效率

垃圾收集器


垃圾收集器是jvm中垃圾回收的实现者.

垃圾收集器种类很多,不同的虚拟机中可以使用不同的垃圾收集器.

垃圾收集器分类:

从垃圾收集器线程数量上划分:

单线程, 垃圾收集器中,只有一个线程在执行垃圾回收操作

多线程,垃圾收集器中,有多个线程在执行垃圾回收操作

从工作模式上分为:

独占式的: 当垃圾回收线程在执行时,其他用户线程暂停(把用户线程暂停的现象称为stw)

并发式的: 垃圾回收线程和用户线程可以同时执行, 减少了用户线程暂停的次数和时间

按照工作内存分为:

新生代垃圾收集器

老年代垃圾收集器

jdk8支持一下几种垃圾回收器

CMS(Concurrent Mark Sweep) 并发标记清除

首创了垃圾回收线程和用户线程并发执行, 追求低延时.

初始标记阶段 : 独占式的

并发标记阶段: 并发式的

重新标记阶段: 独占式的

并发清除阶段: 并发式的

G1(Garbage-First)收集器

首先G1垃圾收集器,适用于整堆收集,不再区分新生代,老年代

适用于服务器场景.

G1垃圾收集器,将每个区域再划分为更小的区域, 例如将伊甸园区划分为多个小区域,

优先回收垃圾对象较多的区域, 故得名Garbage-First--垃圾优先.

也是支持并发执行

查看并设置垃圾收集器

-XX:+PrintCommandLineFlags -version 打印jdk中使用的垃圾收集器信息

-XX:+UseG1GC 设置垃圾收集版本

相关推荐
程序视点2 小时前
JVM虚拟机监控及性能调优实战
java·jvm·后端
土豆炒马铃薯。4 小时前
【Java 基础(人话版)】Java 虚拟机(JVM)
java·开发语言·jvm·后端·java基础·虚拟机
胡歌114 小时前
final 关键字在不同上下文中的用法及其名称
开发语言·jvm·python
小马爱打代码17 小时前
JVM为什么要设计STW机制?
jvm
剑海风云18 小时前
JVM常用概念之垃圾回收设计与停顿
java·开发语言·jvm
顾随18 小时前
(一)Java虚拟机——JVM的组成
java·开发语言·jvm
大彬聊编程21 小时前
线上JVM OOM问题,如何排查和解决?
jvm
帝锦_li1 天前
JVM--虚拟机
jvm·jdk·intellij-idea
pouop1 天前
Linux下线程的同步与互斥
jvm
郑祎亦1 天前
【JAVA面试题】JDK、JRE、JVM 三者区别和联系
java·开发语言·jvm