A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© hongyan0317 初级黑马   /  2019-5-31 11:34  /  434 人查看  /  0 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

[第1题] 流程必知必会

  1、简历要用心准备好,个人信息,特别是联系方式一定要清晰明确,自身掌握的技能要完成清晰,项目经历最好按照时间顺序,说明本人在项目中的职责,完成的工作,有什么样的提升或收获;

  2、一般面试流程是电面=》HR现场面=》技术面=》结果,并不是每一个面试结果就能立马有结果,所以当面试官说回去等消息的时候,并不代表没有机会,有时候需要讨论筛选才能最终确定人选。

  3、关于自我介绍,最好简明扼要,能体现自身的特点,表达流畅、自信,提前最好准备;

  4、准备好扎实的基础知识,以及对经历过的项目要有足够的认识,每一个项目都是一次学习、提升的机会,一般JAVA集合类是考察的重点;

  5、一般好一点的面试官会顺着知识点逐渐深入或者逐渐扩展,所以对于知识点的掌握最好全面深入,不要走马观花式的学习;

6、当遇到一些设计类的问题时,一般面试官考察的是你的思路,对问题的应变能力,对于事物观察的点;

[第2题] JDK源码

要想拿高工资,JDK源码不可不读。总结一下比较重要的源码:

1、List、Map、Set实现类的源代码

2、ReentrantLock、AQS的源代码

3 AtomicInteger的实现原理,主要能说清楚CAS机制并且AtomicInteger是如何利用CAS机制实现的

4 线程池的实现原理

5 Object类中的方法以及每个方法的作用

这些其实要求蛮高的,学习源码,短时间来说,是一件费事费力的事情,从长远角度来看----这是非常值得的,不仅仅是为了应付面试。

[第二部分] 类和对象

[第二篇] 面向对象基础

[第1题] 面向对象是什么?

面向对象是一种思想,世间万物都可以看做一个对象,这里只讨论面向对象编程(OOP),Java是一个支持并发、基于类和面向对象的计算机编程语言,面向对象软件开发的优点:

代码开发模块化,更易维护和修改;

代码复用性强;

增强代码的可靠性和灵活性;

增加代码的可读性。

面向对象的四大基本特性:

抽象:提取现实世界中某事物的关键特性,为该事物构建模型的过程。对同一事物在不同的需求下,需要提取的特性可能不一样。得到的抽象模型中一般包含:属性(数据)和操作(行为)。这个抽象模型我们称之为类。对类进行实例化得到对象。

封装:封装可以使类具有独立性和隔离性;保证类的高内聚。只暴露给类外部或者子类必须的属性和操作。类封装的实现依赖类的修饰符(public、protected和private等)

继承:对现有类的一种复用机制。一个类如果继承现有的类,则这个类将拥有被继承类的所有非私有特性(属性和操作)。这里指的继承包含:类的继承和接口的实现。

多态:多态是在继承的基础上实现的。多态的三个要素:继承、重写和父类引用指向子类对象。父类引用指向不同的子类对象时,调用相同的方法,呈现出不同的行为;就是类多态特性。多态可以分成编译时多态和运行时多态。

抽象、封装、继承和多态是面向对象的基础。在面向对象四大基础特性之上,我们在做面向对象编程设计时还需要遵循有一些基本的设计原则。

面向对象的七大设计原则:

SOLID原则(单一职责原则、开放关闭原则、里氏替换原则、接口隔离原则和依赖倒置原则)

迪米特法则

组合优于继承原则(合成复用原则)。

在遵循这些面向对象设计原则基础上,前辈们总结出一些解决不同问题场景的设计模式,以四人帮的gof23最为知名。

24种设计模式(gof23+1):

创建型模式:

1.简单工厂模式(不包含在gof23中)

2.工厂模式

3.抽象工厂模式

4.单例模式

5.原型模式

创建者模式

6.结构型模式:

7.组合模式

8.装饰者模式

9.外观模式

10.适配器模式

11.代理模式

12.享元模式

13.桥接模式

行为型模式:

14.观察者模式

15.策略模式

16.状态模式

17.中介模式

18.模板方法

19.命令模式

20.备忘录模式

21.访问者模式

22.解释器模式

23.迭代器模式

24.职责链模式

这里只是简单描述了定义和特征以及设计模式的关系,具体细节不讨论。

[第2题] 类加载的过程

参考:

https://blog.csdn.net/javazejian/article/details/73413292#启动bootstrap类加载器

a) 遇到一个新的类时,首先会到方法区去找class文件,如果没有找到就会去硬盘中找class文件,找到后会返回,将class文件加载到方法区中,在类加载的时候,静态成员变量会被分配到方法区的静态区域,非静态成员变量分配到非静态区域,然后开始给静态成员变量初始化,赋默认值,赋完默认值后,会根据静态成员变量书写的位置赋显示值,然后执行静态代码。当所有的静态代码执行完,类加载才算完成。

加载:类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象

验证:目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。

准备:为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。

解析:主要将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,接口方法解析(这里涉及到字节码变量的引用,如需更详细了解,可参考《深入Java虚拟机》)。

初始化:类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。

这便是类加载的5个过程,而类加载器的任务是根据一个类的全限定名来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java.lang.Class对象实例,在虚拟机提供了3种类加载器,引导(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器)


[第3题] 类加载器有哪些

我们进一步了解类加载器间的关系(并非指继承关系),主要可以分为以下4点

启动类加载器,由C++实现,没有父类。

拓展类加载器(ExtClassLoader),由Java语言实现,父类加载器为null

系统类加载器(AppClassLoader),由Java语言实现,父类加载器为ExtClassLoader

自定义类加载器,父类加载器肯定为AppClassLoader。

启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责将

<JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也是没有作用的(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。

扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的,是Launcher的静态内部类,它负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,开发者可以直接使用标准扩展类加载器。

也称应用程序加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader。它负责加载系统类路径java

-classpath或-D java.class.path

指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。

在Java的日常应用程序开发中,类的加载几乎是由上述3种类加载器相互配合执行的,在必要时,我们还可以自定义类加载器,需要注意的是,Java虚拟机对class文件采用的是按需加载的方式,也就是说当需要使用该类时才会将它的class文件加载到内存生成class对象,而且加载某个类的class文件时,Java虚拟机采用的是双亲委派模式即把请求交由父类处理,它一种任务委派模式,下面我们进一步了解它。

public class ClassLoaderTest { public static void main(String[] args)

throws ClassNotFoundException { FileClassLoader loader1 = new

FileClassLoader(rootDir); System.out.println("自定义类加载器的父加载器:

"+loader1.getParent()); System.out.println("系统默认的AppClassLoader:

"+ClassLoader.getSystemClassLoader());

System.out.println("AppClassLoader的父类加载器:

"+ClassLoader.getSystemClassLoader().getParent());

System.out.println("ExtClassLoader的父类加载器:

"+ClassLoader.getSystemClassLoader().getParent().getParent()); /** 输出结果:

自定义类加载器的父加载器: sun.misc.Launcher$AppClassLoader@29453f44 系统默认的AppClassLoader: sun.misc.Launcher$AppClassLoader@29453f44 AppClassLoader的父类加载器: sun.misc.Launcher$ExtClassLoader@6f94fa3e ExtClassLoader的父类加载器: null */ }

[第4题] 双亲委派模式

双亲委派模式要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,请注意双亲委派模式中的父子关系并非通常所说的类继承关系,而是采用组合关系来复用父类加载器的相关代码,类加载器间的关系如下





















双亲委派模式是在Java 1.2后引入的,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这不就是传说中的实力坑爹啊?那么采用这种模式有啥用呢?

双亲委派模式优势

采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。可能你会想,如果我们在classpath路径下自定义一个名为java.lang.SingleInterge类(该类是胡编的)呢?该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会报出如下异常

java.lang.SecurityException: Prohibited package name: java.lang

1

所以无论如何都无法加载成功的。





[第5题] 编写自己的类加载器

场景:

那么编写自定义类加载器的意义何在呢?

当class文件不在ClassPath路径下,默认系统类加载器无法找到该class文件,在这种情况下我们需要实现一个自定义的ClassLoader来加载特定路径下的class文件生成class对象。

当一个class文件是通过网络传输并且可能会进行相应的加密操作时,需要先对class文件进行相应的解密后再加载到JVM内存中,这种情况下也需要编写自定义的ClassLoader并实现相应的逻辑。

当需要实现热部署功能时(一个class文件通过不同的类加载器产生不同class对象从而实现热部署功能),需要实现自定义ClassLoader的逻辑。

方法:

实现自定义类加载器需要继承ClassLoader或者URLClassLoader,继承ClassLoader则需要自己重写findClass()方法并编写加载逻辑,继承URLClassLoader则可以省去编写findClass()方法以及class文件加载转换成字节码流的代码。







[第6题] 对象的创建

a) 遇到一个新类时,会进行类的加载,定位到class文件

b) 对所有静态成员变量初始化,静态代码块也会执行,而且只在类加载的时候执行一次

c) New 对象时,jvm会在堆中分配一个足够大的存储空间

d) 存储空间清空,为所有的变量赋默认值,所有的对象引用赋值为null

e) 根据书写的位置给字段一些初始化操作

f) 调用构造器方法(没有继承)

[第7题] jvm的优化

a) 设置参数,设置jvm的最大内存数

b) 垃圾回收器的选择

[第8题] 什么是Java虚拟机?为什么Java被称作是“平台无关的编程语言”?

Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件。

Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。

[第9题] JDK和JRE的区别是什么?

Java运行时环境(JRE)是将要执行Java程序的Java虚拟机。它同时也包含了执行applet需要的浏览器插件。Java开发工具包(JDK)是完整的Java软件开发包,包含了JRE,编译器和其他的工具(比如:JavaDoc,Java调试器),可以让开发者开发、编译、执行Java应用程序。

JDK(Java Development

Kit)即为Java开发工具包,包含编写Java程序所必须的编译、运行等开发工具以及JRE。开发工具如:用于编译java程序的javac命令、用于启动JVM运行java程序的java命令、用于生成文档的javadoc命令以及用于打包的jar命令等等。

JRE(Java Runtime

Environment)即为Java运行环境,提供了运行Java应用程序所必须的软件环境,包含有Java虚拟机(JVM)和丰富的系统类库。系统类库即为java提前封装好的功能类,只需拿来直接使用即可,可以大大的提高开发效率。

JVM(Java Virtual Machines)即为Java虚拟机,提供了字节码文件(.class)的运行环境支持。

简单说,就是JDK包含JRE包含JVM。

[第10题] ”static”关键字是什么意思?Java中是否可以覆盖(override)一个private或者是static的方法?

“static”关键字表明一个成员变量或者是成员方法可以在没有所属的类的实例变量的情况下被访问。

Java中static方法不能被覆盖,因为方法覆盖是基于运行时动态绑定的,而static方法是编译时静态绑定的。static方法跟类的任何实例都不相关,所以概念上不适用。

[第11题] 是否可以在static环境中访问非static变量?

static变量在Java中是属于类的,它在所有的实例中的值是一样的。当类被Java虚拟机载入的时候,会对static变量进行初始化。如果你的代码尝试不用实例来访问非static的变量,编译器会报错,因为这些变量还没有被创建出来,还没有跟任何实例关联上。

[第12题] Java支持的数据类型有哪些?什么是自动拆装箱?

Java语言支持的8中基本数据类型是:

byte

short

int

long

float

double

boolean

char

自动装箱是Java编译器在基本数据类型和对应的对象包装类型之间做的一个转化。比如:把int转化成Integer,double转化成double,等等。反之就是自动拆箱。

基本数据类型:

整数值型:byte,short,int,long,

字符型:char

浮点类型:float,double

布尔型:boolean

整数默认int型,小数默认是double型。Float和long类型的必须加后缀。

首先知道String是引用类型不是基本类型,引用类型声明的变量是指该变量在内存中实际存储的是一个引用地址,实体在堆中。引用类型包括类、接口、数组等。String类还是final修饰的。

而包装类就属于引用类型,自动装箱和拆箱就是基本类型和引用类型之间的转换,至于为什么要转换,因为基本类型转换为引用类型后,就可以new对象,从而调用包装类中封装好的方法进行基本类型之间的转换或者toString(当然用类名直接调用也可以,便于一眼看出该方法是静态的),还有就是如果集合中想存放基本类型,泛型的限定类型只能是对应的包装类型。

[第13题] Java中的方法覆盖(Overriding)和方法重载(Overloading)是什么意思?

Java中的方法重载发生在同一个类里面两个或者是多个方法的方法名相同但是参数不同的情况。与此相对,方法覆盖是说子类重新定义了父类的方法。方法覆盖必须有相同的方法名,参数列表和返回类型。覆盖者可能不会限制它所覆盖的方法的访问。

[第14题] Java中,什么是构造函数?什么是构造函数重载?什么是复制构造函数?

当新对象被创建的时候,构造函数会被调用。每一个类都有构造函数。在程序员没有给类提供构造函数的情况下,Java编译器会为这个类创建一个默认的构造函数。

Java中构造函数重载和方法重载很相似。可以为一个类创建多个构造函数。每一个构造函数必须有它自己唯一的参数列表。

Java不支持像C++中那样的复制构造函数,这个不同点是因为如果你不自己写构造函数的情况下,Java不会创建默认的复制构造函数。

[第15题] Java支持多继承么?

不支持,Java不支持多继承。每个类都只能继承一个类,但是可以实现多个接口。

[第16题] 接口和抽象类的区别是什么?

Java提供和支持创建抽象类和接口。它们的实现有共同点,不同点在于:

接口中所有的方法隐含的都是抽象的。而抽象类则可以同时包含抽象和非抽象的方法。

类可以实现很多个接口,但是只能继承一个抽象类

类如果要实现一个接口,它必须要实现接口声明的所有方法。但是,类可以不实现抽象类声明的所有方法,当然,在这种情况下,类也必须得声明成是抽象的。

抽象类可以在不提供接口方法实现的情况下实现接口。

Java接口中声明的变量默认都是final的。抽象类可以包含非final的变量。

Java接口中的成员函数默认是public的。抽象类的成员函数可以是private,protected或者是public。

接口是绝对抽象的,不可以被实例化。抽象类也不可以被实例化,但是,如果它包含main方法的话是可以被调用的。

也可以参考JDK8中抽象类和接口的区别

[第17题] 10.什么是值传递和引用传递?

对象被值传递,意味着传递了对象的一个副本。因此,就算是改变了对象副本,也不会影响源对象的值。

对象被引用传递,意味着传递的并不是实际的对象,而是对象的引用。因此,外部对引用对象所做的改变会反映到所有的对象上。

[第18题] 我们能在 Switch 中使用 String 吗?

从 Java 7 开始,我们可以在 switch case 中使用字符串,但这仅仅是一个语法糖。内部实现在 switch 中使用字符串的 hash code。

[第19题] 20.Java 中的构造器链是什么?

当你从一个构造器中调用另一个构造器,就是Java 中的构造器链。这种情况只在重载了类的构造器的时候才会出现。

[第20题] 64 位 JVM 中,int 的长度是多数?



Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。意思就是说,在 32 位 和 64 位 的Java 虚拟机中,int 类型的长度是相同的。

[第21题]  32 位和 64 位的 JVM,int 类型变量的长度是多数?



32 位和 64 位的 JVM 中,int 类型变量的长度是相同的,都是 32 位或者 4 个字节。

[第22题]  “a==b”和”a.equals(b)”有什么区别?



如果 a 和 b 都是对象,则 a==b 是比较两个对象的引用,只有当 a 和 b 指向的是堆中的同一个对象才会返回 true,而 a.equals(b) 是进行逻辑比较,所以通常需要重写该方法来提供逻辑一致性的比较。例如,String 类重写 equals() 方法,所以可以用于两个不同对象,但是包含的字母相同的比较。



[第23题] a.hashCode() 有什么用?与 a.equals(b) 有什么关系?



hashCode() 方法是相应对象整型的 hash 值。它常用于基于 hash 的集合类,如 Hashtable、HashMap、LinkedHashMap等等。它与 equals() 方法关系特别紧密。根据 Java 规范,两个使用 equal() 方法来判断相等的对象,必须具有相同的 hash code。

[第24题] final、finalize 和 finally 的不同之处?



final 是一个修饰符,可以修饰变量、方法和类。如果 final 修饰变量,意味着该变量的值在初始化后不能被改变。finalize 方法是在对象被回收之前调用的方法,给对象自己最后一个复活的机会,但是什么时候调用 finalize 没有保证。finally 是一个关键字,与 try 和 catch 一起用于异常的处理。finally 块一定会被执行,无论在 try 块中是否有发生异常。



[第25题] Java 中的编译期常量是什么?使用它又什么风险?



公共静态不可变(public static final )变量也就是我们所说的编译期常量,这里的 public 可选的。实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道这些变量在运行时不能改变。这种方式存在的一个问题是你使用了一个内部的或第三方库中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端仍然在使用老的值,甚至你已经部署了一个新的jar。为了避免这种情况,当你在更新依赖 JAR 文件时,确保重新编译你的程序。

[第26题] 用最有效率的方法计算2乘以8?

答: 2 << 3(左移3位相当于乘以2的3次方,右移3位相当于除以2的3次方)。

[第三篇] JVM

[第1题] 要求

Java虚拟机

出乎意料,Java虚拟机应该是很重要的一块内容,结果在这几家公司中被问到的概率几乎为0。要知道,去年可是花了大量的时间去研究Java虚拟机的,光周志明老师的《深入理解Java虚拟机:JVM高级特性与最佳实践》,就读了不下五遍。

言归正传,虽然Java虚拟机没问到,但我觉得还是有必要研究的,就简单地列一个提纲吧,谈谈Java虚拟机中比较重要的内容:

(1)Java虚拟机的内存布局

(2)GC算法及几种垃圾收集器

(3)类加载机制,也就是双亲委派模型

(4)Java内存模型

(5)happens-before规则

(6)volatile关键字使用规则

[第2题] JVM的内存结构

  答:主要分为三大块 堆内存、方法区、栈;栈又分为JVM栈、本地方法栈

    堆(heap space),堆内存是JVM中最大的一块,有年轻代和老年代组成,而年轻代又分为三分部分,Eden区,From Survivor,To Survivor,默认情况下按照8:1:1来分配

    方法区(Method area),存储类信息、常量、静态变量等数据,是线程共享的区域

    程序计数器(Program counter Register),是一块较小的内存空间,是当前线程所执行的字节码的行号指示器

    JVM栈(JVM stacks),也是线程私有的,生命周期与线程相同,每个方法被执行时都会创建一个栈帧,用于存储局部变量表、操作栈、动态链接、方法出口等信息

    本地方法栈(Native Mthod Stacks),为虚拟机使用的native方法服务

[第3题] .Java的内存模型以及GC算法

[第4题] Java中垃圾回收有什么目的?什么时候进行垃圾回收?

垃圾回收的目的是识别并且丢弃应用不再使用的对象来释放和重用资源。

[第5题] System.gc()和Runtime.gc()会做什么事情?

这两个方法用来提示JVM要进行垃圾回收。但是,立即开始还是延迟进行垃圾回收是取决于JVM的。



[第12题] Serial 与 Parallel GC之间的不同之处?



Serial 与 Parallel 在GC执行的时候都会引起 stop-the-world。它们之间主要不同 serial 收集器是默认的复制收集器,执行 GC 的时候只有一个线程,而 parallel 收集器使用多个 GC 线程来执行。

[第13题] Java 中 WeakReference 与 SoftReference的区别?



虽然 WeakReference 与 SoftReference 都有利于提高 GC 和 内存的效率,但是 WeakReference ,一旦失去最后一个强引用,就会被 GC 回收,而软引用虽然不能阻止被回收,但是可以延迟到 JVM 内存不足的时候。

[第14题] JVM 选项 -XX:+UseCompressedOops 有什么作用?为什么要使用?



当你将你的应用从 32 位的 JVM 迁移到 64 位的 JVM 时,由于对象的指针从 32 位增加到了 64 位,因此堆内存会突然增加,差不多要翻倍。这也会对 CPU 缓存(容量比内存小很多)的数据产生不利的影响。因为,迁移到 64 位的 JVM 主要动机在于可以指定最大堆大小,通过压缩 OOP 可以节省一定的内存。通过 -XX:+UseCompressedOops 选项,JVM 会使用 32 位的 OOP,而不是 64 位的 OOP。



[第15题] 怎样通过 Java 程序来判断 JVM 是 32 位 还是 64 位?



你可以检查某些系统属性如 sun.arch.data.model 或 os.arch 来获取该信息。



[第16题] 32 位 JVM 和 64 位 JVM 的最大堆内存分别是多数?



理论上说上 32 位的 JVM 堆内存可以到达 2^32,即 4GB,但实际上会比这个小很多。不同操作系统之间不同,如 Windows 系统大约 1.5 GB,Solaris 大约 3GB。64 位 JVM允许指定最大的堆内存,理论上可以达到 2^64,这是一个非常大的数字,实际上你可以指定堆内存大小到 100GB。甚至有的 JVM,如 Azul,堆内存到 1000G 都是可能的。



[第17题] JRE、JDK、JVM 及 JIT 之间有什么不同?



JRE 代表 Java 运行时(Java run-time),是运行 Java 引用所必须的。JDK 代表 Java 开发工具(Java development kit),是 Java 程序的开发工具,如 Java 编译器,它也包含 JRE。JVM 代表 Java 虚拟机(Java virtual machine),它的责任是运行 Java 应用。JIT 代表即时编译(Just In Time compilation),当代码执行的次数超过一定的阈值时,会将 Java 字节码转换为本地代码,如,主要的热点代码会被准换为本地代码,这样有利大幅度提高 Java 应用的性能。



[第18题] 解释 Java 堆空间及 GC?



当通过 Java 命令启动 Java 进程的时候,会为它分配内存。内存的一部分用于创建堆空间,当程序中创建对象的时候,就从对空间中分配内存。GC 是 JVM 内部的一个进程,回收无效对象的内存用于将来的分配。



[第19题] 你能保证 GC 执行吗?



不能,虽然你可以调用 System.gc() 或者 Runtime.gc(),但是没有办法保证 GC 的执行。



[第20题] 怎么获取 Java 程序使用的内存?堆使用的百分比?



可以通过 java.lang.Runtime 类中与内存相关方法来获取剩余的内存,总内存及最大堆内存。通过这些方法你也可以获取到堆使用的百分比及堆内存的剩余空间。



Runtime.freeMemory() 方法返回剩余空间的字节数,Runtime.totalMemory() 方法总内存的字节数,Runtime.maxMemory() 返回最大内存的字节数。



[第21题] Java 中堆和栈有什么区别?(答案)



JVM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。

[第22题] 解释内存中的栈(stack)、堆(heap)和方法区(method area)的用法。

通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用JVM中的栈空间;而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域,由于现在的垃圾收集器都采用分代收集算法,所以堆空间还可以细分为新生代和老生代,再具体一点可以分为Eden、Survivor(又可分为From Survivor和To Survivor)、Tenured;方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据;程序中的字面量(literal)如直接书写的100、”hello”和常量都是放在常量池中,常量池是方法区的一部分,。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量池空间不足则会引发OutOfMemoryError。

[第23题] 动态代理的两种方式,以及区别

  答:jdk动态代理和cglib动态代理;

  JDK动态代理只能对实现了接口的类生成代理,而不能针对类;cglib是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,因为是继承,所以该类或方法最好不要声明称final,final可以阻止继承和多态;

[第四篇] 异常处理

[第1题] Java中的两种异常类型是什么?他们有什么区别?

Java中有两种异常:受检查的(checked)异常和不受检查的(unchecked)异常。不受检查的异常不需要在方法或者是构造函数上声明,就算方法或者是构造函数的执行可能会抛出这样的异常,并且不受检查的异常可以传播到方法或者是构造函数的外面。相反,受检查的异常必须要用throws语句在方法或者是构造函数上声明。这里有Java异常处理的一些小建议。

[第2题] Java中Exception和Error有什么区别?

Exception和Error都是Throwable的子类。Exception用于用户程序可以捕获的异常情况。Error定义了不期望被用户程序捕获的异常。

[第3题] throw和throws有什么区别?

throw关键字用来在程序中明确的抛出异常,相反,throws语句用来表明方法不能处理的异常。每一个方法都必须要指定哪些异常不能处理,所以方法的调用者才能够确保处理可能发生的异常,多个异常是用逗号分隔的。

[第4题] 异常处理的时候,finally代码块的重要性是什么?(译者注:作者标题的序号弄错了)

无论是否抛出异常,finally代码块总是会被执行。就算是没有catch语句同时又抛出异常的情况下,finally代码块仍然会被执行。最后要说的是,finally代码块主要用来释放资源,比如:I/O缓冲区,数据库连接。

[第5题] 异常处理完成以后,Exception对象会发生什么变化?

Exception对象会在下一个垃圾回收过程中被回收掉。

[第6题] finally代码块和finalize()方法有什么区别?

无论是否抛出异常,finally代码块都会执行,它主要是用来释放应用占用的资源。finalize()方法是Object类的一个protected方法,它是在对象被垃圾回收之前由Java虚拟机来调用的。

[第7题] .Java内存泄露的问题调查定位:

jmap,jstack的使用等等

[第五篇] 不可变对象

[第1题] 什么是不可变对象(immutable object)?Java 中怎么创建一个不可变对象?

不可变对象指对象一旦被创建,状态就不能再改变。任何修改都会创建一个新的对象,如 String、Integer及其它包装类。详情参见答案,一步一步指导你在 Java 中创建一个不可变的类。

[第2题] 我们能创建一个包含可变对象的不可变对象吗?

是的,我们是可以创建一个包含可变对象的不可变对象的,你只需要谨慎一点,不要共享可变对象的引用就可以了,如果需要变化时,就返回原对象的一个拷贝。最常见的例子就是对象中包含一个日期对象的引用。

[第六篇] 数据类型

[第1题] 请写出下面几个表达式的结果,答案可以用10进制或16进制书写

1). 0xaa | 0x55

2). 15 & 240

3). 10 ^ 12

4). -2 >> 1

5). -2 >>> 1

1). 分析:十六进制数用0x……来表示,后面一个十六进制位是四位,两个十六进制位为一个字节,最多后面可以有8个十六进制位,32个字节,如:0xFFFFFFFF。 或(“ | ”)运算,全0为0,其他为1。

所以:0xaa 用二进制表示为 10101010 ,0x55 用二进制表示为 01010101 ,按位或之后为 11111111 ,十进制数为255,十六进制数为 0xFF 。

2). 分析:10进制转换成2进制,用该数字除以2,记录商和余数,利用商再次除以2,记录商和余数……直到上为0或余数为0停止,余数逆序组成二进制的从低到高位(最后的余数为二进制最低位)。与(“ & ”)运算,全1为1,其他为0 。

所以: 15 等于1111 ,240等于 11110000,15前面用0补齐为00001111 ,按位与之后为 00000000 ,即结果为0

3). 分析: 亦或(“ ^ ”)运算,相同取0,不同取1 。

所以:1010 ^ 1100 =0110 , 十进制表示为6,十六进制表示为 0x06 。

4). 分析: 带符号右移(“ >> ”),即有符号位时,负数符号位补1,正数符号位补0, -2 的二进制求法是正数取反加1,因此 2 的二进制表示为0000 0000 0000 0000 0000 0000 0000 0010 ,取反加一为

1111 1111 1111 1111 1111 1111 1111 1110 ,即 -2 的二进制表示。

注: >> , << , >>> , 运算符只针对int型和long型,byte ,short ,char型需要转换成Int型在进行操作。

所以: 带符号右移之后为 1111 1111 1111 1111 1111 1111 1111 1111 ,除符号位之外,减一取反,得到带符号十进 制数为 -1 。

5). 分析:无符号右移 (“ >>> ”) ,即无论正负数,右移之后符号位均补 0 。

所以: -2 的二进制无符号右移一位之后为 0111 1111 1111 1111 1111 1111 1111 1111,即 2^31 - 1,二的三十一次方减一。

注:右移和无符号右移主要区别就在于左面最高位补 0 还是补 1 的问题,无符号右移任何时候最高位都补 0 , 有符号右移则是正数补 0 ,负数补 1 。(没有无符号左移!)。

[第2题] &和&&的区别?

&运算符有两种用法:(1)按位与;(2)逻辑与。&&运算符是短路与运算。逻辑与跟短路与的差别是非常巨大的,虽然二者都要求运算符左右两端的布尔值都是true整个表达式的值才是true。&&之所以称为短路运算是因为,如果&&左边的表达式的值是false,右边的表达式会被直接短路掉,不会进行运算。很多时候我们可能都需要用&&而不是&,例如在验证用户登录时判定用户名不是null而且不是空字符串,应当写为:username != null &&!username.equals(“”),二者的顺序不能交换,更不能用&运算符,因为第一个条件如果不成立,根本不能进行字符串的equals比较,否则会产生NullPointerException异常。注意:逻辑或运算符(|)和短路或运算符(||)的差别也是如此。

[第3题] Java 中应该使用什么数据类型来代表价格?



如果不是特别关心内存和性能的话,使用BigDecimal,否则使用预定义精度的 double 类型。

[第4题] 怎么将 byte 转换为 String?

可以使用 String 接收 byte[] 参数的构造器来进行转换,需要注意的点是要使用的正确的编码,否则会使用平台默认编码,这个编码可能跟原来的编码相同,也可能不同。

[第5题] Java 中 bytes 与其他类型的转换?



public class Test {         

    private static ByteBuffer buffer = ByteBuffer.allocate(8);      

    public static void main(String[] args) {   

        //测试 int 转 byte   

        int int0 = 234;   

        byte byte0 = intToByte(int0);   

        System.out.println("byte0=" + byte0);//byte0=-22   

        //测试 byte 转 int   

        int int1 = byteToInt(byte0);   

        System.out.println("int1=" + int1);//int1=234   

        //测试 int 转 byte 数组   

        int int2 = 1417;   

        byte[] bytesInt = intToByteArray(int2);   

        System.out.println("bytesInt=" + bytesInt);//bytesInt=[B@de6ced   

        //测试 byte 数组转 int   

        int int3 = byteArrayToInt(bytesInt);   

        System.out.println("int3=" + int3);//int3=1417   

        //测试 long 转 byte 数组   

        long long1 = 2223;   

        byte[] bytesLong = longToBytes(long1);   

        System.out.println("bytes=" + bytesLong);//bytes=[B@c17164   

        //测试 byte 数组 转 long   

        long long2 = bytesToLong(bytesLong);   

        System.out.println("long2=" + long2);//long2=2223   

    }   

    //byte 与 int 的相互转换   

    public static byte intToByte(int x) {   

        return (byte) x;   

    }   

    public static int byteToInt(byte b) {   

        //Java 总是把 byte 当做有符处理;我们可以通过将其和 0xFF 进行二进制与得到它的无符值   

        return b & 0xFF;   

    }   

    //byte 数组与 int 的相互转换   

    public static int byteArrayToInt(byte[] b) {   

        return   b[3] & 0xFF |   

                (b[2] & 0xFF) << 8 |   

                (b[1] & 0xFF) << 16 |   

                (b[0] & 0xFF) << 24;   

    }   

    public static byte[] intToByteArray(int a) {   

        return new byte[] {   

            (byte) ((a >> 24) & 0xFF),   

            (byte) ((a >> 16) & 0xFF),      

            (byte) ((a >> 8) & 0xFF),      

            (byte) (a & 0xFF)   

        };   

    }   

    //byte 数组与 long 的相互转换   

    public static byte[] longToBytes(long x) {   

        buffer.putLong(0, x);   

        return buffer.array();   

    }   

    public static long bytesToLong(byte[] bytes) {   

        buffer.put(bytes, 0, bytes.length);   

        buffer.flip();//need flip   

        return buffer.getLong();   

    }   

}

[第6题] 我们能将 int 强制转换为 byte 类型的变量吗?如果该值大于 byte 类型的范围,将会出现什么现象?



是的,我们可以做强制转换,但是 Java 中 int 是 32 位的,而 byte 是 8 位的,所以,如果强制转化是,int 类型的高 24 位将会被丢弃,byte 类型的范围是从 -128 到 128。

[第7题] 存在两个类,B 继承 A,C 继承 B,我们能将 B 转换为 C 么?如 C = (C) B;



可以,向下转型。但是不建议使用,容易出现类型转型异常。

[第8题] 哪个类包含 clone 方法?是 Cloneable 还是 Object?



java.lang.Cloneable 是一个标示性接口,不包含任何方法,clone 方法在 object 类中定义。并且需要知道 clone() 方法是一个本地方法,这意味着它是由 c 或 c++ 或 其他本地语言实现的。



[第9题] a = a + b 与 a += b 的区别



+= 隐式的将加操作的结果类型强制转换为持有结果的类型。如果两这个整型相加,如 byte、short 或者 int,首先会将它们提升到 int 类型,然后在执行加法操作。如果加法操作的结果比 a 的最大值要大,则 a+b 会出现编译错误,但是 a += b 没问题,如下:



byte a = 127;

byte b = 127;

b = a + b; // error : cannot convert from int to byte

b += a; // ok

[第10题] 我能在不进行强制转换的情况下将一个 double 值赋值给 long 类型的变量吗?

不行,你不能在没有强制类型转换的前提下将一个 double 值赋值给 long 类型的变量,因为 double 类型的范围比 long 类型更广,所以必须要进行强制转换。

[第11题]  3*0.1 == 0.3 将会返回什么?true 还是 false?

false,因为有些浮点数不能完全精确的表示出来。

[第12题] int 和 Integer 哪个会占用更多的内存?

Integer 对象会占用更多的内存。Integer 是一个对象,需要存储对象的元数据。但是 int 是一个原始类型的数据,所以占用的空间更少。

[第13题] 为什么 Java 中的 String 是不可变的(Immutable)?

Java 中的 String 不可变是因为 Java 的设计者认为字符串使用非常频繁,将字符串设置为不可变可以允许多个客户端之间共享相同的字符串。更详细的内容参见答案。

[第14题] String和StringBuilder、StringBuffer的区别?

Java平台提供了两种类型的字符串:String和StringBuffer/StringBuilder,它们可以储存和操作字符串。其中String是只读字符串,也就意味着String引用的字符串内容是不能被改变的。而StringBuffer/StringBuilder类表示的字符串对象可以直接进行修改。StringBuilder是Java 5中引入的,它和StringBuffer的方法完全相同,区别在于它是在单线程环境下使用的,因为它的所有方面都没有被synchronized修饰,因此它的效率也比StringBuffer要高。



[第三部分] 集合类

Collection集合接口,List、set实现Collection接口,arraylist、linkedlist,vector实现list接口,stack继承vector,Map接口,hashtable、hashmap实现map接口

[第15题] Java集合类框架的基本接口有哪些?

Java集合类提供了一套设计良好的支持对一组对象进行操作的接口和类。Java集合类里面最基本的接口有:

Collection:代表一组对象,每一个对象都是它的子元素。

Set:不包含重复元素的Collection。

List:有顺序的collection,并且可以包含重复元素。

Map:可以把键(key)映射到值(value)的对象,键不能重复。

[第16题] 为什么集合类没有实现Cloneable和Serializable接口?

集合类接口指定了一组叫做元素的对象。集合类接口的每一种具体的实现类都可以选择以它自己的方式对元素进行保存和排序。有的集合类允许重复的键,有些不允许。

[第17题] 什么是迭代器(Iterator)?

Iterator接口提供了很多对集合元素进行迭代的方法。每一个集合类都包含了可以返回迭代器实例的迭代方法。迭代器可以在迭代的过程中删除底层集合的元素,但是不可以直接调用集合的remove(Object Obj)删除,可以通过迭代器的remove()方法删除。

[第18题] Iterator和ListIterator的区别是什么?

下面列出了他们的区别:

Iterator可用来遍历Set和List集合,但是ListIterator只能用来遍历List。

Iterator对集合只能是前向遍历,ListIterator既可以前向也可以后向。

ListIterator实现了Iterator接口,并包含其他的功能,比如:增加元素,替换元素,获取前一个和后一个元素的索引,等等。

[第19题] 快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?

快速失败:当你在迭代一个集合的时候,如果有另一个线程正在修改你正在访问的那个集合时,就会抛出一个ConcurrentModification异常。

在java.util包下的都是快速失败。

安全失败:你在迭代的时候会去底层集合做一个拷贝,所以你在修改上层集合的时候是不会受影响的,不会抛出ConcurrentModification异常。

在java.util.concurrent包下的全是安全失败的。



[第20题] Java中的HashMap的工作原理是什么?

Java中的HashMap是以键值对(key-value)的形式存储元素的。HashMap需要一个hash函数,它使用hashCode()和equals()方法来向集合/从集合添加和检索元素。当调用put()方法的时候,HashMap会计算key的hash值,然后把键值对存储在集合中合适的索引上。如果key已经存在了,value会被更新成新值。HashMap的一些重要的特性是它的容量(capacity),负载因子(load factor)和扩容极限(threshold resizing)。

HashMap是基于哈希表的Map接口的非同步实现,提供所有可选的映射操作,并允许使用null值和null键,不保证映射的顺序;HashMap是一个“链表散列”的数据结构,即数组和链表的结合体;它的底层就是一个数组结构,数组中的每一项又是一个链表,每当新建一个HashMap时,就会初始化一个数组;

  而在JDK8中引入了红黑树的部分,当存入到数组中的链表长度大于(默认)8时,即转为红黑树;利用红黑树快速增删改查的特点提高HashMap的性能,其中会用到红黑树的插入、删除、查找等算法。本文不再对红黑树展开讨论,

















[第21题] 当两个对象的hashcode相同会发生什么?

因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。

当我们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,然后会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。



[第22题] hashCode()和equals()方法的重要性体现在什么地方?

Java中的HashMap使用hashCode()和equals()方法来确定键值对的索引,当根据键获取值的时候也会用到这两个方法。如果没有正确的实现这两个方法,两个不同的键可能会有相同的hash值,因此,可能会被集合认为是相等的。而且,这两个方法也用来发现重复元素。所以这两个方法的实现对HashMap的精确性和正确性是至关重要的。

HashMap使用Key对象的hashCode()和equals()方法去决定key-value对的索引。当我们试着从HashMap中获取值的时候,这些方法也会被用到。如果这些方法没有被正确地实现,在这种情况下,两个不同Key也许会产生相同的hashCode()和equals()输出,HashMap将会认为它们是相同的,然后覆盖它们,而非把它们存储到不同的地方。同样的,所有不允许存储重复数据的集合类都使用hashCode()和equals()去查找重复,所以正确实现它们非常重要。equals()和hashCode()的实现应该遵循以下规则:

(1)如果o1.equals(o2),那么o1.hashCode() == o2.hashCode()总是为true的。

(2)如果o1.hashCode() == o2.hashCode(),并不意味着oequals(o2)会为true。

0 个回复

您需要登录后才可以回帖 登录 | 加入黑马