JVM原理

简介

本教程属于Java进阶篇,本教程将带领你了解什么是JVM,以及JVM的作用、结构、算法、启动流程、配置参数、监控命令和工具等。
针对Java虚拟机的实现有专门的《Java虚拟机规范》,在这一规范下有多种不同的虚拟机实现。
Java SE默认使用的是HotSpot版本的虚拟机。所以通常情况下,我们所讲的java虚拟机指的是HotSpot的版本。
本文讲解内容基于版本:Java8。

什么是JVM

JVM是Java Virtual Machine的缩写,即Java虚拟机。
Java虚拟机只要有两方面作用:

  1. Java语言是平台无关的语言,即一次编译就可以在不同的平台上运行。这主要是靠Java虚拟机的配合。在不同系统中需要安装对应系统的Java虚拟机,而编译后的Java代码是运行在Java虚拟机上的。所以,Java虚拟机帮助Java程序隔离了系统的差异。
  2. Java语言与C语言、C++相比复杂性和开发难度大大降低,这主要归功于Java的内存托管机制,即我们在编写程序时无需过多考虑内存的分配与释放,Java虚拟机会帮助我们完成内存的管理。

本文主要针对Java虚拟机的内存管理机制做讲解。

JVM结构

在这里插入图片描述

线程私有内存:橘色块为线程私有内存。

  • 程序计数器:用于保存当前线程执行的现场(指令偏移量、行号等)。用于在多线程情况下,线程切换的状态保存与恢复。

  • 虚拟机栈:当前线程执行的方法栈。每次出入一个方法都会对应一次栈操作。主要结构如下
    在这里插入图片描述

  • 本地方法栈:与虚拟机栈相似,只不过本地方法栈主要为native方法服务(C、C++实现的方法)。

线程共享内存:

  • Java堆:存放Java对象实例,存放字符串常量。这里是GC的主要区域。
    在这里插入图片描述

  • 元空间:类元信息(klass)、字段、静态属性、方法、常量,还有运行时常量池等。
    在这里插入图片描述

线程共享内存存放的数据可以通过ThreadLocal转换为线程私有。

内存分配

了解垃圾回收之前,先要了解JVM怎么分配内存,然后识别哪些内存是垃圾,需要回收。最后才是用什么方式回收。

JVM向系统申请内存

Java与C、C++不同,C、C++每次申请内存都要调用malloc进行系统调用,这需要一定的开销。Java虚拟机是一次性申请一块较大的空间,除非需要进行内存扩展,否则都是在这一块内存中由虚拟机进行分配和释放。
这样减少了系统调用次数,节省了一定的开销,我们可以把这一块内存称作内存池
但缺点也是显而易见的,Java进程不论实际用到了多少内存都要先占用一块较大的内存空间(这也是Java通常被诟病的地方),而且还要为虚拟机的内存管理单独制定算法。

JVM内部内存分配

  • 静态内存:在编译时就能确定的内存是静态内存,比如局部的基本类型变量和类引用等,这类内存的长度往往是固定。Java栈、本地方法栈、程序计数器,这些都是长度固定而且是线程私有,当线程销毁时内存自然就跟着回收了。
  • 动态内存:在程序执行时才会知道要分配多少内存,比如我们在程序执行时才会知道对象是否会被真正的创建,而且每个对象占用的空间会有所不同。经过一次分配与回收之后,内存会存在很多碎片。这就导致我们需要一个相对复杂的策略来分配和回收这部分内存,不断地消除内存的碎片化。

垃圾检测与回收算法

垃圾回收器要完成两件事:检测出垃圾、回收垃圾。

检测垃圾算法

垃圾检测法有引用计数法和可达性算法。我们这里只讲解Java虚拟机采用的可达性算法。
可达性算法:以根集对象为起点进行搜索,如果有对象不可达,即为垃圾对象。
根集对象包括:

  • 虚拟机栈中引⽤的对象
  • 本地⽅法栈中Native⽅法引⽤的对象
  • ⽅法区中类静态属性引⽤的对象
  • ⽅法区中常量引⽤的对象
  • 所有被同步锁synchronized持有的对象
  • Java虚拟机内部的引⽤,Class对象、异常、类加载器等。
  • 反映java虚拟机内部情况的JMXBean、JVMTI中注册的回调、本地代码缓存等。

回收算法

  • 标记-清除算法:分为两个阶段,标记阶段和清除阶段。标记阶段的任务是标记出所有需要被回收的对象,然后统一回收。这是最基础的算法,后续收集算法都是基于这个算法扩展的。
    在这里插入图片描述

不足:效率低,清除后会产生大量碎片。

  • 复制:把空间划分成两个区域,每次只是用其中一个区域。垃圾回收时遍历当前正在使用的区域,把正在使用的对象复制到另外一个空白区域。该方法能够避免碎片问题。
    在这里插入图片描述

不足:需要额外的内存空间。

  • 标记-整理:先做标记,然后把标记后的对象向一端移动,然后直接清理掉边界意外的内存空间。
    在这里插入图片描述

不足:移动对象成本高。

  • 分代收集算法:这是当前商业虚拟机常用的垃圾回收算法。分代回收策略是基于这样一个事实:不同对象的生命周期是不一样的。因此不同生命周期的对象可以采取不同的回收方式。可提高效率。
    在这里插入图片描述

Yung区采用复制算法,old区采用标记-整理算法。

JVM常用参数

JVM堆参数

  • -Xms:用于设置堆初始化内存的大小,可使用单位K,M,G
  • -Xmx:用于设置堆最大内存的大小,可使用单位K,M,G
  • -XX:NewSize和-XX:MaxNewSize:用于设置年轻代的大小,建议设为整个堆大小的1/3或者1/4,两个值设为一样大。
  • -XX:SurvivorRatio:它定义了新生代中Eden区域和Survivor区域(From幸存区或To幸存区)的比例,默认为-XX:SurvivorRatio=8,也就是说Eden占新生代的8/10,From幸存区或To幸存区各占新生代的1/10。
  • -XX:NewRatio:新生代和老年代的比例,比如:-XX:NewRatio=4表示 新生代:老年代=1:4

一些例子:

# Start with 256MB of memory, and allow the Java process to use up to 4G (4096MB) of memory.
java -Xms256m -Xmx4g -jar abc.jar
java -Xms512m -Xmx1g -Xmn512m -XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=512M -XX:-UseParallelGC -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintGCDateStamps -Xloggc:/www/logs/xxx/xxx_gc.log -jar xxx.jar --spring.profiles.active=dev
JAVA_OPTS="-server -Xms256m  -Xms512m  -XX:PermSize=64M  -XX:MaxPermSize=128m -Djava.awt.headless=true"
JAVA_OPTS="-server -Xms4g -Xmx4g -Xss512k -Xmn1g -
XX:CMSInitiatingOccupancyFraction=65 -XX:+AggressiveOpts -XX:+UseBiasedLocking -
XX:+DisableExplicitGC -XX:MaxTenuringThreshold=10 -XX:NewRatio=2 -
XX:PermSize=128m -XX:MaxPermSize=512m -XX:CMSFullGCsBeforeCompaction=5 -
XX:+ExplicitGCInvokesConcurrent -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -
XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -
XX:LargePageSizeInBytes=128m -XX:+UseFastAccessorMethods"
JAVA_OPTS=-server -Xms1536m -Xmx1536m -XX:NewSize=320m -XX:MaxNewSize=320m -XX:PermSize=96m -XX:MaxPermSize=256m -Xmn500m -XX:MaxTenuringThreshold=5

元数据区

  • -XX:MetaspaceSize:达到该值就会触发垃圾收集进行类型卸载,同时GC会对该值进行调整:如果释放了大量的空间,就适当降低该值;如果释放了很少的空间,那么在不超过MaxMetaspaceSize时,适当提高该值。
  • -XX:MaxMetaspaceSize:最大空间,默认是没有限制的。
  • -XX:MinMetaspaceFreeRatio:最小空闲比,当 Metaspace 发生 GC 后,会计算 Metaspace 的空闲比,如果空闲比(空闲空间/当前 Metaspace 大小)小于此值,就会触发 Metaspace 扩容。默认值是 40 ,也就是 40%,例如 -XX:MinMetaspaceFreeRatio=40
  • -XX:MaxMetaspaceFreeRatio:最大空闲比,当 Metaspace 发生 GC 后,会计算 Metaspace 的空闲比,如果空闲比(空闲空间/当前 Metaspace 大小)大于此值,就会触发 Metaspace 释放空间。默认值是 70 ,也就是 70%,例如 -XX:MaxMetaspaceFreeRatio=70

栈参数

  • -Xss:栈空间大小,栈是线程独占的,所以是一个线程使用栈空间的大小,例如 -Xss256K,如果不设置此参数,默认值是 1M,一般来讲设置成 256K 就足够了。

GC日志

  • -verbose:gc或-XX:+PrintGC:简单日志。
  • -XX:+PrintGCDetails:详细日志。
  • -XX:+PrintGCDateStamps:打印 GC 的时间点。
  • -XX:+PrintHeapAtGC:GC 前后的堆信息
  • -XX:+PrintGCApplicationStoppedTime:GC 导致的 Stop the world 时间
  • -Xloggc:/jvmlog/gc.log:日志输出到文件
  • -XX:+HeapDumpOnOutOfMemoryError:发生heap或meta溢出时生成dump
  • -XX:HeapDumpPath=/jvmlog:dump目录

垃圾回收器

GC主要分二类,新生代GC,老年代GC;
新生代GC包括:串行GC、并行GC、并行回收GC;
老年代GC包括:串行GC、并行GC、CMS;
G1比较特殊,同时支持新生代和老年代。

  • -XX:+UseConcMarkSweepGC加上-XX:+UseParNewGC:暂停时间优先: 并行GC + CMS
  • -XX:+UseParallelOldGC:吞吐量优先: 并行回收GC + 并行GC
  • -XX:+UseParallelGC:Parallel Scavenge + Serial Old,JDK 8 server 模式下的默认设置

参考文献:
JVM 中你不可不知的参数
JVM8内存、堆模型、垃圾回收器总结

JVM性能监控工具

Linux监控

  • uptime:系统时间、运行时间、终端数、平均负载
  • top:同uptime,外加cpu内存等信息
  • vmstat:一般vmstat工具的使用是通过两个数字参数来完成的,第一个参数是采样的时间间隔数,单位是秒,第二个参数是采样的次数,打印进程、cpu、内存、swap、io等信息。
  • pidstat:细致观察进程和线程。

Java监控

  • jps:列出java进程类似ps命令。
  • jinfo:可查看Java程序的扩展参数,甚至可以在运行时修改部分参数。
  • Jhat:查看dump文件
  • jstack:打印线程、栈信息
  • jmap:生成Java应用堆快照和对象统计信息。
  • jstat:JVM统计监测。

例子:

jmap -histo <pid> # 打印对象统计信息

例子:

jmap -dump:format=b,file=<file-path> <pid> # 生成快照

例子:
可配合watch命令一起使用

 watch jmap -heap <pid> # 打印堆信息

结果:

Attaching to process ID 25419, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.131-b11

using thread-local object allocation.
Parallel GC with 4 thread(s)
# 堆配置
Heap Configuration:
   MinHeapFreeRatio         = 0
   MaxHeapFreeRatio         = 100
   MaxHeapSize              = 2051014656 (1956.0MB)
   NewSize                  = 42991616 (41.0MB)
   MaxNewSize               = 683671552 (652.0MB)
   OldSize                  = 87031808 (83.0MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 1073741824 (1024.0MB)
   MaxMetaspaceSize         = 17592186044415 MB
   G1HeapRegionSize         = 0 (0.0MB)

# 堆内存使用情况
Heap Usage:
PS Young Generation
Eden Space:
   capacity = 341835776 (326.0MB)
   used     = 326467304 (311.3434829711914MB)
   free     = 15368472 (14.656517028808594MB)
   95.50413588073356% used
From Space:
   capacity = 48234496 (46.0MB)
   used     = 14614528 (13.9375MB)
   free     = 33619968 (32.0625MB)
   30.29891304347826% used
To Space:
   capacity = 45613056 (43.5MB)
   used     = 0 (0.0MB)
   free     = 45613056 (43.5MB)
   0.0% used
PS Old Generation
   capacity = 181403648 (173.0MB)
   used     = 116113280 (110.7342529296875MB)
   free     = 65290368 (62.2657470703125MB)
   64.00823868768063% used

17633 interned Strings occupying 1610056 bytes.

例子:

jstat -gc <pid> 250 4

结果:

 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
44544.0 47104.0  0.0   14272.0 333824.0 324577.7  177152.0   113391.9  51160.0 48283.5 6656.0 6139.5     13    0.543   3      0.901    1.444
44544.0 47104.0  0.0   14272.0 333824.0 324577.7  177152.0   113391.9  51160.0 48283.5 6656.0 6139.5     13    0.543   3      0.901    1.444
44544.0 47104.0  0.0   14272.0 333824.0 324577.7  177152.0   113391.9  51160.0 48283.5 6656.0 6139.5     13    0.543   3      0.901    1.444
44544.0 47104.0  0.0   14272.0 333824.0 324577.7  177152.0   113391.9  51160.0 48283.5 6656.0 6139.5     13    0.543   3      0.901    1.444

字段描述:

  • S0C S1C S0U S1U:Survivor 0/1 容量和使用情况
  • EC EU:Eden区容量和使用情况
  • OC OU:老年代容量和使用情况
  • MC MU:永久代容量和使用情况
  • CCSC CCSU:当前压缩类空间容量和使用情况
  • YGC YGCT:年轻代GC次数和耗时
  • FGC FGCT:Full GC次数和耗时
  • GCT:GC总耗时
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
深入java虚拟机第二版 第1章 Java体系结构介绍 1.1 为什么使用Java 1.2 网络带来的挑战和机遇 1.3 体系结构 1.3.1 Java虚拟机 1.3.2 类装载器的体系结构 1.3.3 Java class文件 1.3.4 Java API 1.3.5 Java程序设计语言 1.4 Java体系结构的代价 1.5 结论 1.6 资源页 第2章 平台无关 2.1 为什么要平台无关 2.2 Java的体系结构对平台无关的支持 2.2.1 Java平台 2.2.2 Java语言 2.3.3 Java class文件 . 2.2.4 可伸缩性 2.3 影响平台无关性的因素 2.3.1 Java平台的部署 2.3.2 Java平台的版本 2.3.3 本地方法 2.3.4 非标准运行时库 2.3.5 对虚拟机的依赖 2.3.6 对用户界面的依赖 2.3.7 Java平台实现中的bug 2.3.8 测试 2.4 平台无关的七个步骤 2.5 平台无关性的策略 2.6 平台无关性和网络移动对象 2.7 资源页 第3章 安全 3.1 为什么需要安全性 3.2 基本沙箱 3.3 类装载器体系结构 3.4 class文件检验器 3.4.1 第一趟:class文件的结构检查 3.4.2 第二趟:类型数据的语义检查 3.4.3 第三趟:字节码验证 3.4.4 第四趟:符号引用的验证 3.4.5 二进制兼容 3.5 Java虚拟机中内置的安全特性 3.6 安全管理器和Java API 3.7 代码签名和认证 3.8 一个代码签名示例 3.9 策略 3.10 保护域 3.11 访问控制器 3.11.1 implies()方法 3.11.2 栈检查示例 3.11.3 一个回答“是”的栈检查 3.11.4 一个回答“不”的栈检查 3.11.5 doPrivileged()方法 3.11.6 doPrivileged()的一个无效使用 3.12 Java安全模型的不足和今后的发展 方向 3.13 和体系结构无关的安全性 3.14 资源页 第4章 网络移动性 4.1 为什么需要网络移动性 4.2 一种新的软件模式 4.3 Java体系结构对网络移动性的支持 4.4 applet:网络移动性代码的示例 4.5 Jini服务对象:网络移动对象的示例 4.5.1 Jini是什么 4.5.2 Jini如何工作 4.5.3 服务对象的优点 4.6 网络移动性:Java设计的中心 4.7 资源页 第5章 Java虚拟机 5.1 Java虚拟机是什么 5.2 Java虚拟机的生命周期 5.3 Java虚拟机的体系结构 5.3.1 数据类型 5.3.2 字长的考量 5.3.3 类装载器子系统 5.3.4 方法区 5.3.5 堆 5.3.6 程序计数器 5.3.7 Java栈 5.3.8 栈帧 5.3.9 本地方法栈 5.3.10 执行引擎 5.3.11 本地方法接口 5.4 真实机器 5.5 一个模拟:“Eternal Math” 5.6 随书光盘 5.7 资源页 第6章 Java class文件 6.1 Java class文件是什么 6.2 class文件的内容 6.3 特殊字符串 6.3.1 全限定名 6.3.2 简单名称 6.3.3 描述符 6.4 常量池 6.4.1 CONSTANT_Utf8_info表 6.4.2 CONSTANT_Integer_info表 6.4.3 CONSTANT_Float_info表 6.4.4 CONSTANT_Long_info表 6.4.5 CONSTANT_Double_info表 6.4.6 CONSTANT_Class_info表 6.4.7 CONSTANT_String_info表 6.4.8 CONSTANT_Fieldref_info表 6.4.9 CONSTANT_Methodref_info表 6.4.10 CONSTANT_InterfaceMethodref_ info表 6.4.11 CONSTANT_NameAndType_info 表 6.5 字段 6.6 方法 6.7 属性 6.7.1 属性格式 6.7.2 Code属性 6.7.3 ConstantValue属性 6.7.4 Deprecated属性 6.7.5 Exceptions属性 6.7.6 InnerClasses属性 6.7.7 LineNumberTable属性 6.7.8 LocalVariableTable属性 6.7.9 SourceFile属性 6.7.10 Synthetic属性 6.8 一个模拟:“Getting Loaded” 6.9 随书光盘 6.10 资源页 第7章 类型的生命周期 7.1 类型装载、连接与初始化 7.1.1 装载 7.1.2 验证 7.1.3 准备 7.1.4 解析 7.1.5 初始化 7.2 对象的生命周期 7.2.1 类实例化 7.2.2 垃圾收集和对象的终结 7.3 卸载类型 7.4 随书光盘 7.5 资源页 第8章 连接模型 8.1 动态连接和解析 8.1.1 解析和动态扩展 8.1.2 类装载器与双亲委派模型 8.1.3 常量池解析 8.1.4 解析CONSTANT_Class_info入口 8.1.5 解析CONSTANT_Fieldref_info 入口 S.1.6 解析CONSTANT_Methodref_info 入口 8.1.7 解析CONSTANT_Interface- Methodref_info入口 8.1.8 解析CONSTANT_String_info入口 8.1.9 解析其他类型的入口 8.1.10 装载约束 8.1.11 编译时常量解析 8.1.12 直接引用 8.1.13 _quick指令 8.1.14 示例:Salutation程序的连接 8.1.15 示例:Greet程序的动态扩展 8.1.16 使用1.1版本的用户自定义类装 载器 8.1.17 使用1.2版本的用户自定义类装 载器 8.1.18 示例:使用forName()的动态扩展 8.1.19 示例:卸载无法触及的greeter类 8.1.20 示例:类型安全性与装载约束 8.2 随书光盘 8.3 资源页 第9章 垃圾收集 9.1 为什么要使用垃圾收集 9.2 垃圾收集算法 9.3 引用计数收集器 9.4 跟踪收集器 9.5 压缩收集器 9.6 拷贝收集器 9.7 按代收集的收集器 9.8 自适应收集器 9.9 火车算法 9.9.1 车厢、火车和火车站 9.9.2 车厢收集 9.9.3 记忆集合和流行对象 9.10 终结 9.11 对象可触及性的生命周期 9.11.1 引用对象 9.11.2 可触及性状态的变化 9.11.3 缓存、规范映射和临终清理 9.12 一个模拟:“Heap of Fish” 9.12.1 分配鱼 9.12.2 设置引用 9.12.3 垃圾收集 9.12.4 压缩堆 9.13 随书光盘 9.14 资源页 第10章 栈和局部变量操作 10.1 常量入栈操作 10.2 通用栈操作 10.3 把局部变量压入栈 10.4 弹出栈顶部元素,将其赋给局部变量 10.5 wide指令 10.6 一个模拟:“Fibonacci Forever” 10.7 随书光盘 10.8 资源页 第11章 类型转换 11.1 转换操作码 11.2 一个模拟:“Conversion Diversion” 11.3 随书光盘 11.4 资源页 第12章 整数运算 12.1 二进制补码运算 12.2 Inner Int:揭示Java int类型内部性质 的applet 12.3 运算操作码 12.4 一个模拟:“Prime Time” 12.5 随书光盘 12.6 资源页 第13章 逻辑运算 13.1 逻辑操作码 13.2 一个模拟:“Logical Results” 13.3 随书光盘 13.4 资源页 第14章 浮点运算 14.1 浮点数 14.2 Inner Float:揭示Java float类型内部 性质的applet 14.3 浮点模式 14.3.1 浮点值集合 14.3.2 浮点值集的转换 14.3.3 相关规则的本质 14.4 浮点操作码 14.5 一个模拟:“Circle of Squares” 14.6 随书光盘 14.7 资源页 第15章 对象和数组 15.1 关于对象和数组的回顾 15.2 针对对象的操作码 15.3 针对数组的操作码 15.4 一个模拟:“Three—Dimensional Array” 15.5 随书光盘 15.6 资源页 第16章 控制流 16.1 条件分支 16.2 五条件分支 16.3 使用表的条件分支 16.4 一个模拟:“Saying Tomato” 16.5 随书光盘 16.6 资源页 第17章 异常 17.1 异常的抛出与捕获 17.2 异常表 17.3 一个模拟:“Play Ball!” 17.4 随书光盘 17.5 资源页 第18章 finally子句 18.1 微型子例程 18.2 不对称的调用和返回 18.3 一个模拟:“Hop Around” 18.4 随书光盘 18.5 资源页 第19章 方法的调用与返回 19.1 方法调用 19.1.1 Java方法的调用 19.1.2 本地方法的调用 19.2 方法调用的其他形式 19.3 指令invokespecial 19.3.1 指令invokespecial和[init]()方法 19.3.2 指令invokespecial和私有方法 19.3.3 指令invokespecial和super关键字 19.4 指令invokeinterface 19.5 指令的调用和速度 19.6 方法调用的实例 19.7 从方法中返回 19.8 随书光盘 19.9 资源页 第20章 线程同步 20.1 监视器 20.2 对象锁 20.3 指令集中对同步的支持 20.3.1 同步语句 20.3.2 同步方法 20.4 Object类中的协调支持 20.5 随书光盘 20.6 资源页 附录A 按操作码助记符排列的指令集 附录B 按功能排列的操作码助记符 附录C 按操作码字节值排列的操作码助

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值