摩根面试准备要点1.JVM架构(Vincent)主要包括两个子系统和两个组件:Classloader(类装载器)子系统,Executionengine(执行引擎)子系统;Runtimedataarea(运行时数据区域)组件,Nativeinterface(本地接口)组件。Classloader子系统的作用:根据给定的全限定名类名(如java.lang.Object)来装载class文件的内容到Runtimedataarea中的methodarea(方法区域)。Javsa程序员可以extendsjava.lang.ClassLoader类来写自己的Classloader。Executionengine子系统的作用:执行classes中的指令。任何JVMspecification实现(JDK)的核心是Executionengine,换句话说:Sun的JDK和IBM的JDK好坏主要取决于他们各自实现的Executionengine的好坏。每个运行中的线程都有一个Executionengine的实例。Nativeinterface组件:与nativelibraries交互,是其它编程语言交互的接口。Runtimedataarea组件:这个组件就是JVM中的内存。下面对这个部分进行详细介绍。Runtimedataarea的整体架构图Runtimedataarea主要包括五个部分:Heap(堆),MethodArea(方法区域),JavaStack(java的栈),ProgramCounter(程序计数器),Nativemethodstack(本地方法栈)。Heap和MethodArea是被所有线程的共享使用的;而Javastack,Programcounter和Nativemethodstack是以线程为粒度的,每个线程独自拥有。HeapJava程序在运行时创建的所有类实或数组都放在同一个堆中。而一个Java虚拟实例中只存在一个堆空间,因此所有线程都将共享这个堆。每一个java程序独占一个JVM实例,因而每个java程序都有它自己的堆空间,它们不会彼此干扰。但是同一java程序的多个线程都共享着同一个堆空间,就得考虑多线程访问对象(堆数据)的同步问题。(这里可能出现的异常java.lang.OutOfMemoryError:Javaheapspace)Methodarea在Java虚拟机中,被装载的class的信息存储在Methodarea的内存中。当虚拟机装载某个类型时,它使用类装载器定位相应的class文件,然后读入这个class文件内容并把它传输到虚拟机中。紧接着虚拟机提取其中的类型信息,并将这些信息存储到方法区。该类型中的类(静态)变量同样也存储在方法区中。与Heap一样,methodarea是多线程共享的,因此要考虑多线程访问的同步问题。比如,假设同时两个线程都企图访问一个名为Lava的类,而这个类还没有内装载入虚拟机,那么,这时应该只有一个线程去装载它,而另一个线程则只能等待。(这里可能出现的异常java.lang.OutOfMemoryError:PermGenfull)JavastackJavastack以帧为单位保存线程的运行状态。虚拟机只会直接对Javastack执行两种操作:以帧为单位的压栈或出栈。每当线程调用一个方法的时候,就对当前状态作为一个帧保存到javastack中(压栈);当一个方法调用返回时,从javastack弹出一个帧(出栈)。栈的大小是有一定的限制,这个可能出现StackOverFlow问题。下面的程序可以说明这个问题。publicclassTestStackOverFlow{publicstaticvoidmain(String[]args){Recursiver=newRecursive();r.doit(10000);//Exceptioninthreadmainjava.lang.StackOverflowError}}classRecursive{publicintdoit(intt){if(t=1){return1;}returnt+doit(t-1);}}Programcounter每个运行中的Java程序,每一个线程都有它自己的PC寄存器,也是该线程启动时创建的。PC寄存器的内容总是指向下一条将被执行指令的地址;,这里的地址可以是一个本地指针,也可以是在方法区中相对应于该方法起始指令的偏移量。Nativemethodstack对于一个运行中的Java程序而言,它还能会用到一些跟本地方法相关的数据区。当某个线程调用一个本地方法时,它就进入了一个全新的并且不再受虚拟机限制的世界。本地方法可以通过本地方法接口来访问虚拟机的运行时数据区,不止与此,它还可以做任何它想做的事情。比如,可以调用寄存器,或在操作系统中分配内存等。总之,本地方法具有和JVM相同的能力和权限。(这里出现JVM无法控制的内存溢出问题nativeheapOutOfMemory)2.CLassLoader(Vincent)Java的可执行文件不同于C/C++,Java编译器只产生中间字节码文件(.class文件),由Java虚拟机(java.exe)解释执行。Java发布的程序(JAR包)也多半是一堆class文件,运行时由ClassLoader加载到Java虚拟机中执行。ClassLoader是Java虚拟机的主要组成部分,由Java语言编写,用户可以实现自定义的ClassLoader来完成特定的功能。下面我们用例子说明ClassLoader。JVM规范定义了两种类型的ClassLoader:BootstrapClassLoader和User-definedClassLoader。JVM在运行时会产生三个ClassLoader:BootstrapClassLoader、ExtensionClassLoader和AppClassLoader。Bootstrap是用C++编写的,我们在Java中看不到它,是null,是JVM自带的类装载器,用来装载核心类库,如java.lang.*等。AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent为BootstrapClassLoader。3.java中,什么叫不可更改的类(immutableclass)(KevinTam)从字面意思来理解就是不会发生变化的类,那么是什么不会发生变化呢,其实就是类的状态,也就是不变类的实例一旦被创建,其状态就不会发生变化,举个例子:如果人是一个class,那么我们中的每一个都是人这个类的具体的instance,如果人这个类只有一个状态就是生身父母,那么它就是一个不变类,因为每一个人在出生的那一刹那,生身父母就已经被设置了值,而且终生都不会发生变化。不变类有什么好处呢?1)不变类是线程安全的,由于不变类的状态在创建以后不再发生变化,所以它可以在线程之间共享,而不需要同步。2)不变类的instance可以被reuse创建类的实例需要耗费CPU的时间,当这个实例不再被引用时,将会被垃圾回收掉,这时候,又需要耗费CPU的时间。对于不变类而言,一个好处就是可以将常用的实例进行缓存,从而减少了对象的创建。举个例子,对于布尔型,最常用的便是trueandfalse。JDK中的Boolean类就是一个不变类,并且对这两个实例进行了缓冲。publicfinalclassBooleanimplementsjava.io.Serializable{/***ThecodeBoolean/codeobjectcorrespondingtotheprimitive*valuecodetrue/code.*/publicstaticfinalBooleanTRUE=newBoolean(true);/***ThecodeBoolean/codeobjectcorrespondingtotheprimitive*valuecodefalse/code.*/publicstaticfinalBooleanFALSE=newBoolean(false);//这个方法不会创建新的对象,而是重用已经创建好的instancepublicstaticBooleanvalueOf(booleanb){return(b?TRUE:FALSE);}}3)不变类的某些方法可以缓存计算的结果hashCode这个方法来自于Object这个类,这个方法用来返回对象的hashCode,主要用于将对象放置到hashtable中时,来确定这个对象的存储位置。对于一个不变类的实例,它的hashCode也是不变的,所以就可以缓存这个计算的结果,来提高性能,避免不必要的运算,JDK中的String类就是一个例子。publicfinalclassString{/**Cachethehashcodeforthestring*/privateinthash;//Defaultto0publicinthashCode(){inth=hash;if(h==0){//computethevaluehash=h;//cachethevalue}returnh;}}在JDK中,String,theprimitivewrapperclasses,andBigIntegerandBigDecimal都是不变类。如果一个类是不变类,这个类是不是就不能有改变状态的方法呢?答案当然是否定的,String是一个不变类,仍然有replace,replaceAll这样的方法,而String仍然是一个不变类,那是因为在这些改变状态的方法中,每次都是新创建一个String对象。如果大家理解了不变类,那也就不难理解为什么在做String的concatenate时,应当用StringBuffer而不是用+的操作符。如何正确使用String呢?1)不要用new去创建String对象。如果使用new去创建String,那么每次都会创建一个新对象。publicstaticvoidmain(String[]args){StringA1=A;StringA2=A;//Itwon'tcreateanewobjectcheckInstance(A1,A2);//Result:TheyaresameinstancesStringB1=newString(A);//createanewobjectStringB2=newString(A);//creatanewobjectcheckInstance(B1,B2);//Result:Theyaredifferentinstances}privatestaticvoidcheckInstance(Stringa1,Stringa2){if(a1==a2){System.out.println(Theyaresameinstances);}else{System.out.println(Theyaredifferentinstances);}}2)应当用StringBuffer来做连接操作因为String是一个不变类,那么在做连接操作时,就会创建临时对象来保存中间的运算结果,而StringBuffer是一个mutableclass,这样就不需要创建临时的对象来保存结果,从而提高了性能。4.JAVAGarbageCollection(Vincent)垃圾分代回收算法(GenerationalCollecting)基于对对象生命周期分析后得出的垃圾回收算法。把对象分为年青代、年老代、持久代,对不同生命周期的对象使用不同的算法(上述方式中的一个)进行回收。现在的垃圾回收器(从J2SE1.2开始)都是使用此算法的。如上图所示,为Java堆中的各代分布。1.Young(年轻代)JVMspecification中的Heap的一部份年轻代分三个区。一个Eden区,两个Survivor区。大部分对象在Eden区中生成。当Eden区满