JVM介绍

1. JVM的体系架构(内存模型)

绿色的为线程私有,橘色的为线程共有

2. 类加载器

负责将.class文件加载到内存中,并且将该文件中的数据结构转换为方法区中的数据结构,生成一个Class对象

2.1 类加载器分类

  • 自启动类加载器。Bootstrap ClassLoader类加载器。负责加载jdk自带的包。

    • %JAVA_HOME%/lib/rt.jar%即JDK源码
    • 使用C++编写
    • 在程序中直接获取被该加载器加载的类的类加载器会出现null
  • 扩展类加载器.Extension ClassLoader。负责加载jdk扩展的包
    • 便于未来扩展
    • %JAVA_HOME/lib/ext/*.jar%
  • 应用类加载器或系统类加载器。AppClassLoader或SystemClassLOader
    • 用于加载自定义类的加载器
    • CLASSPATH路径下
  • 自定义类加载器
    • 通过实现ClassLoader抽象类实现

2.2 双亲委派机制

当应用类加载器获取到一个类加载的请求的时候,不会立即处理这个类加载请求,而是将这个请求委派给他的父加载器加载,如果这个父加载器不能够处理这个类加载请求,便将之传递给子加载器。一级一级传递指导可以加载该类的类加载器。

该机制又称沙盒安全机制。防止开发者对JDK加载做破坏

2.3 打破双亲委派机制

  • 自定义类加载器,重写loadClass方法
  • 使用线程上下文类加载器

2.4 Java虚拟机的入口文件

sun.misc.Launcher

3. Execution Engine

执行引擎负责执行解释命令,交给操作系统进行具体的执行

4. 本地接口

4.1 native方法

native方法指Java层面不能处理的操作,只能通过本地接口调用本地的函数库(C函数库)

4.2 Native Interface

一套调用函数库的接口

5. 本地方法栈

在加载native方法的时候,会将执行的C函数库的方法,放在这个栈区域执行

6. 程序计数器

每个线程都有程序计数器,主要作用是存储代码指令,就类似于一个执行计划。

内部维护了多个指针,这些指针指向了方法区中的方法字节码。执行引擎从程序计数器中获取下一次要执行的指令。

由于空间很小,他是当前线程执行代码的一个行号指示器/

不会引发OOM

7. 方法区

供各线程共享的运行时内存区域,存放了各个类的结构信息(一个Class对象),包括:字段,方法,构造方法,运行时常量池。

虽然JVM规范将方法区描述为堆的一个逻辑部分,但它却还有一个别名叫做Non-Heap(非堆),目的就是要和堆分开

主要有:永久代或者元空间。存在GC

元空间中由于直接使用物理内存的影响,所以默认的最大元空间大小为1/4物理内存大小

8. Java栈

主要负责执行各种方法,是线程私有的,随线程的消亡而消亡,不存在垃圾回收的问题。八大数据类型和实例引用都是在函数的栈内存中分配内存的。

默认大小为512~1024K,通过-Xss1024k参数修改

8.1 栈和队列数据结构

FILO:先进后出

队列FIFO:先进先出

8.2 存储的数据

  • 本地变量Local Variable。包括方法的形参和返回值
  • 栈操作Operand Stack。包括各种压栈和出栈操作
  • 栈帧数据Frame Data。就相当于一个个方法。在栈空间中,方法被称为栈帧

8.3 执行流程

栈中执行的单位是栈帧,栈帧就是一个个方法。

  • 首先将main方法压栈,成为一个栈帧
  • 然后调用其他方法,即再次压栈
  • 栈帧中存储了这个方法的局部变量表,操作数栈、动态链接、方法出口等
  • 栈的大小和JVM的实现有关,通常在256K~756K

9. 方法区,栈,堆的关系

10. Heap 堆

10.1 堆内存结构

默认初始大小为物理内存的1/64,默认最大大小为1/4。在实际生产中一般会将这两个值设置为相同,避免垃圾回收器执行完垃圾回收以后还需要进行空间的扩容计算,浪费资源。

堆外内存:内存对象分配在Java虚拟机的堆以外的内存,这些内存直接受操作系统管理(而不是虚拟机),这样做的结果就是能够在一定程度上减少垃圾回收对应用程序造成的影响。使用未公开的Unsafe和NIO包下ByteBuffer来创建堆外内存。

默认的堆外内存大小为,通过-XX:MaxDirectMemorySize=执行堆外内存的大小

10.1.1 JDK1.7

在逻辑上划分为三个区域:

  • 新生区Young Generation Space

    • 伊甸区Eden Space
    • 幸存区Survivor 0 Space
    • 幸存区Survivor 1 Space
  • 养老区Tenure Generation Space
  • 永久区Permanent Space(方法区)

在物理层面划分为两个区域:

  • 新生区
  • 老年区
10.1.1.1 堆内存GC过程

主要流程有三步:

  • Eden满了以后出发一次轻GC(Minor GC),没有死亡的对象,年龄+1,存放到from区域

  • Eden再次满了以后再次触发一次GC,没有死亡的对象放置于to区域,然后将from区域中没有死亡的对象全部置于to区域,年龄+1。之后每一次GC都会出发一次fromto的交换,哪个区域是空的那个区域就是to

  • survivor区域满了以后,再次触发GC,当存在对象的年龄等于15的时候,就会将该对象移入老年区

    • MaxTenuringThreshold通过这个参数设置当年龄为多少的时候移入
  • 老年区满了以后触发一次Full GC,如果老年区无法再存放对象直接报OOM

注意:每一次GC都会给存活的对象的年龄+1

10.1.2 JDK1.8

1.7相比,仅仅是将永久代更替为了元空间。元空间的存放内置是物理内存,而不是JVM中。

这样处理,可以使元空间的大小不再受虚拟机内存大小的影响,而是由系统当前可用的空间来控制。

新生区和老年区的大小比例为1:2,通过-XX:NewRatio=n设置新生代和老年代的比例,n代表老年区所占的比例。

Eden Space和Survivor Space之间的比例默认为8:1,通过-XX:SurvivorRatio设置伊甸区和幸存者区的比例

逻辑层面分层:

  • 新生区Young Generation Space

    • 伊甸区Eden Space
    • 幸存区Survivor 0 Space
    • 幸存区Survivor 1 Space
  • 老年区Tenure Generation Space
  • 元空间(方法区)

物理层面分层:

  • 新生区 他占据堆的1/3
  • 老年区 他占据堆的2/3

10.2 堆参数调优

10.2.1 常用堆参数
参数 作用
-Xms 设置初始堆大小,默认为物理内存的1/64
-Xmx 设置最大堆大小,默认为物理内存的1/4
-XX:+PrintGCDetails 输出详细的GC日志

模拟OOM

//设置最大堆内存为10m
//-Xms10m -Xmx10m -XX:+PrintGCDetails

下面我们具体分析一下GC的过程做了什么,GC日志怎么看

名称:GC以前占用->GC之后占用(总共占用)

//GC 分配失败
GC (Allocation Failure)
[PSYoungGen: 1585K->504K(2560K)] 1585K->664K(9728K), 0.0009663 secs] //[新生代,以前占用->线程占用(总共空闲)] 堆使用大小->堆现在大小(总大小)
[Times: user=0.00 sys=0.00, real=0.00 secs] [Full GC (Allocation Failure)
[PSYoungGen: 0K->0K(2560K)]
[ParOldGen: 590K->573K(7168K)] 590K->573K(9728K),
[Metaspace: 3115K->3115K(1056768K)], 0.0049775 secs]
[Times: user=0.00 sys=0.00, real=0.01 secs]

11. 垃圾回收算法

11.1 垃圾回收类型

  • 普通GC(minor GC)发生在新生区的,很频繁
  • 全局GCmajor GC发生在老年代的垃圾收集动作,出现一次major GC经常会伴随至少一次的Minor GC

11.2 垃圾回收算法分类

11.2.1 引用计数法

主要思想:每存在一个对象引用就给这个对象加一,当这个对象的引用为零的时候,便触发垃圾回收。一般不使用

缺点:

  • 每次新创建对象就需要添加一个计数器,比较浪费
  • 循环引用较难处理
11.2.2 复制算法

主要思想:将对象直接拷贝一份,放置到其他区域

优点:不会产生内存碎片

缺点:占用空间比较大

使用场景:新生区的复制就是通过复制算法来执行的。当Minor Gc以后,就会幸存的对象复制一份放置到to

11.2.3 标记清除算法

主要思想:从引用根节点遍历所有的引用,标记出所有需要清理的对象,然后进行清除。两步完成

缺点:在进行垃圾回收的时候会打断整个代码的运行。会产生内存碎片

11.2.4 标记整理算法

主要思想:和标记清除算法一样,最后添加了一个步骤整理,将整理内存碎片。三步完成

缺点:效率低,需要移动对象。

11.3 各大垃圾回收算法比较

11.3.1 内存效率

复制算法>标记清除法>标记整理法

11.3.2 内存整齐度

复制算法=标记整理法>标记清除法

11.3.3 内存利用率

标记整理法=标记清除法>复制算法

11.3.4 最优算法

通过场景使用不同的算法,来达到最优的目的

年轻代:因为其对象存活时间段,对象死亡率高,所以一般使用复制算法

老年代:区域大,存活率高,一般采用标记清除和标记整理的混合算法。

老年代一般是由标记清除或者是标记清除与标记整理的混合实现。以hotspot中的CMS回收器为例,CMS是基于Mark-Sweep实现的,对于对像的回收效率很高,而对于碎片问题,CMS采用基于Mark-Compact算法的Serial Old回收器做为补偿措施:当内存回收不佳(碎片导致的Concurrent Mode Failure时),将采用Serial Old执行Full GC以达到对老年代内存的整理。

11.3.5 GCRoots

上面我们提到标记清除算法的时候,提到了一个名词,根节点引用。那么什么叫做根节点引用呢?

根节点引用也成GCRoots,他是指垃圾回收算法进行对象遍历的根节点。即从这个对象开始往下遍历,标记需要进行回收的对象。

垃圾回收标记的过程就是:以GCRoots对象开始向下搜索,如果一个对象到GCRoots没有任何的引用链相连时,说明此对象不可用。

就是从GCRoots进行遍历,可以被遍历到的就不是垃圾,没有被遍历到的就是垃圾,判定死亡

11.3.5.1 可达性对象和不可达性对象

可达性对象是指,在对象链路引用的顶层是一个GCRoot引用

不可达对象是指,在对象链路引用的顶层不是一个GCRoot引用

通俗解释:可达性对象就是对象有一个归属,这个归属有一个术语名称叫做GCRoot,不可达性对象就是这些对象没有归属。

11.3.5.2 什么引用可以作为GCRoots
  • 栈内的局部变量引用
  • 元空间中的静态属性引用
  • 元空间中的常量引用
  • 本地方法栈中native修饰的方法

说白了,就是所有暴露给开发者的引用

12. 垃圾回收器

垃圾回收器是基于GC算法实现的。

主要有四种垃圾回收器,不过具体有七种使用方式

12.1 四种垃圾回收器

12.1.1 串行垃圾回收器(Serial)

单线程进行垃圾回收,此时其他的线程全部被暂停

通过-XX:+UseSerialGC

12.1.2 并行垃圾回收器(Parallel)

多线程进行垃圾回收,此时其他的线程全部被暂停

12.1.3 并发垃圾回收器(CMS)

GC线程和用户线程同时运行

12.1.4 G1垃圾回收器

分区垃圾回收。物理上不区分新生区和养老区,将堆内存划分为1024个小的region,每一个占据的空间在2~32M,每一个region都可能是Eden SpaceSurvivor01 SpaceSurvivor02 SpaceOld区。

整体使用了标记整理算法,局部使用了复制算法。通过复制算法将GC后的对象从一个region向另一个region迁移,至于造成了内存碎片问题,通过整体的标记整理算法,避免了内存碎片的诞生

在进行垃圾回收的时候直接对一个region进行回收,保存下来的对象通过复制算法复制到TO区或者Old区。

逻辑上堆有四个区,每一个区的大小不定,按需分配。分为Eden SpaceSurvivor01 SpaceOldHumongous。其中Humongous用来存放大对象,一般是连续存储,当由于连续region不足的时候,会触发Full GC清理周围的Region以存放大对象

G1堆内存示意

G1垃圾回收

出现大对象,三个region不能存放,进行FullGC

执行流程

  • 初始标记。GC多线程,标记GCRoots
  • 并发标记。用户线程和GC线程同时进行。GC线程遍历GCRoots的所有的对象,进行标记
  • 重新标记。修正被并发标记标记的对象,由于用户程序再次调用,而需要取消标记的对象。GC线程
  • 筛选回收。清理被标记的对象。GC线程
  • 用户线程继续运行

12.1.4.1 案例
  • 初始标记。是通过一个大对象引发的G1

  • 并发标记

  • 重新标记、筛选清理和大对象引发的Full GC

12.1.4.2 G1常用参数
-XX:+UseG1GC  开启GC
-XX:G1HeapRegionSize=n : 设置G1区域的大小。值是2的幂,范围是1M到32M。目标是根据最小的Java堆大小划分出约2048个区域
-XX:MaxGCPauseMillis=n : 最大停顿时间,这是个软目标,JVM将尽可能(但不保证)停顿时间小于这个时间 -XX:InitiatingHeapOccupancyPercent=n 堆占用了多少的时候就触发GC,默认是45
-XX:ConcGCThreads=n 并发GC使用的线程数
-XX:G1ReservePercent=n 设置作为空闲空间的预留内存百分比,以降低目标空间溢出的风险,默认值是10%

12.2 常用参数

DefNew  	Default New Generation //串行垃圾回收器,新生代叫法
Tenured Old //串行垃圾回收器,老年代叫法
ParNew Parallel New Generation //新生代并行垃圾回收器,新生代叫法
PSYongGen Parallel Scavenge //新生代和老年代垃圾回收器,叫法
ParOldGen Parallel Old Generation //新生代和老年代垃圾回收器,叫法

12.3 新生代垃圾回收器

上图显示的是新生区和老年区可以使用垃圾回收器的所有种类,我们一个一个来说明

12.3.1 串行GC(Serial/Serial Coping)

新生代使用Serial Coping垃圾回收器使用复制算法

老年区默认使用Serial Old垃圾回收器,使用标记清除算法和标记整理算法

通过-XX:+UseSerialGC设置

12.3.2 并行GC(ParNew)

新生区使用ParNew垃圾回收器,使用复制算法

老年区使用Serial Old垃圾回收器(不推荐这样使用),使用标记清除算法和标记整理算法

通过-XX:+UseParNewGC启动

12.3.3 并行回收GC(Parallel/Parallel Scavenge)

新生代使用并行垃圾回收

老年代使用并行垃圾回收。Java1.8中默认使用的垃圾回收器

一个问题:Parallel和Parallel Scavenge收集器的区别?

Parallel Scavenge收集器类似于ParNew也是一个新生代的垃圾收集器,使用了复制算法,也是一个并行的多线程的垃圾收集器,俗称吞吐量优先收集器。

parallel Scavenge是一种自适应的收集器,虚拟机会根据当前系统运行情况收集性能监控信息,动态调整这些参数以提供最合适的提顿时间或者最大吞吐量

他关注的点是:

可控制的吞吐量。吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间),

同时,当新生代选择为Parallel Scavenge的时候,会默认激活老年区使用并行垃圾回收

通过-XX:UseParallelGC或者-XX:UseParallelOldGC两者会互相激活

-XX:ParallelGCThreads=n表示启动多少个GC线程

cpu>8时 N=5或者8

cpu<8时 N=实际个数

12.4 老年代垃圾回收器

12.4.1 串行垃圾回收器(Serial Old/Serial MSC)

Serial OldSerial垃圾收集器老年代版本,是一个单线程的收集器,使用标记整理算法,运行在Client中的年老代垃圾回收算法

与新生代的Serial GC相关联

12.4.2 并行回收(Parallel Old/Parallel MSC)

Parallel Old/采用标记整理算法实现

与新生代的Parallel Scavenge GC相关联

12.4.3 并发标记清除GC

CMS收集器(Concurrent Mark Sweep并发标记清除):一种以获取最短回收停顿时间为目标的收集器

适合应用在互联网站或者B/S系统的服务器上,重视服务器的响应速度

CMS非常适合堆内存大、CPU核数多的服务端应用,也是G1出现之前大型应用的首选收集器

标记的时候,GC线程运行;清除的时候和用户线程一起运行

通过-XX:+UseConcMarkSweepGC指令开启

配合新生区的pallellal New GC回收器使用

当CMS由于CPU压力太大无法使用的时候会使用SerialGC作为备用收集器

12.4.3.1 CMS执行过程
  • 初始标记(CMS initial mark)。遍历寻找到所有的GCRootsGC线程执行,用户线程暂停
  • 并发标记(CMS concurrent mark)和用户线程一起遍历GCRoots,标记需要清除的对象
  • 重新标记(CMS remark)。修正标记期间,对因用户程序继续运行而不需要进行回收的对象进行修正
  • 并发清除(CMS concurrent sweep)和用户线程一起清除所有标记的对象

12.4.3.2 优缺点

优点:

  • 并发收集低停顿

缺点:

  • 并发执行,对CPU资源压力大
  • 采用标记清除算法会导致大量的内存碎片

12.5 垃圾回收器小结

参数(-XX:+……) 新生代垃圾回收器 新生代算法 老年代垃圾回收器 老年代算法
UseSerialGC SerialGC 复制算法 Serial Old GC 标整
UseParNewGC Parallel New GC 复制算法 Serial Old GC 标整
UseParllelGC Parallel Scavenge GC 复制算法 Parallel GC 标整
UseConcMarkSweepGC Parallel New GC 复制算法 CMS和Serial Old GC 标清
UseG1GC 整体标整 局部复制

垃圾回收算法通用逻辑

12.6 CMS和G1的区别

  • G1不会引发内存碎片
  • G1对内存的精准控制,可以精准的去收集垃圾。根据设置的GC处理时间去收集垃圾最多的区域

13. JMM

java内存模型。是一种规范。

线程在操作变量的时候,首先从物理内存中复制一份到自己的工作内存中(栈内存),更新以后再写入物理内存中

特点:

  • 原子性
  • 可见性
  • 有序性

更多原创文章和学习教程请关注 同名公众号@MakerStack

面试重灾区——JVM内存结构和GC的更多相关文章

  1. 管中窥豹——从对象的生命周期梳理JVM内存结构、GC调优、类加载、AOP编程及性能监控

    如题,本文的宗旨既是透过对象的生命周期,来梳理JVM内存结构及GC相关知识,并辅以AOP及双亲委派机制原理,学习不仅仅是海绵式的吸收学习,还需要自己去分析why,加深对技术的理解和认知,祝大家早日走上 ...

  2. JVM内存结构与GC

    JVM内存模型总体架构图 程序计数器多线程时,当线程数超过CPU数量或CPU内核数量,线程之间就要根据时间片轮询抢夺CPU时间资源.因此每个线程有要有一个独立的程序计数器,记录下一条要运行的指令.线程 ...

  3. 面试官,Java8 JVM内存结构变了,永久代到元空间

    在文章<JVM之内存结构详解>中我们描述了Java7以前的JVM内存结构,但在Java8和以后版本中JVM的内存结构慢慢发生了变化.作为面试官如果你还不知道,那么面试过程中是不是有些露怯? ...

  4. 一文搞懂JVM内存结构+GC

    一.jvm是干什么的? 大家都知道java是跨平台语言,一次编译可以在不同操作系统上运行,怎么做到的呢,看下图: javac把写的源代码(java文件),编译成字节码(class文件),字节码部署到l ...

  5. java面试-JVM内存结构

    一.JVM内存结构 二.类加载(classLoader)机制 java中的ClassLoader详解 java类加载机制面试题 java类加载机制面试题 虚拟机把描述类的数据从Class文件加载到内存 ...

  6. 面试官问我JVM内存结构,我真的是

    面试官:今天来聊聊JVM的内存结构吧? 候选者:嗯,好的 候选者:前几次面试的时候也提到了:class文件会被类加载器装载至JVM中,并且JVM会负责程序「运行时」的「内存管理」 候选者:而JVM的内 ...

  7. 面试~jvm(JVM内存结构、类加载、双亲委派机制、对象分配,了解垃圾回收)

    一.JVM内存结构 ▷ 谈及内存结构各个部分的数据交互过程:还可以再谈及生命周期.数据共享:是否GC.是否OOM 答:jvm 内存结构包括程序计数器.虚拟机栈.本地方法栈.堆.方法区:它是字节码运行时 ...

  8. JAVA8 JVM内存结构变了,永久代到元空间

    在文章<JVM之内存结构详解>中我们描述了Java7以前的JVM内存结构,但在Java8和以后版本中JVM的内存结构慢慢发生了变化.作为面试官如果你还不知道,那么面试过程中是不是有些露怯? ...

  9. JVM内存结构从永久代到元空间

    在文章<JVM之内存结构详解>中我们描述了Java7以前的JVM内存结构,但在Java8和以后版本中JVM的内存结构慢慢发生了变化.作为面试官如果你还不知道,那么面试过程中是不是有些露怯? ...

随机推荐

  1. LPCTSTR的含义

    LPCTSTR: LP代表指针.C代表不可改变.T代表根据是否定义UNICODE宏而分别define为char或wchar_t.STR代表字符串. 例如: LPCTSTR lp="BMP F ...

  2. CSS中居中的完全指南(中英对照翻译)

    翻译自:https://css-tricks.com/centering-css-complete-guide/ Centering things in CSS is the poster child ...

  3. 下载 node.js 步骤 bower npm 报错解决

    1,下载node.js  ,安装之后就可以 了 2,解决"npm不是内部或外部命令"   ,打开安装的node.js  的文件夹 将这个地址,放在环境变量里面 <1,属性环境 ...

  4. C#开启线程的四种方式

    如果需要查看更多文章,请微信搜索公众号 csharp编程大全,需要进C#交流群群请加微信z438679770,备注进群, 我邀请你进群! ! ! 1.异步委托开启线程 public class Pro ...

  5. 用redis当作LRU缓存

    原文地址:https://redis.io/topics/lru-cache Redis可以用来作缓存,他可以很方便的淘汰(删除)旧数据添加新数据,类似memcached.LRU只是其中的一种置换算法 ...

  6. 《罗辑思维》试读:U盘化生存

    <罗辑思维>试读:U盘化生存 何为"U盘" 记得有一次我到一个大学去讲课,我随机做了一个调查.我说大四啦,咱们班同学谁找着工作了,一堆人举手.我又问都加入什么样的组织了 ...

  7. hugo官方相关文档地址

    +++ date="2020-10-17" title="hugo官方相关文档地址" tags=["hugo"] categories=[& ...

  8. python 保存登录状态 cookie

    import requests from lxml import etree import faker url = "https://www.yeves.cn/admin/Articles& ...

  9. 关于设置Vscode缩进,保存代码任然缩进无效解决方式

    在Vscode按F1,运行命令,输入Formatter config 把内容更改为以下代码 { "onSave": true, "javascript": { ...

  10. CF1430 E. String Reversal(div 2)

    题目链接:http://codeforces.com/contest/1430/problem/E 题意:有一串长度为n(n<=2*10^5)由小写字母组成的字符串,求通过相邻交换得到其反转串( ...