首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

深入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个左右的操作码,还有一些操作码则保留给调试操作。

根据指令的性质,主要分为四个大类:

  1. 栈操作指令,包括与局部变量交互的指令
  2. Java虚拟机(JVM)是一个基于栈的计算机。所有的计算都发生在栈上。
  3. 程序流程控制指令
  4. 程序的流程控制指令,比如,for,if,函数调用
  5. 对象操作指令,包括方法调用指令
  6. java是一个面向对象的语言,创建一个对象,调用对象的方法。
  7. 算数运算以及类型转换指令。

1.2 查看字节码

  1. 代码
  1. 查看字节码
  • javac xxx.java 生成class文件
  • javap -c xxx.class 查看字节码

实际上,class文件里面保存的都是字节码,0到255的数字,上图展示的是助记符,方便记忆和阅读用的。aload_0,return等都有自己对应的操作码的。

  • javap -c -verbose xxx.class 查看更信息的字节码信息

上图中,有jdk的版本号,类的属性(public),还有常量表。代码行号表(调试的时候可以看到指令对于的行号)。

  1. 执行流程

执行的时候,从常量表中获取到常量值,在放到程序栈(变量表)中尽心计算。

2. 类加载

2.1 类的生命周期

  1. 加载(Loading):找class文件,并读入程序内存中
  2. 通过类名com.xxx.Class从各种classpath目录里面找到对应类,也可以自定义类加载器,从网络上加载类或jar包。
  3. 验证(Verification): 验证格式,依赖
  4. 验证格式是否正确。版本号。
  5. 类之间的相互引用关系。
  6. 准备(Preparation): 静态字段,方法表
  7. 抽取类里面的静态字段
  8. 抽取类里面的方法。
  9. 搭建类的结构(骨架)
  10. 解析(Resolution): 符号解析为引用
  11. 把各种符号替换成引用。
  12. 初始化(Initialization): 构造器,静态变量赋值,静态代码
  13. 静态变量赋值
  14. 静态代码执行
  15. 然后这个类就可以创建实例了。可以被使用了。
  16. 使用(Using): 创建类实例,并使用
  17. 卸载(Unloading): 清除类的信息

2.2 什么时候会加载类,会初始化类

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

2.3 什么时候会加载类,不会初始化类

  1. 通过子类引用父类的静态字段,只会触发父类的初始化,而不会触发子类的初始化。但子类肯定是被加载了的。
  2. 定义对象数组,不会触发类的初始化。数组只是一个声明,实际上还没有创建对象呢。
  3. 常量在编译期间会存入调用类的常量池中,本质上并没有直接一用定义常量的类,不会触发定义常量所在的类。比如:java字符串字面量"xxxx"其实就是一个String常量了。但是并不会触发String类的初始化。
  4. 通过类名获取Class对象,不会触发类的初始化。Hello.Class不会让Hello类初始化。
  5. 通过Class.forName加载指定类时,如果指定参数initialize为false时,不会触发类初始化,其实这个参数是告诉虚拟机,是否需要对类进行初始化。Class.forName("jvm.Hello")默认会初始化Hello类。
  6. 通过ClassLoader默认的loadClass方法,不会触发初始化动作(类加载了,但是不初始化)

所谓加载类,不初始化类指的就是,会从class文件里面加载类的字节码,但是并不会执行静态字段的赋值,静态代码块的执行。

2.4 类加载器

  1. 启动类加载器(BootstrapClassLoader)
  2. 加载JVM启动的核心系统类
  3. 扩展类加载器(ExtClassLoader)
  4. 扩展的类,也是在jdk里面自带的。
  5. 应用类加载器(AppClassLoader)
  6. 加载程序员写的代码,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

死锁都能检测出来。

  • 发表于:
  • 本文为 InfoQ 中文站特供稿件
  • 首发地址https://www.infoq.cn/article/7ab84eaa04d7f06e3f72900ba
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券