深入浅出Java虚拟机(JVM)-认识JVM

1、引言

对于Java开发者来说,不再像C语言那样需要开发者自己去管理内存,Java语言的内存由虚拟机管理。当程序出现内存方面的问题时,如果你了解虚拟机,排除起问题来,更加容易。

2、JVM 基础概念

2.1、什么是 JVM

JVM是Java程序的运行环境,负责将编译后的字节码翻译为机器能够识别的指令并执行。JVM也是Java语言能跨平台的重要原因。JVM可以比喻成一个转换器,有上下两端的接口,上端的接口对接字节码,下端的接口对接不同系统。

2.2、JVM 的核心职责

JVM的几大核心功能:

  • 内存管理(自动分配与回收)。
  • 字节码解释与编译(解释器 + JIT 编译器)。
  • 线程调度与同步机制。
  • 安全控制(类加载验证、沙箱机制)。

3、JVM 内存结构详解

JVM内存主要有三部分区域:线程共享、线程私有和直接内存。

线程共享:堆、方法区;

线程私有:虚拟机栈、本地方法栈和程序计数器;

直接内存:不被JVM GC管理(线程共用)

线程共享区域随JVM创建/销毁,线程之间共用。

线程私有区域随线程创建/销毁,线程之间相互隔离。

直接内存并不是JVM运行时数据区的一部分,但也会被JVM使用。

在JDK1.8之后方法区被元空间取代,元空间使用的是直接内存。

Java 堆(Java Heap) :Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;

方法区(Methed Area) :用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据。

Java 虚拟机栈(Java Virtual Machine Stacks) :用于存储局部变量表、操作数栈、动态链接、方法出口等信息;

本地方法栈(Native Method Stack) :与虚拟机栈的作用是一样的,只不过虚拟机栈是服务 Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;

程序计数器(Program Counter Register) :当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;

4、类加载机制

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成可以被虚

拟机直接使用的java类型

4.1、类的生命周期

类的整个生命周期可以简单概括为 7 个阶段:加载、校验、准备、解析、初始化、使用和卸载。

4.2、JVM类加载器

JVM 提供了3种类加载器,当然我们可以通过继承 java.lang.ClassLoader 实现自定义的类加载器。

  • 启动类加载器(Bootstrap ClassLoader):是虚拟机自身的一部分,用来加载Java_HOME/lib/目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库;
  • 扩展类加载器(Extension ClassLoader):负责加载\lib\ext目录或Java. ext.dirs系统变量指定的路径中的所有类库;
  • 应用程序类加载器(Application ClassLoader):负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。
  • 自定义类加载器:由 Java 语言实现,继承自抽象类 ClassLoader。

4.3、双亲委派机制

当一个类加载器收到一个类加载请求时,它首先会把这个类加载请求委派给父类加载器去完成,直到最顶级的类加载器(启动类加载器),只有当父类反馈无法加载时,才会自己去尝试加载。

采用双亲委派机制的好处是什么,例如:我们自己定义了一个java.lang.String 类,无论哪个类加载器收到这个类加载请求都会委托给顶层的启动类加载器进行加载。然而,启动类加载器已经加载了系统提供的java.lang.String类,不会再去加载我们定义的java.lang.String类,从而比例系统定义的java.lang.String被覆盖。

5、垃圾回收(GC)机制

Java 自动内存管理最核心的功能是堆内存中对象的分配与回收。Java堆内存是垃圾回收的主要区域。

在JVM中,有一个垃圾回收线程,它是低优先级的,在正常情况下是不会执行的,只有在虚拟机空闲或者当前堆内存不足时,才会触发执行,扫面那些没有被任何引用的对象,并将它们添加到要回收的集合中,进行回收。

5.1、判断对象是否可以被回收

JVM中有两种方法来判断:

  • 引用计数器法:为每个对象创建一个引用计数器,有对象引用时计数器+1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;
  • 可达性分析算法:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。 当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。

5.2、JVM垃圾回收算法

JVM垃圾回收的算法:标记-清除算法、复制算法、标记-整理算法、分代算法。

5.2.1、标记-清除算法

标记-清除算法是最基础的垃圾回收算法,标记-清除算法分为两个阶段,标记和清除。标记阶段将标记需要回收的对象,清除阶段将回收所有被标记的对象占用的内存空间。如图:

从图中可以看出,标记-清除算法清除后内存碎片化严重,后续大对象可能没有可以用的空间。

5.2.2、复制算法

为了解决标记-清除算法内存碎片化问题,从而产生了复制算法。它把内存空间划为两个相等的区域,每次只使用其中一个区域。垃圾收集时,遍历当前使用的区域,把存活对象复制到另外一个区域中,最后将当前使用的区域的可回收的对象进行回收。如图:

复制算法实现简单、运行高效,解决了内存碎片问题,但内存使用空间只有原来的一半。

5.2.3、标记整理算法

在新生代中可以使用复制算法,但是在老年代就不能选择复制算法了,因为老年代的对象存活率会较高,这样会有较多的复制操作,导致效率变低。标记-清除算法可以应用在老年代中,但是它效率不高,在内存回收后容易产生大量内存碎片。因此就出现了一种标记-整理算法(Mark-Compact)算法,与标记-清除算法不同的是,在标记可回收的对象后将所有存活的对象压缩到内存的一端,使他们紧凑的排列在一起,然后对端边界以外的内存进行回收。如图:

5.2.4、分代算法

当前商业虚拟机都采用分代收集 的垃圾收集算法。分代收集算法,顾名思义是根据对象的存活周期将内

存划分为几块。一般情况下将GC堆分为年轻代老年代

新生代默认的空间占比总空间的1/3,老生代的默认占比是2/3。

新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:

  • 把 Eden + From Survivor 存活的对象放入 To Survivor 区;
  • 清空 Eden 和 From Survivor 分区;
  • From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。

每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是15)时,升级为老生代。大对象也会直接进入老生代。 老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。

5.3垃圾回收器

用于回收新生代的收集器包括Serial、PraNew、Parallel Scavenge,回收老年代的收集器包括Serial Old、Parallel Old、CMS,还有用于回收整个Java堆的G1收集器。不同收集器 之间的连线表示它们可以搭配使用。

Serial收集器(复制算法): 新生代单线程收集器,标记和清理都是单线程,优点是简单高效;

ParNew收集器 (复制算法): 新生代并行收集器,实际上是Serial收集器的多线程版本,在多核CPU环境下有着比Serial更好的表现;

Parallel Scavenge收集器 (复制算法): 新生代并行收集器,追求高吞吐量,高效利用 CPU。吞吐量 = 用户线程时间/(用户线程时间+GC线程时间),高吞吐量可以高效率的利用CPU时间,尽快完成程序的运算任务,适合后台应用等对交互相应要求不高的场景;

Serial Old收集器 (标记-整理算法): 老年代单线程收集器,Serial收集器的老年代版本;

Parallel Old收集器 (标记-整理算法): 老年代并行收集器,吞吐量优先,Parallel Scavenge收集器的老年代版本;

CMS(Concurrent Mark Sweep)收集器(标记-清除算法): 老年代并行收集器,以获取最短回收停顿时间为目标的收集器,具有高并发、低停顿的特点,追求最短GC回收停顿时间。

G1(Garbage First)收集器 (标记-整理算法): Java堆并行收集器,G1收集器是JDK1.7提供的一个新收集器,G1收集器基于"标记-整理"算法实现,也就是说不会产生内存碎片。此外,G1收集器不同于之前的收集器的一个重要特点是:G1回收的范围是整个Java堆(包括新生代,老年代),而前六种收集器回收的范围仅限于新生代或老年代。

新生代回收器:Serial、ParNew、Parallel Scavenge

老年代回收器:Serial Old、Parallel Old、CMS

整堆回收器:G1

相关推荐
bing_1582 小时前
JVM 类加载器在什么情况下会加载一个类?
java·jvm
明天不下雨(牛客同名)9 小时前
为什么 ThreadLocalMap 的 key 是弱引用 value是强引用
java·jvm·算法
种豆走天下15 小时前
JVM基础原理
jvm
鸭梨大大大21 小时前
JVM的学习
jvm·学习
geek_super1 天前
JVM学习--JVM运行时参数
jvm·学习
JIU_WW1 天前
JVM面试专题
java·jvm·面试·java虚拟机·垃圾回收
君山李小狼2 天前
JVM垃圾回收
jvm
纸醉金迷金手指2 天前
GHCTF-web-wp
前端·jvm
兮动人2 天前
arthas之jvm相关命令
jvm·arthas·arthas之jvm相关命令·arthas基础命令