什么是 Java 字节码?采用字节码的好处是什么?
Posted JavaGuide
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么是 Java 字节码?采用字节码的好处是什么?相关的知识,希望对你有一定的参考价值。
在 Java 中,JVM 可以理解的代码就叫做字节码(即扩展名为 .class
的文件),它不面向任何特定的处理器,只面向虚拟机。Java 语言通过字节码的方式,在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点。所以, Java 程序运行时相对来说还是高效的(不过,和 C++,Rust,Go 等语言还是有一定差距的),而且,由于字节码并不针对一种特定的机器,因此,Java 程序无须重新编译便可在多种不同操作系统的计算机上运行。
Java 程序从源代码到运行的过程如下图所示:
我们需要格外注意的是 .class->机器码
这一步。在这一步 JVM 类加载器首先加载字节码文件,然后通过解释器逐行解释执行,这种方式的执行速度会相对比较慢。而且,有些方法和代码块是经常需要被调用的(也就是所谓的热点代码),所以后面引进了 JIT(just-in-time compilation) 编译器,而 JIT 属于运行时编译。当 JIT 编译器完成第一次编译后,其会将字节码对应的机器码保存下来,下次可以直接使用。而我们知道,机器码的运行效率肯定是高于 Java 解释器的。这也解释了我们为什么经常会说 Java 是编译与解释共存的语言 。
HotSpot 采用了惰性评估(Lazy Evaluation)的做法,根据二八定律,消耗大部分系统资源的只有那一小部分的代码(热点代码),而这也就是 JIT 所需要编译的部分。JVM 会根据代码每次被执行的情况收集信息并相应地做出一些优化,因此执行的次数越多,它的速度就越快。JDK 9 引入了一种新的编译模式 AOT(Ahead of Time Compilation),它是直接将字节码编译成机器码,这样就避免了 JIT 预热等各方面的开销。JDK 支持分层编译和 AOT 协作使用。
为什么不全部使用 AOT 呢?
AOT 可以提前编译节省启动时间,那为什么不全部使用这种编译方式呢?
长话短说,这和 Java 语言的动态特性有千丝万缕的联系了。举个例子,CGLIB 动态代理使用的是 ASM 技术,而这种技术大致原理是运行时直接在内存中生成并加载修改后的字节码文件也就是 .class
文件,如果全部使用 AOT 提前编译,也就不能使用 ASM 技术了。为了支持类似的动态特性,所以选择使用 JIT 即时编译器。
相关阅读:Java基础常见面试题总结(上)
Java基础总结一(概述基础知识)
文章目录
- Java基础总结一(概述、基础知识)
Java基础总结一(概述、基础知识)
Java概述
什么是Java
Java是一门面向对象的编程语言。
JVM、JRE、JDK的关系
JVM:Java Virtual Machine是Java虚拟机,Java程序需要运行在虚拟机上,不同的平台有自己的虚拟机,因此Java语言可以实现跨平台。
JRE:Java Runtime Environment包括Java虚拟机和Java程序所需的核心类库等。核心类库主要是java.lang包:包含了运行Java程序必不可少的系统类,如基本数据类型、基本数学函数、字符串处理、线程、异常处理类等,系统缺省加载这个包。
JDK:Java Development Kit是提供给Java开发人员使用的,其中包含了Java的开发工具,也包括了JRE。所以安装了JDK,就无需再单独安装JRE了。其中的开发工具:编译工具(javac.exe),打包工具(jar.exe)等。
什么是跨平台性?原理是什么
指Java一次编写,到处运行。实现原理:Java程序通过Java虚拟机在系统平台上运行的,只要该系统可以安装相应的Java虚拟机,该系统就可以运行Java程序。
什么是字节码?采用字节码的最大好处是什么
字节码
Java源代码经过虚拟机编译器编译后产生的文件(即扩展为.class的文件),它不面向任何特定的处理器,只面向虚拟机。
采用字节码的好处
Java语言通过字节码的方式,在一定程度上解决了传统解释性语言执行效率低的问题,同时又保存了解释语言可移植的特点。所以Java程序运行时比较高效,而且,由于字节码并不是专门对一种特定的机器,因此,Java程序无需重新编译就可以在多种不同的计算机上运行。
Oracle JDK和Open JDK的对比
- Oracle JDK版本将每三年发布一次,而Open JDK版本每三个月发布一次.
- Open JDK 是一个参考模型并且是完全开源的,而Oracle JDK是Open JDK的一个实现,并不是完全开源的。
- Oracle JDK 比 Open JDK 更稳定。Open JDK和Oracle JDK的代码几乎相同,但Oracle JDK有更多的类和一些错误修复。因此,如果您想开发企业/商业软件,建议您选择Oracle JDK,因为它经过了彻底的测试和稳定。某些情况下,有些人提到在使用Open JDK 可能会遇到了许多应用程序崩溃的问题,但是,只需切换到Oracle JDK就可以解决问题。
Java基础知识
Java有哪些数据类型
基本数据类型
- 整型:byte(1字节)、short(2字节)、int(4字节)、long(8字节)
- 字符型:char(2字节)
- 浮点型:float(4字节)、double(8字节)
- 布尔型:boolean
long类型数字后要加后缀L,float要加后缀F。
引用数据类型
- 数组
- 接口
- 类
类型转换
自动类型转换:小到大
强制类型转换:大到小
表达式类型自动提升:byte 、short、char、转换为int型,表达式最终类型以表达式中类型等级最高为准。
运算符
- 算数运算符:+ - * / % ++ –
- 赋值运算符:= -= += *= /=
- 位运算符:& |^ >> << >>>
- 比较运算符:== != > < >= <=
- 逻辑运算符:&& || !
- 三目运算符:? :
Java中的取余的结果可以不是整型,如:5.2%3.1=2.1
流程控制
-
顺序
-
分支
if() else
switch(expr) case expr:
在 Java 5 以前,switch(expr)中,expr 只能是 byte、short、char、int。从 Java5 开始,Java 中引入了枚举类型,expr 也可以是 enum 类型,从 Java 7 开始,expr 还可以是字符串(String),但是长整型(long)在目前所有的版本中都是不可以的。
-
循环:while、do_while、for、for_each
-
循环控制:break、continue、return
数组
数组的定义
type[] arrayname
数组的初始化
- 静态初始化(直接给每个元素赋值)
- 动态初始化(全部赋值为0)
数组在内存中的存储
定义数组 int[] a;
数组引用变量a存储在栈内存
数组元素存储于堆内存中
数组查找:通过引用变量找到堆内存的元素
数组定义时为引用变量开辟空间(栈),初始化时为元素开辟空间(堆)
对数组的操作
Arrays类
多维数组
本质还是一维数组
面向对象
面向对象概述
面向对象和面向过程的区别
- 面向过程:优点是性能方面比面向对象高,因为类调用时需要大量实例化,开销较大。缺点是没有面向对象易维护、易复用、易扩展。
- 面向对象:由于面向对象有封装、继承、多态的特性,可以设计出低耦合的系统,使系统更加灵活。但是性能比面向过程低
面向对象的三大特征
-
封装:隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性。
-
继承:继承是使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承可以提高代码复用性。继承是多态的前提。
-
多态:父类或接口定义的引用变量可以指向子类或具体实现类的实例对象。提高了程序的拓展性。
实现多态的三大要素:继承、重写、向上转型。
什么是多态机制?
所谓多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。
多态分为编译时多态和运行时多态。其中编辑时多态是静态的,主要是指方法的重载,它是根据参数列表的不同来区分不同的函数,通过编辑之后会变成两个不同的函数,在运行时谈不上多态。而运行时多态是动态的,它是通过动态绑定来实现的,也就是我们所说的多态性。
类和对象
类和对象的区别
类是模板,对象是实体。如:人类是类,小明是实体。
定义类
构造器
成员变量
方法
对象的产生和使用
对象的产生:先定义,在使用new关键字实例化对象
对象的使用:访问对象的变量,调用对象的方法。
对象的存储
对象的引用变量存储在栈内存中
对象存储在堆内存中
通过引用变量找到堆内存中的对象
this和super
-
this:this是自身的一个对象,代表对象本身,可以理解为指向对象本身的一个指针。
this的用法在Java中大体可以分为3种:
- 普通的直接引用,this相当于是指向当前对象本身
- 形参与成员名字重名,用this来区分
- 引用本类的构造函数
-
super:super可以理解为指向自己父类对象的一个指针,而这个父类指的是离自己最近的一个父类。
super也有三种用法:
- 普通的直接引用
- 子类中的成员变量或方法与父类中的成员变量或方法同名时,用super进行区分
- 引用父类构造函数
-
this和super的区别:本质上来讲,this是一个指向本对象的指针,而super是Java的关键字。
方法
Java方法和C函数的区别
Java方法不可以单独定义,方法不能单独存在,方法要不然属于类,要不然属于对象,方法不可以单独执行,必须使用类或对象作为调用者
参数传递
值传递
方法重载
一个类中有两个及两个以上同名但参数类别不同的方法
确定方法的三要素
变量
变量分类
-
局部变量:方法内变量
-
成员变量:类中方法外
局部变量和成员变量的区别
-
类中位置不同
成员变量:在方法外。
局部变量:在方法内。
-
内存中的位置不同
成员变量:在堆内存中。
局部变量:在栈内存中。
-
生命周期
成员变量:随对象的创建而存在,随对象的消失而消失。
局部变量:随方法的调用而存在,随方法的调用完毕而消失。
-
初始化
成员变量有默认初始值。
局部变量无默认初始值。
隐藏和封装
访问控制级别
- private:只可以在当前类中被访问
- default:可以被同一个类和包被访问
- protected:可以被同一个类和包以及子类访问
- public:可以被所有类访问
包
关键字:package
作用:解决类命名和文件管理问题
包机制两方面保证:源文件使用package语句指定报名,class文件必须放在对应的路径下
import:导入指定包下全部的类或者单个类
构造器
格式
- 方法名和类名相同
- 没有返回值类型
作用
创建对象时进行初始化
默认初始化
0/false/null
构造器和对象的关系
创建对象的最后一步,Jvm会收集构造方法块中的代码和构造器中的代码生成< init >方法,并执行。此时外部程序才可以使用该对象。
构造器重载
类似方法重载
类的继承
关键字
extends
特点
- 子类可以访问父类允许访问的方法和变量。
- 一个子类只有一个直接父类,可以有多个间接父类。
重写方法
发生在父子类中,方法名、参数列表必须相同,返回值小于等于父类,抛出的异常小于等于父类,访问修饰符大于等于父类(里氏代换原则);如果父类方法访问修饰符为private则子类中就不是重写。
包装类
定义及作用
为8种基本数据类型分别定义相应的引用
自动装箱和自动拆箱
- 装箱:将基本数据类型用他们对应的引用类型包装起来。
- 拆箱:将包装类转化为基本数据类型。
包装类型
- Boolean、Character、Byte、Short、Integer、Long、Float、Double
-128~127
如果整型字面量的值是在-128~127之间,那么自动装箱时不会new新的Integer对象,而是直接引用常量池中的Integer对象。
抽象类
关键字
abstract
抽象类的特点
抽象类中包含抽象方法,但抽象类不能创建对象
抽象方法的特点
抽象方法没有具体实现,需要子类去重写
应用场景
模板设计
接口
接口的基本概念
- 关键字:interface
- 接口定义的要求:
- 接口修饰符为public或省略
- 接口名与类名采用相同的命名规则
- 接口无构造器和初始化块
接口的成员特点
- 成员变量:默认修饰符public final static
- 成员方法:全部抽象,默认修饰符 public abstract
接口的实现
- 关键字:implements
- 特点:可以多实现
- 要求:子类必须实现接口中的所有抽象类
抽象类和接口的对比
抽象类是用来集成子类通用特性,接口时一组抽象方法的集合。
从设计层面来说,抽象类是对类的抽象,是一种模板设计,接口是对行为的抽象,是一种行为规范。
相同点
- 都不能被实例化
- 都位于继承的顶端,用于被其他类继承或者实现
- 都包含抽象方法,子类或实现类必须重写覆盖这些抽象方法
不同点
参数 | 抽象类 | 接口 |
---|---|---|
声明 | 抽象类使用abstract关键字声明 | 接口使用interface关键字声明 |
实现 | 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现 | 使用implements关键字来实现接口。它需要提供接口中所有声明的方法的实现 |
构造器 | 抽象类可以有构造器 | 接口不能有构造器 |
访问修饰符 | 抽象类中的方法可以是任意访问修饰符 | 接口方法默认修饰符是public。并且不允许定义为 private 或者 protected |
多继承 | 一个类最多只能继承一个抽象类 | 一个类可以实现多个接口 |
字段声明 | 抽象类的字段声明可以是任意的 | 接口的字段默认都是 static 和 final 的 |
内部类
什么是内部类
将一个类的定义放在另一个类的定义内部,这就是内部类。内部类本身就是类的一个属性,与其他属性定义方式一致。
内部类的分类
成员内部类、局部内部类、匿名内部类、静态内部类
静态内部类
定义在类内部的静态类,就是静态内部类
public class Outer
static class StaticInner
静态内部类可以访问外部类所有的静态变量,而不可以访问外部类的非静态变量,静态内部类创建方式:
Outer.StaticInner inner = new Outer.StaticInner();
成员内部类
定义在类外部,成员位置上的非静态类,就是成员内部类。
public class Outer
class Inner
成员内部类可以访问外部类所有的变量和方法,包括静态和非静态,私有和公有。成员内部类依赖于外部类的实例,它的创建方式如下:
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
或者连起来:
Outer.Inner inner = new Outer().new Inner();
局部内部类
public class Outer
void outMethod()
final int a =10;
class Inner
void innerMethod()
System.out.println(a);
Inner inner = new Inner();
inner.innerMethod();
定义在实例方法中的局部类可以访问外部类的所有变量和方法,定义静态方法中的局部类只能访问外部类的静态变量和方法。
匿名内部类
匿名内部类是没有名字的内部类,日常开发中使用的比较多。
public class Outer
private void test(final int i)
new Service()
public void method()
for (int j = 0; j < i; j++)
System.out.println("匿名内部类" );
.method();
//匿名内部类必须继承或实现一个已有的接口
interface Service
void method();
除了没有名字,匿名内部类还有以下特点:
- 匿名内部类必须继承一个类或者实现一个接口。
- 匿名内部类不能定义任何静态成员和静态方法。
- 当所在的方法的形参需要被匿名内部类使用时,必须声明为 final。
- 匿名内部类不能是抽象的,它必须要实现继承的类或者实现的接口的所有抽象方法。
内部类的应用场景
- 适当使用内部类,使得代码更加灵活和富有扩展性。
- 当某个类除了它的外部类,不再被其他的类使用时。
局部内部类和匿名内部类访问局部变量的时候,为什么变量必须要加上final?
是因为生命周期不一致, 局部变量直接存储在栈中,当方法执行结束后,非final的局部变量就被销毁。而局部内部类对局部变量的引用依然存在,如果局部内部类要调用局部变量时,就会出错。加了final,可以确保局部内部类使用的变量与外层的局部变量区分开,解决了这个问题。
Java8中以做优化,即使不加final也不会报错
以上是关于什么是 Java 字节码?采用字节码的好处是什么?的主要内容,如果未能解决你的问题,请参考以下文章