深入 java week1-01 字节码、内存、GC、调试工具
1. java 字节码技术
1.1 什么是字节码?
字节码(Java bytecode),是由 Java 编译器把 Java 代码转换后,可以由 java 虚拟机无脑执行的指令集。也是 java 跨平台的核心所在。Java 维护者(组织)为所有主流操作系统提供了一个 Java 虚拟机,这些虚拟机向上可以识别 java 字节码,向下则适配本地环境,执行字节码里面的指令,在转换成 cpu 执行指令。
它是程序的一种低级表示,可以运行于 Java 虚拟机上。将程序抽象成字节码可以保证 Java 程序在各种设备上的运行。计算机里面的很多事情问题都可以通过增加一个中间层来解决,很显然,字节码+JVM 就是这么一个中间层,解决跨平台的问题。
Java bytecode 由单字节(byte)的指令组成,理论上最左支持 256 个操作码(opcode)。实际上 Java 只是用了 200 个左右的操作码,还有一些操作码则保留给调试操作。
根据指令的性质,主要分为四个大类:
- 栈操作指令,包括与局部变量交互的指令 
- Java 虚拟机(JVM)是一个基于栈的计算机。所有的计算都发生在栈上。 
- 程序流程控制指令 
- 程序的流程控制指令,比如,for,if,函数调用 
- 对象操作指令,包括方法调用指令 
- java 是一个面向对象的语言,创建一个对象,调用对象的方法。 
- 算数运算以及类型转换指令。 
1.2 查看字节码
- 代码 
 
 - 查看字节码 
- javac xxx.java 生成 class 文件 
- javap -c xxx.class 查看字节码 
 
 实际上,class 文件里面保存的都是字节码,0 到 255 的数字,上图展示的是助记符,方便记忆和阅读用的。aload_0,return 等都有自己对应的操作码的。
- javap -c -verbose xxx.class 查看更信息的字节码信息 
 
 上图中,有 jdk 的版本号,类的属性(public),还有常量表。代码行号表(调试的时候可以看到指令对于的行号)。
- 执行流程 
 
  
 执行的时候,从常量表中获取到常量值,在放到程序栈(变量表)中尽心计算。
2. 类加载
2.1 类的生命周期
- 加载(Loading):找 class 文件,并读入程序内存中 
- 通过类名 com.xxx.Class 从各种 classpath 目录里面找到对应类,也可以自定义类加载器,从网络上加载类或 jar 包。 
- 验证(Verification): 验证格式,依赖 
- 验证格式是否正确。版本号。 
- 类之间的相互引用关系。 
- 准备(Preparation): 静态字段,方法表 
- 抽取类里面的静态字段 
- 抽取类里面的方法。 
- 搭建类的结构(骨架) 
- 解析(Resolution): 符号解析为引用 
- 把各种符号替换成引用。 
- 初始化(Initialization): 构造器,静态变量赋值,静态代码 
- 静态变量赋值 
- 静态代码执行 
- 然后这个类就可以创建实例了。可以被使用了。 
- 使用(Using): 创建类实例,并使用 
- 卸载(Unloading): 清除类的信息 
2.2 什么时候会加载类,会初始化类
- 当虚拟机启动时,初始化用户指定的主类。(main 方法所在的类) 
- 当遇到用以新建目标类实例的 new 指令时,初始化 new 指令的目标类,就是 new 一个类的时候要初始化。(创建类的实例,那肯定需要类被加载了。) 
- 当遇到调用静态方法的指令时,初始化该静态方法所在的类。 
- 当遇到访问静态字段的指令时,初始化该静态字段所在的类。 
- 子类的初始化会触发父类的初始化。 
- 如果一个接口定义了 default 方法,那么直接实现或间接实现该接口的类的初始化,会触发该接口的初始化。 
- 使用反射 API 对某个类进行反射调用时,初始化这个类,反射调用要么是已经有实例了,要么是静态方法,都需要初始化。 
- 当初次调用 MethodHandle 实例时,初始化该 MethodHandle 指向的方法所在的类。 
2.3 什么时候会加载类,不会初始化类
- 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。但子类肯定是被加载了的。 
- 定义对象数组,不会触发类的初始化。数组只是一个声明,实际上还没有创建对象呢。 
- 常量在编译期间会存入调用类的常量池中,本质上并没有直接一用定义常量的类,不会触发定义常量所在的类。比如:java 字符串字面量"xxxx"其实就是一个 String 常量了。但是并不会触发 String 类的初始化。 
- 通过类名获取 Class 对象,不会触发类的初始化。Hello.Class 不会让 Hello 类初始化。 
- 通过 Class.forName 加载指定类时,如果指定参数 initialize 为 false 时,不会触发类初始化,其实这个参数是告诉虚拟机,是否需要对类进行初始化。Class.forName("jvm.Hello")默认会初始化 Hello 类。 
- 通过 ClassLoader 默认的 loadClass 方法,不会触发初始化动作(类加载了,但是不初始化) 
所谓加载类,不初始化类指的就是,会从 class 文件里面加载类的字节码,但是并不会执行静态字段的赋值,静态代码块的执行。
2.4 类加载器
- 启动类加载器(BootstrapClassLoader) 
- 加载 JVM 启动的核心系统类 
- 扩展类加载器(ExtClassLoader) 
- 扩展的类,也是在 jdk 里面自带的。 
- 应用类加载器(AppClassLoader) 
- 加载程序员写的代码,jar 包。 
- 怎么保证类不会重复加载? 
- 双亲委托:应用类加载器在加载类的时候,会先去扩展类加载器里面找类是否已被加载,如果没有就去启动类加载器找,如果还没有,则自己加载类。 
- 负责依赖:加载一个类的时候,还需要把这个类依赖的其它类也给加载进来。 
- 缓存加载:类被加载完后,会缓存起来。 
- 自定义类加载器 
- 自定义类加载器加载出来的类是不一样的,哪怕都是从同一个 class 文件加载进来的类,但是实例是不能相互类型转换的。因为没有共同的上一级加载器。java 中类其实也是一个对象,不同的加载器加载的类,就相当于 2 个类对象,虽然对象的内容是一样的,但是地址什么的就不一样了(比喻)。基于这个特性,可以加载不同版本的类,解决类兼容的问题,比如引入了一个外部工具,这个工具依赖了 xxx.class 1.0.0。但是现有的代码也依赖的却是 xxx.class 1.1.0 这样就势必要加载 2 个版本的 xxx.class 了。那么自定义类加载器就派上用场了。 
 
 2.5 添加引用类的几种方式(就是发现类)
- 把类/jar 放到 JDK 的 lib/ext 下,或者-Djava.ext.dirs 指定类查找路径 
- java-cp/classpath 或者 class 文件放到当前路径 
- 自定义 ClassLoader 加载。这个就比较灵活了,可以从网络上下载一个类。 
- 拿到当前执行类的 ClassLoader,反射调用 addUrl 方法添加 jar 或者路径。说白了,还是添加路径其他的类加载器才能找到类、jar 包。(JDK9 就不能用这种方法了,提供了新方法。) 
3. JVM 内存模型
4. JDK 内置的命令行工具
4.1 工具功能展示
4.1.1 jps/jinfo
- 查看当前系统启动的 java 进程。就跟 linux 的 ps 命令一样,只是 jps 只显示 java 进程 
- jps -mlv :查看更详细的信息。jvm 的启动参数,垃圾回收算法等。 
4.1.2 jstat
- jstat -gc 98800 1000 20 查看进程 98800 的内存,和 gc 情况,1000 表示每秒刷新一次,显示 20 次。s0c 存活区 0 的容量,s0u 表示存活区 0 使用的内存数。EC 表示伊甸区的容量。OC 老年区的容量。MC 表示元数据区的容量。YGC 表示 youngGC 次数。YGCT 表示 youngGC 的总时间。FGC 全量垃圾回收的册数,FGCT 表示全量 GC 的总时间。单位都是字节。 
 
 - jstat -gcutil 98800 1000 20: 查看各个区域内存的使用率。百分比 
4.1.3 jmap 查看更详细的 jvm 信息,jvm 里面的所有对象,以及对象个数,使用的字节数。如果某个对象特别多。可能就是内存泄漏了。
- jmap -histo pid 
 
 - jmap -heap 14068 查看堆内存信息 
 
 4.1.4 jstack
- jstack pid:查看 jvm 所有线程的栈。 
4.1.5 jcmd
- jcmd 是一个比较综合的命令行工具。和上面的那些都是职责单一的。 
- jcmd 14068 help :查看指定进程,支持哪些工具。 
 
 - jcmd pid Thread.print: 查看 jvm 的所有线程栈。 
4.1.6 jrunscript/jjs
- 执行 js 脚本命令。 
5. JDK 内置图形化工具
说明:功能其实和命令行工具差不多,只是有窗口界面,比较方便。
5.1 jconsole
图形化显示 JVM 内存,线程,cpu 的使用情况。
 
 5.2 jvisualvm
抽样统计功能,比 jconsole 更好用一点。更强大一点。可以查看一段时间(单位时间)系统的状态。
 
 5.3 jmc
死锁都能检测出来。












 
    
评论