前言
最近无聊的在逛某 tube 网站,本来想看看大家是怎么吐槽川普的,结果无意间点进了一个老外面试 Java 的视频,对于常年面试被吊打的我瑟瑟发抖,于是决定进去一探究竟。
毕竟不是专业的后台开发,所以我在面试到后台知识的时候果断的退了出来,才让自己免受了侮辱。
不过鉴于我手速出众,飞速的记录下了 Java 的基础题,所以准备贡献出来,供大家享乐。
鉴于题目比较多,会分成上下 2 篇 来整理,主要是面对 Java 的基础,看看老外的面试题和我们有什么区别。
当然问题是老外问的,答案是我编的。
正文
Java 中有哪些可用的分配内存
Java 虚拟机在执行程序时候会将内存划分为不同的数据区域
-
方法区 - Method Area
-
方法区(Method Area)与 Java 堆一样,是所有线程共享的内存区域。
-
虽然 Java 虚拟机规范把方法区描述为堆的一个逻辑部分,但是它却有一个别名叫 Non-Heap(非堆),目的应该是与 Java 堆区分开。
-
运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版本/字段/方法/接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将类在加载后进入方法区的运行时常量池中存放。运行期间也可能将新的常量放入池中,这种特性被开发人员利用得比较多的是 String.intern() 方法。受方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。
-
方法区的大小和堆空间一样,可以选择固定大小也可选择可扩展,方法区的大小决定了系统可以放多少个类,如果系统类太多,会导致方法区溢出,虚拟机同样会抛出内存溢出错误
-
JVM 关闭后方法区即被释放
-
-
堆 - Heap Area
-
对于大多数应用,Java 堆是 Java 虚拟机管理的内存中最大的一块,被所有线程共享。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例以及数据都在这里分配内存。
-
Java 虚拟机规范规定,Java 堆可以是处于物理上不连续的内存空间中,只要逻辑上是连续的即可,像磁盘空间一样。实现时,既可以是固定大小,也可以是可扩展的,主流虚拟机都是可扩展的(通过
-Xmx
和-Xms
控制),如果堆中没有完成实例分配,并且堆无法再扩展时,就会抛出 OutOfMemoryError 异常。
-
-
栈 - Stack Area
-
栈是一种快速有效的分配存储方式,访问速度仅次于程序计数器
-
JVM 直接对虚拟机栈的操作只有两个:每个方法执行,伴随着入栈(进栈/压栈),方法执行结束出栈
-
栈不存在垃圾回收问题
-
-
程序计数器 - Program Counter Register
-
它是一块很小的内存空间,几乎可以忽略不计。也是运行速度最快的存储区域
-
在 JVM 规范中,每个线程都有它自己的程序计数器,是线程私有的,生命周期与线程的生命周期一致
-
任何时间一个线程都只有一个方法在执行,也就是所谓的当前方法。如果当前线程正在执行的是 Java 方法,程序计数器记录的是 JVM 字节码指令地址,如果是执行 native 方法,则是未指定值(undefined)
-
它是程序控制流的指示器,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成
-
字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令
-
它是唯一一个在 JVM 规范中没有规定任何 OutOfMemoryError 情况的区域
-
-
本地方法栈 - Native Method Stack Area
-
Java 虚拟机栈用于管理 Java 方法的调用,而本地方法栈用于管理本地方法的调用
-
本地方法栈也是线程私有的
-
允许线程固定或者可动态扩展的内存大小
-
如果线程请求分配的栈容量超过本地方法栈允许的最大容量,Java 虚拟机将会抛出一个 StackOverflowError 异常
-
如果本地方法栈可以动态扩展,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的本地方法栈,那么 Java虚拟机将会抛出一个 OutofMemoryError 异常
-
-
它的具体做法是 Mative Method Stack 中登记 native 方法,在 E_xecution Engine_ 执行时加载本地方法库当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。它和虚拟机拥有同样的权限。
-
并不是所有 JVM 都支持本地方法。因为 Java 虚拟机规范并没有明确要求本地方法栈的使用语言、具体实现方式、数据结构等。如果 JVM 产品不打算支持 native 方法,也可以无需实现本地方法栈
-
在 Hotspot JVM 中,直接将本地方法栈和虚拟机栈合二为一
-
如果我写 static public void main,程序还会运行吗?
刚看到这个题目的时候懵逼了,在国内面试真没见过这么问的,所以还是有些不确定,所以赶紧搜索。
在 Java 中的修饰符出现在字段声明中时的顺序与 oracle 规定的 FieldModifier 顺序一致,这是习惯顺序,形成规范。不遵守这个约定没有技术影响,但是会降低代码的可读性,因为大多数开发人员都习惯于标准顺序。
所以程序变成 static public void main() 也可以运行。
下面仅列举方法修饰符:
局部变量中的默认值是什么?
在 Java 中的局部变量不会初始化。
public class staticFactory { public static void main(String[] args) { int temp; System.out.println(temp); }}// 打印信息:Error:(10, 28) java: 可能尚未初始化变量 temp
如果是静态变量不赋值,会直接默认值为 0
public class staticFactory { static int temp; public static void main(String[] args) { System.out.println(temp); }}// 打印值为 0
如果是成员变量
public class staticFactory { public static void main(String[] args) { LocalVariables obj = new LocalVariables(); System.out.println("a="+obj.a); System.out.println("b="+obj.b); System.out.println("c="+obj.c); System.out.println("d="+obj.d); }}class LocalVariables { int a; char b; float c; String d;}/* 打印结果: a=0 int 默认值为 0 b="" char 默认值为 "" c=0.0 float 默认值为 0.0 d=null String 默认值为 null*/
在 Java 中如何拷贝构造函数
Java 中的拷贝构造方法是一种使用该类的一个对象构造另外一个对象的构造方法。
如何创造拷贝构造方法
// 要创建拷贝构造方法,首先需要声明带有和本类相同类型的参数构造函数:public class Employee { private int id; private String name; public Employee(Employee employee) { }}
// 然后,将参数对象的每个属性都复制给新的实例。public class Employee { private int id; private String name; public Employee(Employee employee) { this.id = employee.id; this.name = employee.name; }}
上面的做法属于浅拷贝。
上面定义的属性是基本类型和不可变类型 (int 和 String),因此使用前拷贝就没问题。
但是如果类中包含可变类型就要通过该构造函数实现深拷贝。
为了实现深拷贝,我们需要根据原始可变对象类型构造新的实例。
public class Employee { private int id; private String name; private Date startDate; public Employee(Employee employee) { this.id = employee.id; this.name = employee.name; this.startDate = new Date(employee.startDate.getTime()); }}
什么是标记接口
标记接口是没有任何方法和属性的接口,它仅仅表明它的类属于一个特定类型,供其他代码测试允许做一些事情。并且标记接口不是 Java 这门语言特有的,而是计算机科学中的一种通用化的设计理念。
使用标记接口的唯一目的是使得可以用 instanceof 进行类型查询,例如:
if (obj instanceof Cloneable) {………}
在 Java 中,有一个常用的标记接口类
-
java.io.Serializable:未实现此接口的类将无法使其任何状态序列化或反序列化。为保证 serialVersionUID 值跨不同 java 编译器实现的一致性,序列化类必须声明一个明确的 serialVersionUID 值。
-
java.lang.Cloneable:表明 Object.clone() 方法可以合法地对该类实例进行按字段复制。实现此接口的类应该使用公共方法重写 Object.clone(它是受保护的)。如果在没有实现 Cloneable 接口的实例上调用 Object 的 clone 方法,则会导致抛出 CloneNotSupportedException 异常。
-
java.util.RandomAccess:用来表明其支持快速(通常是固定时间)随机访问。此接口的主要目的是允许一般的算法更改其行为,从而在将其应用到随机或连续访问列表时能提供良好的性能。
-
java.rmi.Remote:Remote 接口用于标识其方法可以从非本地虚拟机上调用的接口。任何远程对象都必须直接或间接实现此接口。只有在“远程接口”(扩展 java.rmi.Remote 的接口)中指定的这些方法才可远程使用。
为什么 Java 不完全面向对象
首先什么才是纯面向对象语言?
纯面向对象语言或完全面向对象语言是指完全面向对象的语言,它支持或具有将程序内的所有内容视为对象的功能。它不支持原始数据类型(如 int,char,float,bool 等)。编程语言满足七种标准可以就可以称为纯粹的面向对象语言,他们是:
-
封装 / 数据隐藏
-
继承
-
多态性
-
抽象化
-
所有预定义类型都是对象
-
所有用户定义的类型都是对象
-
对对象执行的所有操作必须仅通过对象公开的方法
为什么 Java 不是纯面向对象语言?
Java 支持属性 1、2、3、4 和 6 但不支持上面给出的 5 和 7。 Java 语言不是纯面向对象语言,因为它包含以下属性:
原始数据类型例如对象:
Smalltalk 是一种 “纯粹的” 面向对象的编程语言,与 Java 和 C++ 不同,因为作为对象的值和作为基本类型的值之间没有区别。在 Smalltalk 中,原始值,例如整数,布尔值和字符也是对象。在 Java 中,我们将预定义类型作为非对象 (基本类型)。
int a = 5; System.out.print(a);
static 关键字:
当我们将一个类声明为 static 时,可以在不使用 Java 中的对象的情况下使用它。
以上两点可以解释为什么 Java 不是完全面向对象。
你能在 Java 程序中实现指针吗
Java 虚拟机隐式地负责内存管理。Java 的主要格言是保持编程简单。因此,不建议直接通过指针访问内存。所以,指针在 Java 中被消除了,Java 中并没有指针。
解释一下 Java String 池
在 Java 的堆内存空间中,有一块区域用来存放 Java 字符串池。当需要创建一个新的字符串的时候,JVM 首先检查对象是否存在于字符串池中,如果存在,这个引用的对象将指向该变量,如果不存在则创建一个新对象。
由于String实例对象不可修改,因此,“多个 String 实例对象引用变量引用同一个 String 实例对象”在节省 Java 堆内存的情况下,并不会带来任何问题。
直接赋值的方式
使用 new 的方式
-
此时,如果判断 S1 == S2 是否相等是返回 true 的,因为他们指向同一个值。
-
如果判断 S3 == S4 是否相等返回 false,因为他们比较的是 S3 和 S4 的地址。
当 main() 方法没有生命成静态会发生什么?
main() 方法是 Java 程序的入口,也可以理解为一个接口,在 java 编程中,JVM 会查找类中的 public static void main ( String [] args ),如果找不到该方法就抛出错误 NoSuchMethodError:main 程序终止。main() 方法必须严格遵循它的语法规则,方法签名必须是 public static void,参数是字符串数组类型。
当然还有其他一些意义:
-
正因为 main() 方法是静态的,JVM 调用这个方法就不需要创建任何包含这个 main() 方法的实例。
-
因为 C 和 C++ 同样有类似的 main() 方法作为程序执行的入口。
-
如果 main() 方法不声明为静态的,JVM 就必须创建 main() 类的实例,因为构造器可以被重载,JVM 就没法确定调用哪个 main() 方法。
-
静态方法和静态数据加载到内存就可以直接调用而不需要像实例方法一样创建实例后才能调用,如果 main() 方法是静态的,那么它就会被加载到 JVM 上下文中成为可执行的方法。
总结
以上可以看的出来,老外的面试的偏向性和我们还是有些区别的,这些题有一半在我们的面试中是不常问到的,所以取长补短。
共同学习,写下你的评论
评论加载中...
作者其他优质文章