前言
这篇文章主要针对的是笔试题中出现的通过查看代码执行结果选择正确答案题材。
正式进入题目内容:
1、(单选题)下面代码的输出结果是什么?
public class Base {
private String baseName = "base";
public Base (){
callName();
}
public void callName(){
System.out.println(baseName);
}
static class Sub extends Base {
private String baseName = "Sub";
public void callName(){
System.out.println(baseName);
}
}
public static void main(String argn[]){
Base b = new Sub();
}
}
A、null B、Sub C、base
题目解析:这道题主要考察的是java面向对象的基本特征之多态特性以及一个类的加载顺序。首先我们要明白一个类的加载顺序,如果是存在有父类和子类继承关系的情况下,一个类文件的加载顺序是怎么样子的呢?
加载顺序如下:
(1)、父类的静态变量,再加载父类的静态代码块,静态方法除外;
(2)、子类的静态变量,再加载子类的静态代码块,静态方法除外;
(3)、父类的非静态变量、再加载父类的非静态代码块;
(4)、父类的构造函数;
(5)、子类的非静态变量,再加载子类的非静态代码块;
(6)、子类的构造函数。
其中步骤(1)和(2)是在类进入到连接阶段的时候就执行了,而不是等到通过new 实例化对象的时候才执行。
其次是多态特征的问题,Base b = new Sub();它是一种多态性的表现,声明是Base类型,而运行是Sub类型。题目中,callNam()方法是Sub类重写Base类中的方法,而不是扩展的方法。
当Base b = new Sub();操作时,按照类文件加载顺序,会先执行Base的无参构造调用callName()方法,如果子类没有重写callName()方法,那么则执行父类的callName()方法,如果子类重写了callName()方法,则执行子类的,遵循这个原则 :“如果子类没有,则从父类查找”。但是此时子类的非静态变量还未赋值,因此输出结果为null,选择A。
2、(单选题)下面代码的输出结果是什么?
public class Test{
public static int a = 1;
public static void main(String argn[]){
int a = 10;
a++;
Test.a++;
Test t = new Test();
System.out.println("a = "+a+" t.a = "+t.a);
}
}
A、a = 10 t.a = 3
B、a = 11 t.a = 2
C、a = 12 t.a = 1
D、a = 11 t.a = 1
题目解析:这道题考察的是成员变量和局部变量的差异以及自增运算符。我们来分析main方法中执行结果,按照执行顺序,代码执行是从上到下,在main方法中定义一个变量a,那么这里的变量a是局部变量,它的生命周期在这个main方法体中,会随着方法体被调用而调用,调用完毕而销毁。执行到第二步,进行了自增操作,a++,是先赋值在自增,那么操作结束后a的值为11,则排除A和C,继续往下走,执行到Test.a++;操作结果相当于是给类中的成员变量a进行了+1操作,而后new了Test的实例对象,再通过实例对象.a变量,相当于是调用类中的成员变量,因此结果为2,选B。
3、(单选题)下面代码的输出结果是什么?
class Foo{
final int i;
int j;
public void doSomethind(){
System.out.println(++j + i);
}
}
A、0 B、1 C、2 D、不能执行,因为编译有误
题目解析:这道题主要考察的是对final修饰符的掌握程度。因为final修饰符修饰的变量为常量,因此如果定义某成员变量或者局部变量为final类型的时候,一定要进行初始化。选D。
4、(单选题)下面代码的输出结果是什么?
class Base{
public void method(){
System.out.println("Base");
}
}
class Son extends Base{
public void method(){
System.out.println("Son");
}
public void methodB(){
System.out.println("SonB");
}
}
public class Test{
public static void main(String[] args) {
Base b = new Son();
b.method();
b.methodB();
}
}
A、Base SonB
B、Son SonB
C、Base Son SonB
D、编译不通过
题目解析:这道题主要考察的是多态的知识点。Base b = new Son();是多态的表现形式,父类对象调用了子类创建了Son对象。b调用的method()方法就是调用了子类重写的method()方法,而此时b还是属于Base对象,b调用的methodB()方法时,Base类中没有该方法,因此会编译不通过,选D。
5、(单选题)下面代码的输出结果是什么?
public class Test{
static String x = "1";
static int y = 2;
public static void main(String[] args) {
static int z = 3;
System.out.println(x+y+z);
}
}
A、3 B、123 C、13 D、程序有编译错误
题目解析:这道题考察的是static关键字的知识点。static修饰的变量为静态变量,该静态变量是与类一一对应的,只要该类被加载了,那么静态变量就能被使用。但是static关键字不能修饰局部变量,因此在程序编译时报出,选D。
6、对于JVM内存配置参数:-Xmx10240m -Xms10240m -Xmn5120m -XXSurvivorRatio=3,其最小内存值和Survivor区总大小分别是()?
A、5120m,1024m B、5120m,2048m C、10240m,1024m D10240m,2048m
题目解析:这道题主要考察的是对JVM内存参数的知识点,因此借此对JVM内存参数做更详细的说明:
1)、堆/Heap
JVM管理的内存叫堆,在32Bit操作系统上有4G的限制,一般来说Windows下为2G,而Linux 下为3G;64Bit的就没有这个限制。
-Xmx为JVM最大分配的堆内存大小,默认为物理内存的1/4;
-Xms为JVM初始分配的堆内存大小,默认为物理内存的1/64;
默认空余堆内存小于40%时,JVM就会增大堆直到-Xmx的最大限制,可以由 -XX:MinHeapFreeRatio=指定。
默认空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制,可以由 -XX:MaxHeapFreeRatio=指定。
服务器一般设置-Xms、-Xmx相等以避免在每次GC后调整堆的大小,所以上面的两个参数没啥用。
2)、分代/堆模型
JVM中堆内存分为:permantspace(持久代)和heapspace。
持久代中主要用于存放静态类型数据,如java Class,Method,field等反射对象,与垃圾收集器要收集的java对象关系不大。
而heapspace则分为年轻代和年老代,即HeapSpace = 【old + new{=Eden,from,to}】。
3)、年轻代(Young Generation)
所有新生成的对象首先都是放在年轻代中,年轻代的目标是尽可能快速的收集掉那些生命周期较短的对象。年轻代分为3个区,一个Eden区,两个Survivor区(from和to),题目中的-Xmn为堆内存中年轻代的大小,而-XXSurvivorRatio=3为年轻代中Eden区和一个Suvivor区大小比值。因此年轻代为5120m,Eden:Suvivor=3,而堆内存中有两个Suvivor区,因此Survivor区总大小为2048m。
大部分对象在Eden区生成。当Eden区满时,还存活的对象将会复制到Survivor区(两个中的一个),两个2Survivor区是对称的,没有先后关系,所以同一个Survivor区中可能同时存在从Eden区复制过来的对象,和从另外一个Survivor区复制过来的对象。当一个Survivor区满时,此区存活的对象将会复制到另外一个Survivor区,当两个Survivor区都满时,从之前Survivor区复制过来的对象如果还存在,那么将可能被复制到年老代。针对年轻代的垃圾回收即young GC。
4)、年老代(Old Generation)
在年老代中经历过N次(可配置)垃圾回收后仍然存活的对象将会被复制到年老代。因此年老代存放的都是一些生命周期较长的对象。针对年老代的垃圾回收为Full GC。
5)、持久代(permantspace)
用于存放静态类型数据,如java class,method等。默认为64M,可通过设置 -XX:MaxPermSize=xxx 来增加其空间大小。持久代对垃圾回收没有显著影响。但是有些应用可能动态生成或调用一些Class,例如Hibernate CGLib等,在这种时候往往需要设置一个比较大的持久代空间来存放这些运行过程中动态增加的类型数据。所以当一组对象生成时,内存申请过程如下:
- JVM会试图为相关Java对象在年轻代的Eden区中初始化一块内存区域。
- 当Eden区空间足够时,内存申请结束。否则执行下一步。
- JVM试图释放在Eden区中所有不活跃的对象(Young GC)。释放后若Eden空间仍然不足以放入新对象,JVM则试图将部分Eden区中活跃对象放入Survivor区。
- Survivor区被用来作为Eden区及年老代的中间交换区域。当年老代空间足够时,Survivor区中存活了一定次数的对象会被移到年老代。
- 当年老代空间不够时,JVM会在年老代进行完全的垃圾回收(Full GC)。
- Full GC后,若Survivor区及年老代仍然无法存放从Eden区复制过来的对象,则会导致JVM无法在Eden区为新生成的对象申请内存,即出现“Out of Memory”。
OOM(“Out of Memory”)异常一般主要有如下2种原因:
1. 年老代溢出,表现为:java.lang.OutOfMemoryError:Javaheapspace
这是最常见的情况,产生的原因可能是:设置的内存参数Xmx过小或程序的内存泄露及使用不当问题。
例如循环上万次的字符串处理、创建上千万个对象、在一段代码内申请上百M甚至上G的内存。还有的时候虽然不会报内存溢出,却会使系统不间断的垃圾回收,也无法处理其它请求。这种情况下除了检查程序、打印堆内存等方法排查,还可以借助一些内存分析工具,比如MAT就很不错。
2. 持久代溢出,表现为:java.lang.OutOfMemoryError:PermGenspace
通常由于持久代设置过小,动态加载了大量Java类而导致溢出 ,解决办法唯有将参数 -XX:MaxPermSize 调大(一般256m能满足绝大多数应用程序需求)。将部分Java类放到容器共享区(例如Tomcat share lib)去加载的办法也是一个思路,但前提是容器里部署了多个应用,且这些应用有大量的共享类库