java常见面试题
JVM和JRE和JDK
jvm是运行字节码的虚拟机,jvm对于不同的系统有不同的实现,所以对于相同的字节码文件在不同的系统上运行可以得到相同的结果,这就是java"一次编译,随处运行"的关键
jdk就是Java开发工具包,用于创建和编译java程序,包含了JRE运行环境,以及Javac和其它工具(javadoc文档生成器,jdb调试器,javap反编译器,jconsole监控工具)
jre就是运行已经编译Java程序的环境,主要包括JVM和java基础类库
什么是字节码,字节码的好处是什么
字节码就是jvm能够理解的代码(.class文件),不面向任何特定的处理,只面向虚拟机。好处就是在一定程度上解决了传统解释型语言执行效率低的问题,同时又保留了解释型语言可移植的特点
Java 程序从源代码到运行的过程如下图所示:
因为解释器这种执行方式较慢,有些代码和方法经常被调用(热点代码),所以就有了JIT(即时编译),当JIT编译之后会将字节码对应的机器码存下来,下次直接使用,机器码的运行效率高于java解释器,所以说java是编译与解释共存的语言
JDK、JRE、JVM、JIT 这四者的关系如下图所示
为什么说Java语言"编译与解释并存"
这是因为 Java 语言既具有编译型语言的特征,也具有解释型语言的特征。Java程序需要先编译(javac生成class文件),后由Java解释器来解释执行,并且在JVM中用了JIT即时编译,可将热点代码编译成机器码来进行运行
-
编译型:通过编器将源代码一次性转换成机器码的语言,编译后的文件可直接运行,二不需要解释器。一般情况下,编译语言的执行速度比较快,开发效率比较低。常见的编译性语言有 C、C++、Go、Rust 等等。
-
解释型:通过解释器一行一句直接解释并执行的语言。解释型语言开发效率比较快,执行速度比较慢。常见的解释性语言有 Python、JavaScript、PHP 等等。
AOT有什么优点?为什么不全部使用AOT?
jdk9有了一种全新的编译模式,它会在程序运行前就编译为机器码,AOT避免了JIT预热的开销,以及减少了内存占用和增强Java程序的安全性(不容易被反编译和修改),适合云原生场景
为什么不全部使用AOT,因为AOT编译无法支持Java的动态特性,反射,动态代理,动态加载。但是很多框架和库用到了这个特性,如果只使用AOT编译的话,就不能使用框架和库了。比如动态代理的CGLIB 就是使用的是ASM技术(在内存中生成和加载修改后的字节码文件),全部使用AOT的话就无法使用ASM技术了
Java和C++的区别
- Java不提供指针来访问内存,程序内存更安全
- Java是单继承,C++支持多继承
- Java支持内存管理垃圾回收机制(GC),不需要手动释放无用的内存
- c++支持方法上重载和操作符重载,而Java只支持方法重载
基本类型和包装类型的区别
- 存储方式:局部变量的基本类型存在栈中,成员变量存在堆中,包装类型存在堆中
- 占用空间:基本类型占用的空间较少
- 默认值:包装类型默认值是null
- 比较方式:基本类型==比较的是值,而包装类型是地址
- 初始化方式:一个是需要new
包装类型的缓存机制
Java基本数据类型的包装类型大部分用到了缓存机制来提升性能,两种浮点数类型的包装类 Float
,Double
并没有实现缓存机制
Byte
,Short
,Integer
,Long
这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character
创建了数值在 [0,127] 范围的缓存数据,Boolean
直接返回 True
or False
。
记住:所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
自动装箱和拆箱了解吗?原理是?
- 装箱:将基本类型用它们对应的引用类型包装起来;
- 拆箱:将包装类型转换为基本数据类型;
- 原理:我们发现装箱其实就是调用了 包装类的
valueOf()
方法,拆箱其实就是调用了xxxValue()
方法。 - 如果频繁拆装箱的话,也会严重影响系统的性能。我们应该尽量避免不必要的拆装箱操作。
为什么浮点数会出现精度丢失的风险
计算机是二进制的,而且计算机在表示一个数字时,宽度是有限的,无限循环的小数存储在计算机时,只能被截断,所以就会导致小数精度发生损失的情况
如何解决精度丢失的问题
BigDecimal可以实现对浮点数精确的运算不丢失,涉及到钱的场景几乎都是用它来做的
超过Long整形的数据应该怎么表示
BigDecimal来表示,因为它的内部使用的是int[]数组来存任意大小的数据,但是它的运算效率会低一些
成员变量和局部变量的区别
- **语法形式:**成员变量属于类,局部变量是方法中或代码块或是参数定义的,成员变量可以被public,private,static修饰,局部变量不能被访问修饰符和static修饰,两者都可以被final修饰
- **存储方式:**成员变量在堆中,局部变量在栈中
- **生存时间:**成员变量是对象的一部分,随着对象的创建而存在,局部变量随着变量方法的调用而生成,结束而消亡
- **默认值:**成员变量没有被赋初值就会以类型的默认值赋值(如果用final修饰需要显示赋值),局部变量不会自动赋值
为什么成员变量有默认值
1.如果没有默认值,在内存中这个内存地址存的是任意值,运行会出意外
2.成员变量可以通过反射赋值,局部 变量不行
3.局部变量没有赋值比较好判断,成员变量可能在运行时赋值,无法判断
静态变量有什么作用
被static修饰,多个实例共享一个变量,也就是说只会被分配一次内存,节约内存
使用类名来访问,前提是没有private修饰,通常情况下静态变量会被final修饰
字符型常量和字符串常量的区别?
- 形式:字符常量是单引号引起的一个字符,字符串是双引号引起的0个或多个字符
- 含义:字符常量相当于一个ascll值,字符串代表一个地址值
- 内存大小:字符常量占两个字节
静态方法为什么不能调用非静态成员
- 静态方法属于类,在类加载时就会分配内存,通过类名访问,非静态成员属于实例对象,只有在对象实例化后才存在,通过实例对象访问
- 在类的非静态成员不存在时,静态方法就存在了,此时调用不存在的非静态成员是非法操作
静态方法和实例方法有什么不同
- 调用方式:静态变量通过类名来调用,实例方法通过对象来调用
- 访问类成员的限制:静态方法访问本类的成员时,只能访问静态成员,实例方法没有限制
什么是可变长参数
从 Java5 开始,Java 支持定义可变长参数,所谓可变长参数就是允许在调用方法时传入不定长度的参数
法通过对象来调用
- 访问类成员的限制:静态方法访问本类的成员时,只能访问静态成员,实例方法没有限制
什么是可变长参数
从 Java5 开始,Java 支持定义可变长参数,所谓可变长参数就是允许在调用方法时传入不定长度的参数
可变参数只能作为函数的最后一个参数,但其前面可以有也可以没有任何其他参数,Java 的可变参数编译后实际会被转换成一个数组