JVM 运行时数据区概述

内存是十分重要的系统资源,是硬盘和 CPU 的两头仓库及桥梁,承载着操作系统和应用程序的实时运行。JVM 内存布局规定了 Java 在运行过程中内存申请、调配、治理的策略,保障了 JVM 的高效稳固运行。不同的 JVM 对于内存的划分形式和管理机制存在着局部差别。联合 JVM 虚拟机标准,来讨论一下经典的 JVM 内存布局。

Java 虚拟机定义了若干种程序运行期间会应用到的运行时数据区,其中有一些会随着虚拟机启动而创立,随着虚拟机退出而销毁。而另外一些则是与线程一一对应的,这些与线对应的数据区域会随着线程开始和完结而创立和销毁。

JVM 整体架构

JVM 零碎线程

线程是一个程序里的运行单元。JVM 容许一个利用有多个线程并行的执行

在 HotSpot 虚拟机里,每个线程都与操作系统的本地线程间接映射。当一个 Java 线程筹备好执行当前,此时一个操作系统的本地线程也同时创立。Java 线程执行终止后,本地线程也会回收

操作系统负责所有的线程的安顿调度到任何一个可用的 CPU 上。一旦本地线程初始化胜利,它就会调用 Java 线程中的人run() 办法

如果你应用 jconsole 或者是任何一个调试工具,都能看到在后盾有许多线程在运行。这些后盾线程不包含调用 public static void main(String[] args) 的 main 线程以及所有这个 main 线程本人创立的线程。

这些次要的后盾零碎线程在 HotSpot 虚拟机里次要是以下几个

  • 虚拟机线程:这种线程的操作是须要 JVM 达到平安点才会呈现。这些操作必须在不同的线程中产生的起因是他们都须要 JVM 达到平安点,这样堆才不会变动。这种线程的执行类型包含“stop-the-world”的垃圾收集,线程栈收集,线程挂起以及偏差锁撤销
  • 周期工作线程:这种线程是工夫周期事件的体现(比方中断),他们个别用于周期性操作的调度执行
  • GC 线程:这种线程对在 JVM 里不同品种的垃圾收集行为提供了反对
  • 编译线程:这种线程在运行时会将字节码编译成本地代码
  • 信号调度线程:这种线程接管信号并发送给 JVM,在它外部通过调用适当的办法进行解决

JVM 内存构造

  • 每个线程只能拜访本人的线程栈。
  • 每个线程都不能拜访(看不见)其余线程的局部变量。
  • 所有原生类型的局部变量都存储在线程栈中,因而对其余线程是不可见的。
  • 线程能够将一个原生变量值的正本传给另一个线程,但不能共享原生局部变量自身。
  • 堆内存中蕴含了 Java 代码中创立的所有对象,不论是哪个线程创立的。其中也涵盖了包装类型(例如,Byte,Integer,Long等)。
  • 不论是创立一个对象并将其值赋值给局部变量,还是赋值给另一个对象的成员变量,创立的对象都会被保留到堆内存中。

  • 如果是原生数据类型的局部变量,那么它的内容就全副保留在线程栈上。
  • 如果是对象援用,则栈中的局部变量槽位中保留着对象的援用地址,而理论的对象内容保留在堆中。
  • 对象的成员变量与对象自身一起存储在堆上,不论成员变量的类型是原生数据类型,还是对象援用。
  • 类的动态变量则和类定义一样都保留在堆中。

  • 总结一下:办法中应用的原生数据类型和对象援用地址在栈上存储;对象、对象成员与类定义、动态变量在堆上。
  • 堆内存又称为“共享堆”,堆中的所有对象,能够被所有线程拜访,只有他们能拿到对象的援用地址。
  • 如果一个线程能够拜访某个对象时,也就能够拜访该对象的成员变量。
  • 如果两个线程同时调用某个对象的同一办法,则它们都能够拜访到这个对象的成员变量,但每个线程的局部变量正本是独立的。

JVM 内存整体构造

  • 每启动一个线程,JVM就会在栈空间栈调配对应的线程栈,比方 1MB 空间(-Xss1m)
  • 线程栈也叫做 Java 办法栈。如果应用了 JNI 办法,则会调配一个独自的本地办法栈(Native Stack)
  • 线程执行过程中,个别会有多个办法组成调用栈(Stack Trace),比方 A 调用 B,B 调用 C 。每执行到一个办法,就会创立对应的栈帧(Frame)。

JVM 栈内存机构

  • 栈帧是一个逻辑上的概念,具体的大小在一个办法编写实现后基本上就能确定。
  • 比方返回值,须要有一个空间寄存吧,每个局部变量都须要对应的地址空间,此外还有给指令应用的操作数栈,以及 Class 指针(标识这个栈帧对应的是哪个类的办法,指向非堆外面的 Class 对象)。

JVM 堆内存构造

  • 堆内存是所有线程共用的内存空间,JVM 将 Heap 内存分为年老代(Young generation)和老年代(Old generation,也叫 Tenured)两局部。
  • 年老代还划分为3个内存池,伊甸园区(Eden space)和存活区(Survivor space),在大部分GC算法中有两个存活区(S0,S1),在咱们能够察看到的任何时刻,S0和S1总有一个是空的,但个别很小,也节约不了多少空间。
  • Non-Heap实质上还是Heap,只是个别不归GC治理,外面划分为3个内存区池。
  • Metaspace 以前叫长久代(永恒代,Permanent generation),Java 换了个名字叫 Metaspace
  • CCS Compressed Class Space,寄存 class 信息的,和 Metaspace 有穿插
  • Code Cache,寄存 JIT 编译器编译后的本地机器代码。

CPU 与内存行为

  • CPU 乱序执行
  • volatile 关键字
  • 原子性操作
  • 内存屏障

Java对象模型

Java内存模型

JMM 标准对应的是 JSR-133 Java Memory Model and Thread Specification 《Java 语言标准》 $17.4 Memory Model 章节

JMM 标准明确定义了不同的线程之间通过哪些形式,在什么时候能够看见其余线程保留到共享变量中的值;以及在必要时,如何对共享变量的拜访进行同步。这样的益处是屏蔽各种硬件平台的操作系统之间的内存拜访差别,实现了Java并发程序真正的跨平台。

  • 所有的对象(包含外部的实例成员变量),static 变量,以及数组,都必须寄存到堆内存中。
  • 局部变量,办法的形参/入参,异样解决语句的入参不容许在线程之间共享,所以不受内存模型的影响。
  • 多个线程同时对一个变量拜访时【读取/写入】,这时候只有有某个线程执行的是写操作,那么这种景象称之为“抵触”。
  • 能够被其余线程影响或感知的操作,称为线程间的交互行为,可分为:读取、写入、同步操作、内部操作等等。其中同步操作包含:对 volatile 变量的读写,对管程(monitor)的锁定与解锁,线程的起始操作与结尾操作,线程启动和完结等等。内部操作则是指对线程执行环境之外的操作,比方进行其余线程等等。
  • JMM 标准的是线程间的交互操作,而不论线程外部对局部变量进行的操作。

JVM 启动参数

  • 以 - 结尾为规范参数,所有的 JVM 都要实现这些参数,并且向后兼容。例,-server
  • -D 设置零碎属性。例,-Dfile.encoding=UTF-8
  • 以 -X 结尾为非标准参数,根本都是传给 JVM 的,默认 JVM 实现这些参数的性能,然而并不保障所有 JVM 实现都满足,且不保障向后兼容。能够应用 java -X 命令来查看以后 JVM 反对的非标准参数。例,-Xmx8g
  • 以 -XX: 结尾为非稳固参数,专门用于管制 JVM 的行为,跟具体的 JVM 实现无关,随时可能会在下个版本勾销。

    • -XX: +-Flags 模式,+-是对布尔值进行开关。例,-XX:+UseG1GC
    • -XX: key=value 模式,指定某个选项的值。例,-XX:MaxPermSize=256m

1.零碎属性参数

-Dfile.encoding=UTF-8-Duser.timezone=GMT+08-Dmaven.test.skip=true-Dio.netty.eventLoopThreads=8// 还能够这样System.setProperty("a", "A100");String a = System.getProperty("a");

2.运行模式参数

  • -server: 设置 JVM 应用 server 模式,特点是启动速度比较慢,但运行时性能和内存管理效率很高,实用于生产环境。在具备 64 位能力的 JDK 环境下将默认启用该模式,而疏忽 -client 参数。
  • -client: JDK1.7 之前在32位的 x86 机器上的默认值是 -client 选项。设置 JVM 应用 client 模式,特点是启动速度比拟快,但运行时性能和内存管理效率不高,通常用于客户端应用程序或者 PC 利用开发和调试。此外,咱们晓得 JVM 加载字节码后,能够解释执行,也能够编译成本地代码再执行,所以能够配置 JVM 对字节码的解决模式。
  • -Xint: 在解释模式(interpreted mode)下运行,-Xint 标记会强制 JVM 解释执行所有的字节码,这当然会升高运行速度,通常低10倍或更多。
  • -Xcomp: -Xcomp 参数与 -Xint 正好相同,JVM 在第一次应用时会把所有的字节码编译成本地代码,从而带来最大水平的优化。【留神预热】
  • -Xmixed: -Xmixed 是混合模式,将解释模式和编译模式进行混合应用,有 JVM 本人决定,这是 JVM 的默认模式,也是举荐模式。 咱们应用 java -version 能够看到 mixed mode 等信息。

3.堆内存设置参数

  • -Xmx, 指定最大堆内存。 如 -Xmx4g. 这只是限度了 Heap 局部的最大值为4g。这个内存不包含栈内存,也不包含堆外应用的内存。
  • -Xms, 指定堆内存空间的初始大小。 如 -Xms4g。 而且指定的内存大小,并不是操作系统理论调配的初始值,而是GC先布局好,用到才调配。专用服务器上须要放弃 –Xms 和 –Xmx 统一,否则利用刚启动可能就有好几个 FullGC。 当两者配置不统一时,堆内存扩容可能会导致性能抖动。
  • -Xmn, 等价于 -XX:NewSize,应用 G1 垃圾收集器 不应该 设置该选项,在其余的某些业务场景下能够设置。官网倡议设置为 -Xmx 的 1/2 ~ 1/4.
  • -XX:MaxPermSize=size, 这是 JDK1.7 之前应用的。Java8 默认容许的 Meta空间无限大,此参数有效。
  • -XX:MaxMetaspaceSize=size, Java8 默认不限度 Meta 空间, 个别不容许设置该选项。
  • -XX:MaxDirectMemorySize=size,零碎能够应用的最大堆外内存,这个参数跟 -Dsun.nio.MaxDirectMemorySize 成果雷同。
  • -Xss, 设置每个线程栈的字节数,影响栈的深度。 例如 -Xss1m 指定线程栈为 1MB,与-XX:ThreadStackSize=1m 等价
  1. 如果什么都不配置会如何?
  2. Xmx 是否与 Xms 设置相等?
  3. Xmx 设置为机器内存的什么比例适合?
  4. 作业: 画一下 Xmx、Xms、Xmn、Meta、DirectMemory、Xss 这些内存参数的关系

4.GC设置参数

  • -XX:+UseG1GC:应用 G1 垃圾回收器
  • -XX:+UseConcMarkSweepGC:应用 CMS 垃圾回收器
  • -XX:+UseSerialGC:应用串行垃圾回收器
  • -XX:+UseParallelGC:应用并行垃圾回收器
  • -XX:+UnlockExperimentalVMOptions -XX:+UseZGC // Java 11+
  • -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC // Java 12+

各个 JVM 版本的默认 GC 是什么?

5.剖析诊断参数

  • -XX:+-HeapDumpOnOutOfMemoryError 选项, 当 OutOfMemoryError 产生,即内存溢出(堆内存或长久代)时,主动 Dump 堆内存。

    • 示例用法: java -XX:+HeapDumpOnOutOfMemoryError -Xmx256m ConsumeHeap
  • -XX:HeapDumpPath 选项, 与 HeapDumpOnOutOfMemoryError 搭配应用, 指定内存溢出时 Dump 文件的目 录。如果没有指定则默认为启动 Java 程序的工作目录。

    • 示例用法: java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/usr/local/ ConsumeHeap
    • 主动 Dump 的 hprof 文件会存储到 /usr/local/ 目录下
  • -XX:OnError 选项, 产生致命谬误时(fatal error)执行的脚本。

    • 例如, 写一个脚本来记录出错工夫, 执行一些命令, 或者 curl 一下某个在线报警的 url. 示例用法:java -XX:OnError="gdb - %p" MyApp
    • 能够发现有一个 %p 的格式化字符串,示意过程 PID。
  • -XX:OnOutOfMemoryError 选项, 抛出 OutOfMemoryError 谬误时执行的脚本。
  • -XX:ErrorFile=filename 选项, 致命谬误的日志文件名,绝对路径或者相对路径。
  • -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=1506,近程调试

6.JavaAgent参数

Agent 是 JVM 中的一项黑科技, 能够通过无侵入形式来做很多事件,比方注入 AOP 代码,执行统计等等,权限十分大。这里简略介绍一下配置选项,具体性能须要专门来讲。

设置 agent 的语法如下:

  • -agentlib:libname[=options] 启用 native 形式的 agent, 参考 LD_LIBRARY_PATH 门路。
  • -agentpath:pathname[=options] 启用 native 形式的 agent。
  • -javaagent:jarpath[=options] 启用内部的 agent 库, 比方 pinpoint.jar 等等。
  • -Xnoagent 则是禁用所有 agent。 以下示例开启 CPU 应用工夫抽样剖析:

    • JAVA_OPTS="-agentlib:hprof=cpu=samples,file=cpu.samples.log"