JVM核心技术(第一篇)
阅读原文时间:2021年05月15日阅读:1

目录

Java基础知识

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

一. 字节码技术

将写好的java文件编译成class

javac .\TestJvm.java

查看字节码

javap -c TestJVM

查看更详细的字节码

javap -c -verbose TestJVM

字节码的运行时结构

JVM是一个基于栈的计算机器。每个线程都有他所对应的线程栈,用于存储栈帧。每一次方法调用,都会创建一个栈帧。

栈帧由操作数栈,局部变量数组以及一个Class引用(也叫动态链接)组成。

  • 操作数栈:每个帧都包含了一个后入先出的栈,称为操作数栈。

  • 局部变量表:用于存放方法参数和内部定义的局部变量。局部变量表的容量以变量槽(Slot)为单位,一个Slot只能存放一个boolean、byte、char、shoert、int、float、reference或returnAddress类型的数据

  • Class引用:指向当前方法在运行时常量池中对应的class

二、JVM类加载器

类的生命周期

  1. 加载:找class文件
  2. 校验 :验证格式,依赖
  3. 准备 :静态字段,方法表
  4. 解析:符号解析为引用
  5. 初始化 :构造器,静态变量赋值,静态代码块
  6. 使用
  7. 卸载
类的加载时机
  1. 当虚拟机启动时,初始化用户指定的主类,就是启动执行的 main 方法所在的类;

  2. 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类,就是 new

    一个类的时候要初始化;

  3. 当遇到调用静态方法的指令时,初始化该静态方法所在的类;

  4. 当遇到访问静态字段的指令时,初始化该静态字段所在的类

  5. 子类的初始化会触发父类的初始化

  6. 如果一个接口定义了 default 方法,那么直接实现或者间接实现该接口的类的初始化,

    会触发该接口的初始化

  7. 使用反射 API 对某个类进行反射调用时,初始化这个类,其实跟前面一样,反射调用

    要么是已经有实例了,要么是静态方法,都需要初始化

  8. 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的

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

  1. 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化

  2. 定义对象数组,不会触发该类的初始化

  3. 常量在编译期间会存入调用类的常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量所在的类

  4. 通过类名获取 Class 对象,不会触发类的初始化,Hello.class 不会让 Hello 类初始化。

  5. 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,也不会触

    发类初始化,其实这个参数是告诉虚拟机,是否要对类进行初始化。Class.forName

    (“jvm.Hello”)默认会加载 Hello 类。

  6. 通过 ClassLoader 默认的 loadClass 方法,也不会触发初始化动作(加载了,但是

    不初始化)

三类类加载器

从上到下依次是:

  • 启动类加载器(BootstrapClassLoader)
  • 扩展类加载器(ExtClassLoader)
  • 应用类加载器(AppClassLoader)

类加载器特点:

双亲委派、负责依赖、缓存加载

加载过程:如一个Hello.class文件,不考虑自定义加载器,首先会在AppClassLoader中检查是否已经加载过,如果加载过就不加载了。如果没有加载过,就会拿到父加载器,那么父加载器(ExtClassLoader)就会检查是否加载过,如果没有,就再往上,让BootStrapClassLoader检查是否加载过。

如果还是没有,因为他的上面已经没有父加载器了,那么他就开始自己加载,如果能加载,他就自己加载。不能加载,就下沉到子加载器去加载,一直到最底层,如果没有类加载器能加载就抛出异常ClassNotFoundException

添加引用类的几种方式

  1. 放到JDK的lib/ext下,或者-Djava.ext.dirs

  2. java -cp/classpath 或者将class文件放在当前路径

  3. 自定义ClassLoader加载

  4. 拿到当前执行类的ClassLoader,反射调用addUrl方法添加jar或路径

    public class Test1 {

    public static void main(String[] args) throws MalformedURLException, NoSuchMethodException, ClassNotFoundException, InvocationTargetException, IllegalAccessException {
       String appurl="file:/d:/logs/";
        URLClassLoader classLoader = (URLClassLoader)Test1.class.getClassLoader();
        Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
        addURL.setAccessible(true);
        URL url=new URL(appurl);
        addURL.invoke(classLoader,url);
        Class.forName("Test2");
    }

    }

我将Test2.class文件放在d盘的logs文件夹下。

三、JVM内存结构

Jvm整体结构

可以看到,我们的JVM进程里面除了堆还有栈、非堆、JVM自身。而我们的操作系统里还有其他进程。

所以我们设置堆内存的时候,不能设置为机器的内存大小,如4G的机器千万不能把-Xms -Xmx 设置为4G,一般设置为机器内存的60%-70%。

JVM栈结构

栈:线程栈,也叫Java方法栈,每启动一个线程就会创建一个栈,如果使用了JNI方法,就会分配一个单独的本地方法栈。线程执行过程中,一般会有多个方法组成调用关系,如方法A调用方法B,每执行到一个方法,就会创建一个栈帧。

所有的原生对象类型(如int,long)和对象引用地址都在栈上存储。

JVM堆结构

堆:对象、对象成员以及类定义、静态变量都在堆上。

什么是JMM?

Java内存模型。明确定义了不同的线程之间,通过哪些方式,在什么时候可以看到其他线程保存在共享变量中的值;以及如何对共享变量进行同步。JMM规范的是线程间的交互操作。

从抽象上来看,JMM定义了线程和主内存之间的抽象关系。

四、JVM启动参数

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++

4.1 系统属性参数

-Dfile.encoding=UTF-8 -Duser.timezone=GMT+08

或者通过

System.setProperty("a","A100");设定,Linux上还可以通过a=A100 java XXX 设定。

4.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 等信息。

4.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 等价

4.4 GC相关

-XX:+UseG1GC:使用 G1 垃圾回收器

-XX:+UseConcMarkSweepGC:使用 CMS 垃圾回收器

-XX:+UseSerialGC:使用串行垃圾回收器

-XX:+UseParallelGC:使用并行垃圾回收器

// Java 11+

-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

// Java 12+

-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC

4.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,远程调试

4.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