L1:JVM核心技术(一):基础知识

【目录】
1.JVM基础知识
2.字节码技术(源码与编译.class转换)
3.JVM类加载器*
4.JVM内存模型*
5.JVM启动参数

基础知识

Java 是一种面向对象、静态类型、编译执行,有 VM/GC 和运行时、跨平台的高级语言

  1. 静态语言和动态语言区别

区别一:静态语言在编译时变量的数据类型即可确定,多数静态类型语言在使用变量之前必须申明数据类型
区别二:静态语言编译时进行类型匹配检查,所以不能给变量赋不同类型值;动态类型变量类型在运行期可变,意味着对象的多态性
区别三:静态语言封闭,使得第三方开发包对代码侵入性小;动态语言简洁

  1. 编译执行和解释执行

编译执行:类似于“全文翻译”,Java类编译为.class字节码文件,一处编译,处处执行
解释执行:类似于“同声传译”,逐行解释执行
JVM采取两种方式结合

字节码技术

  1. 什么是字节码
    Java Code由单字节(byte)的指令组成,理论支持256个操作码(opdoce);实际只使用了200个左右,还有一些操作码保留给调试操作

    根据指令性质,主要分类:
    1. 栈操作指令
    2. 程序流程控制指令
    3. 对象操作指令,包括方法调用
    4. 算数运算以及类型转换指令

编译:javac demo/hello.java
查看字节码:javap -c demo.hello   
查看字节码(更多内容):javap -c -verbose demo.hello

在这里插入图片描述
2. 字节码运行时结构

  • JVM是一台基于栈的计算器
  • 每个线程都有一个独属于自己的线程栈(JVM Stack),用于存储栈帧(Frame)
  • 每一次方法调用,JVM都会自动创建一个栈帧
  • 栈帧由操作数,局部变量数组以及一个Class引用组成
  • Class引用指向当前方法在运行时常量池中对应的Class
    运行时结构
    助记符到二级制码之间一一映射,关系如下:
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述
    方法的调用指令:

invokestatic,顾名思义,这个指令用于调用某个类的静态方法,这是方法调用指令中最快的一个。
invokespecial, 用来调用构造函数,但也可以用于调用同一个类中的 private 方法, 以及可见的超类方法。
invokevirtual,如果是具体类型的目标对象,invokevirtual 用于调用公共,受保护和package 级的私有方法。
invokeinterface,当通过接口引用来调用方法时,将会编译为 invokeinterface 指令。
invokedynamic,JDK7 新增加的指令,是实现“动态类型语言”(Dynamically Typed Language)支持而进行的升级改进,同时也是 JDK8 以后支持 lambda 表达式的实现基础

JVM类加载器

类的生命周期

类的生命周期:加载-{链接}[验证-准备-解析]-初始化-使用-卸载
在这里插入图片描述

  1. 加载(Loading):找 Class 文件
  2. 验证(Verification):验证格式、依赖
  3. 准备(Preparation):静态字段、方法表
  4. 解析(Resolution):符号解析为引用
  5. 初始化(Initialization):构造器、静态变
    量赋值、静态代码块
  6. 使用(Using)
  7. 卸载(Unloading)

类加载

类加载时机

  1. 当虚拟机启动时,初始化用户指定的主类,就是启动执行的 main 方法所在的类;
  2. 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类,就是 new 一个类的时候要初始化;
  3. 当遇到调用静态方法的指令时,初始化该静态方法所在的类;
  4. 当遇到访问静态字段的指令时,初始化该静态字段所在的类;
  5. 子类的初始化会触发父类的初始化;
  6. 如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,会触发该接口的初始化;
  7. 使用反射 API 对某个类进行反射调用时,初始化这个类,其实跟前面一样,反射调用要么是已经有实例了,要么是静态方法,都需要初始化;
  8. 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类

不会初始化(可能会加载)

  1. 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。
  2. 定义对象数组,不会触发该类的初始化。
  3. 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类。
  4. 通过类名获取 Class 对象,不会触发类的初始化,Hello.cla ss 不会让 Hello 类初始化。
  5. 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。Class.forName(“jvm.Hello”)默认会加载 Hello 类。
  6. 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作(加载了,但是不初始化)

类加载器

三类加载器:

  1. 启动类加载器(BootstrapClassLoader)
  2. 扩展类加载器(ExtClassLoader)
  3. 应用类加载器(AppClassLoader

加载器的特点
4. 双亲委托
5. 负责依赖
6. 缓存加载
在这里插入图片描述
自定义类加载器实现

添加引用类的几种方式:

  1. 放到 JDK 的 lib/ext 下,或者-Djava.ext.dirs
  2. java –cp/classpath 或者 class 文件放到当前路径
  3. 自定义 ClassLoader 加载
  4. 拿到当前执行类的 ClassLoader,反射调用 addUrl 方法添加 Jar 或路径(JDK9 无效)

JVM内存模型

内存结构

在这里插入图片描述

每个线程都只能访问自己的线程栈;
每个线程都不能访问(看不见)其他线程的局部变量;
所有原生类型的局部变量都存储在线程栈中,因此对其他线程是不可见的;
线程可以将一个原生变量值的副本传给另一个线程,但不能共享原生局部变量本身;
堆内存中包含了 Java 代码中创建的所有对象,不管是哪个线程创建的。 其中也涵盖了包装类型
(例如 Byte,Integer,Long 等);
不管是创建一个对象并将其赋值给局部变量, 还是赋值给另一个对象的成员变量, 创建的对象都会被保存到堆内存中;

在这里插入图片描述

如果是原生数据类型的局部变量,那么它的内容就全部保留在线程栈上;
如果是对象引用,则栈中的局部变量槽位中保存着对象的引用地址,而实际的对象内容保存在堆中;
对象的成员变量与对象本身一起存储在堆上, 不管成员变量的类型是原生数值,还是对象引用;
类的静态变量则和类定义一样都保存在堆中;

小结:

  • 方法中使用的原生数据类型和对象引用地址在栈上存储,对象、对象成员与类定义、静态变
    量在堆上;
  • 堆内存又称为“共享堆”,堆中的所有对象,可以被所有线程访问, 只要他们能拿到对象的引用地址。如果一个线程可以访问某个对象时,也就可以访问该对象的成员变量;
  • 如果两个线程同时调用某个对象的同一方法,则它们都可以访问到这个对象的成员变量,但每个线程的局部变量副本是独立的;

JVM内存整体结构

在这里插入图片描述

在这里插入图片描述

栈内存结构

在这里插入图片描述

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

堆内存结构

在这里插入图片描述
堆:
1、堆内存是所有线程共用的内存空间,JVM 将Heap 内存分为年轻代(Young generation)和老年代(Old generation, 也叫 Tenured)两部分;
2、年轻代还划分为 3 个内存池,新生代(Edenspace)和存活区(Survivor space), 在大部分GC 算法中有 2 个存活区(S0, S1),在我们可以观察到的任何时刻,S0 和 S1 总有一个是空的,但一般较小,也不浪费多少空间;
3、Non-Heap 本质上还是 Heap,只是一般不归 GC管理,里面划分为 3 个内存池。
Metaspace, 以前叫持久代(永久代, Permanent generation), Java8 换了个名字叫 Metaspace.
4、CCS, Compressed Class Space, 存放 class 信息的,和 Metaspace 有交叉。
5、Code Cache, 存放 JIT 编译器编译后的本地机器代码

CPU与内存行为

CPU乱序执行(指令重排)
volatile关键字
原子性操作
内存屏障

总结

  • JMM:Java Memory Model and Thread Specification 规范明确定义了不同的线程之间,通过哪些方式,在什么时候可以看见其他线程保存到共享变量中的值;以及在必要时,如何对共享变量的访问进行同步。这样的好处是屏蔽各种硬件平台和操作系统之间的内存访问差异,实现了 Java 并发程序真
    正的跨平台;
  • 所有的对象(包括内部的实例成员变量),static 变量,以及数组,都必须存放到堆内存中;
  • 局部变量,方法的形参/入参,异常处理语句的入参不允许在线程之间共享,所以不受内存模型的影响;
  • 多个线程同时对一个变量访问时【读取/写入】,这时候只要有某个线程执行的是写操作,那么这种现象就称之为“冲突”;
  • 可以被其他线程影响或感知的操作,称为线程间的交互行为, 可分为: 读取、写入、同步操作、外部操作等等。 其中同步操作包括:对 volatile 变量的读写,对管程(monitor)的锁定与解锁,线程的起始操作与结尾操作,线程启动和结束等等。 外部操作则是指对线程执行环境之外的操作,比如停止其他线程等等;
  • JMM 规范的是线程间的交互操作,而不管线程内部对局部变量进行的操作;

JVM启动参数

启动参数分类
在这里插入图片描述

1.系统属性参数

-Dfile.encoding=UTF-8
-Duser.timezone=GMT+08
-Dmaven.test.skip=true
-Dio.netty.eventLoopThreads=8

2.运行模式参数

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

3.堆内存设置参数

堆外(XMS-Xmx)| 非堆+堆外

-Xmx, 指定最大堆内存。 如 -Xmx4g. 这只是限制了 Heap 部分的最大值为4g。这个内存不包括栈内存,也不包括堆外使用的内存。
-Xms, 指定堆内存空间的初始大小。 如 -Xms4g。 而且指定的内存大小,并不是操作系统实际分配的初始值,而是GC先规划好,用到才分配。 专用服务器上需要保持 –Xms 和 –Xmx 一致,否则应用刚启动可能就有好几个 FullGC。
当两者配置不一致时,堆内存扩容可能会导致性能抖动。
-Xmn, 等价于 -XX:NewSize,使用 G1 垃圾收集器 不应该 设置该选项,在其他的某些业务场景下可以设置。官方建议设置为 -Xmx1/2 ~ 1/4.
-XX:MaxPermSize=size, 这是 JDK1.7 之前使用的。Java8 默认允许的Meta空间无限大,此参数无效。
-XX:MaxMetaspaceSize=size, Java8 默认不限制 Meta 空间, 一般不允许设置该选项。
-XX:MaxDirectMemorySize=size,系统可以使用的最大堆外内存,这个参数跟 -sun.nio.MaxDirectMemorySize 效果相同。
-Xss, 设置每个线程栈的字节数。 例如 -Xss1m 指定线程栈为 1MB,与-XX:ThreadStackSize=1m 等价

4.GC设置参数

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

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"
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Wimb

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值