JavaSE阶段面试题汇总<二> 19、静态变量和实例变量的区别 在语法定义上的区别:静态变量前要加 static 关键字,而实例变量前则不加。 在程序运行时的区别:实例变量属于某个对象的属性,必须创建了实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过这个对象来使用,静态变量则可以直接使用类名来引用。 例如,对于下面的程序,无论创建多少个实例对象,永远都只分配了一个 staticVar 变量,并且每创建一个实例对象,这个 staticVar就会加 1;但是,每创建一个实例对象,就会分配一个 instanceVar,即可能分配多个 instanceVar,并且每个 instanceVar 的值都只自加1次。 public class VariantTest { public static int staticVar = 0; public int instanceVar = 0; public VariantTest() { staticVar++; instanceVar++; System.out.println(“staticVar=” + staticVar + ”,instanceVar=” + instanceVar); } } 20:是否可以从一个 c static 方法内部发出对static方法的调用 不可以。因为非 static 方法是要与对象关联在一起的,必须创建一个对象后,才可以在该对象上进行方法调用,而 static 方法调用时不需要创建对象,可以直接调用。也就是说,当一个 static 方法被调用时,可能还没有创建任何实例对象,如果从一个 static 方法中发出对非 static 方法的调用,那个非 static 方法是关联到哪个对象上的呢?这个逻辑无法立,所以,一个 static 方法内部发出对非 static 方法的调用。 21:请说出作用域 public ,private ,protected,以及不写时的区别,以及不写时的区别 修饰符 当前类 同包 子类 其他包 public √ √ √ √ protected √ √ √ × default √ √ × × private √ × × × 类的成员不写访问修饰时默认为 default。默认对于同一个包中的其他类相当于公开(public),对于不是同一个包中的其他类相当于私有(private)。受保护(protected)对子类相当于公开,对不是同一包中的没有父子关系的类相当于私有。Java 中,外部类的修饰符只能是 public 或默认,类的成员(包括内部类)的修饰符可以是以上四种 22:abstract class 和 和 interface 有什么区别? 含有 abstract 修饰符的 class 即为抽象类,abstract 类不能创建的实例对象。含有 abstract 方法的类必须定义为 abstract class,abstract class 类中的方法不必是抽象的。abstract class 类中定义抽象方法必须在具体(Concrete)子类中实现,所以,不能有抽象构造方法或抽象静态方法。如果的子类没有实现抽象父类中的所有抽象方法,那么子类也必须定义为 abstract 类型。接口(interface)可以说成是抽象类的一种特例,接口中的所有方法都必须是抽象的。接口中的方法定义默认为 public abstract 类型,接口中的成员变量类型默认为 public static final。 下面比较一下两者的语法区别: 1.抽象类可以有构造方法,接口中不能有构造方法。 2.抽象类中可以有普通成员变量,接口中没有普通成员变量 3.抽象类中可以包含非抽象的普通方法,接口中的所有方法必须都是抽象的,不能有非抽象的普通方法。 4. 抽象类中的抽象方法的访问类型可以是public,protected和(默认类型,虽然eclipse 下不报错,但应该也不行),但接口中的抽象方法只能是public 类型的,并且默认即为 public abstract 类型。 5. 抽象类中可以包含静态方法,接口中不能包含静态方法 6. 抽象类和接口中都可以包含静态成员变量,抽象类中的静态成员 变量的访问类型可以任意,但接口中定义的变量只能是 public static final 类型,并且默认即为 public static final 类型。 7. 一个类可以实现多个接口,但只能继承一个抽象类。 下面接着再说说两者在应用上的区别: 接口更多的是在系统架构设计方法发挥作用,主要用于定义模块之间的通信契约。而抽象类在代码实现方面发挥作用,可以实现代码的重用,例如,模板方法设计模式是抽象类的一个典型应用,假设某个项目的所有 Servlet 类都要用相同的方式进行权限判断、记录访问日志和处理异常,那么就可以定义一个抽象的基类,让所有的Servlet 都继承这个抽象基类,在抽象基类的 service 方法中完成权限判断、记录访问日志和处理异常的代码,在各个子类中只是完成各自的业务逻辑代码,伪代码如下: public abstract class BaseServlet extends HttpServlet { public final void service(HttpServletRequest request, HttpServletResponse response) throws IOExcetion,ServletException { 记录访问日志 进行权限判断 if(具有权限) { try { doService(request,response); } catch(Excetpion e) { 记录异常信息 } } } protected abstract void doService(HttpServletRequest request, HttpServletResponse response) throws IOExcetion,ServletException; //注意访问权限定义成 protected,显得既专业,又严谨,因为 它是专门给子类用的 } public class MyServlet1 extends BaseServlet { protected void doService(HttpServletRequest request, HttpServletResponse response) throws IOExcetion,ServletException { 本 Servlet 只处理的具体业务逻辑代码 } } 父类方法中间的某段代码不确定,留给子类干,就用模板方法设计模式。 23:final, finally, finalize 的区别 final 用于声明属性,方法和类,分别表示属性不可变,方法不可覆盖,类不可继承。内部类要访问局部变量,局部变量必须定义成 final 类型,例如,一段代码…… finally 是异常处理语句结构的一部分,表示总是执行。 finalize 是 Object 类的一个方法,在垃圾收集器执行的时候会调用被回收对象的此方法,可以覆盖此方法提供垃圾收集时的其他资源回收,例如关闭文件等。JVM 不保证此方法总被调用。 24:运行时异常与一般异常有何异同? 异常表示程序运行过程中可能出现的非正常状态,运行时异常表示虚拟机的通常操作中可能遇到的异常,是一种常见运行错误。java 编译器要求方法必须声明抛出可能发生的非运行时异常,但是并不要求必须声明抛出未被捕获的运行时异常。 25:JAVA 语言如何进行异常处理,关键字:throws,throw,try,catch,finally 分别代表什么意义?在 try块中可以抛出异常吗?块中可以抛出异常吗? throws 是获取异常 throw 是抛出异常 try 是将会发生异常的语句括起来,从而进行异常的处理, catch 是如果有异常就会执行他里面的语句, 而 finally 不论是否有异常都会进行执行的语句。 throw 和 throws 的详细区别如下: throw 是语句抛出一个异常。 语法:throw (异常对象); throw e; throws 是方法可能抛出异常的声明。(用在声明方法时,表示该方法可能要抛出异常) 语 法 : [( 修 饰 符 )]( 返 回 值 类 型 )( 方 法 名 )([ 参 数 列 表])[throws(异常类)]{......} public void doA(int a) throws Exception1,Exception3{......} 26:java 中有几种方法可以实现一个线程?用什么关键字修饰同步方法中有几种方法可以实现一个线程?用什么关键字修饰同步方法 第一种: new Thread(){}.start();这表示调用 Thread 子类对象的 run 方法,new Thread(){}表示一个 Thread 的匿名子类的实例对 象,子类加上 run 方法后的代码如下: new Thread(){ public void run(){ } }.start(); 第二种: new Thread(new Runnable(){}).start();这表示调用 Thread 对象接受的 Runnable 对象的 run 方法,new Runnable(){} 表示一个 Runnable 的匿名子类的实例对象,runnable 的子类加上 run 方法后的代码如下: new Thread(new Runnable(){ public void run(){ } }).start(); 从 java5 开始,还有如下一些线程池创建多线程的方式: ExecutorService pool = Executors.newFixedThreadPool(3) for(int i=0;i<10;i++) { pool.execute(new Runable(){public void run(){}}); } Executors.newCachedThreadPool().execute(new Runable(){public void run(){}}); Executors.newSingleThreadExecutor().execute(new Runable(){public void run(){}}); 有两种实现方法,分别使用 new Thread()和 newThread(runnable)形式,第一种直接调用 thread 的 run 方法,所以,我们往往使用 Thread 子类,即 new SubThread()。第二种调用 runnable 的 run 方法。有两种实现方法,分别是继承 Thread 类与实现 Runnable接口 用 synchronized 关键字修饰同步方法反对使用 stop(),是因为它不安全。它会解除由线程获取的所有锁定,而且如果对象处于一种不连贯状态,那么其他线程能在那种状态下检查和修改它们。结果很难检查出真正的问题所在。suspend()方法容易发生死锁。调用 suspend()的时候,目标线程会停下来,但却仍然持有在这之前获得的锁定。此时,其他任何线程都不能访问锁定的资源,除非被"挂起"的线程恢复运行。对任何线程来说,如果它们想恢复目标线程,同时又试图使用任何一个锁定的资源,就会造成死锁。所以不应该使用 suspend(),而应在自己的 Thread 类中置入一个标志,指出线程应该活动还是挂起。若标志指出线程应该挂起,便用 wait()命其进入等待状态。若标志指出线程应当恢复,则用一个 notify()重新启动线程。 27:sleep() 和 wait() 有什么区别? sleep 是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用 sleep 不会释放对象锁。 wait 是 Object 类的方法,对此对象调用 wait 方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出 notify 方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。 28:启动一个线程是用 run() 还是 start()? 启动一个线程是调用 start()方法,使线程就绪状态,以后可以被调度为运行状态,一个线程必须关联一些具体的执行代码,run()方法是该线程所关联的执行代码。 29:List 和 Map 区别? 一个是存储单列数据的集合,另一个是存储键和值这样的双列数据的集合,List 中存储的数据是有顺序,并且允许重复;Map中存储的数据是没有顺序的,其键是不能重复的,它的值是可以有重复的。 30:说出 ArrayList,Vector, LinkedList 的存储性能和特性 这样的题属于随意发挥题:这样的题比较考水平,两个方面的水平:一是要真正明白这些内容,二是要有较强的总结和表述能力。如果你明白,但表述不清楚,在别人那里则等同于不明白。首先,List 与 Set 具有相似性,它们都是单列元素的集合,所以,它们有一个功共同的父接口,叫 Collection。Set 里面不允许有重复的元素,所谓重复,即不能有两个相等(注意,不是仅仅是相同)的对象 ,即假设 Set 集合中有了一个 A 对象,现在我要向 Set 集合再存入一个 B 对象,但 B 对象与 A 对象 equals相等,则 B 对象存储不进去,所以,Set 集合的 add 方法有一个boolean 的返回值,当集合中没有某个元素,此时 add 方法可成功加入该元素时,则返回 true,当集合含有与某个元素 equals相等的元素时,此时 add 方法无法加入该元素,返回结果为 false。Set 取元素时,没法说取第几个,只能以 Iterator 接口取得所有的元素,再逐一遍历各个元素。 List 表示有先后顺序的集合, 注意,不是那种按年龄、按大小、按价格之类的排序。当我们多次调用 add(Obj e)方法时,每次加入的对象就像火车站买票有排队顺序一样,按先来后到的顺序排序。有时候,也可以插队,即调用 add(int index,Obj e)方法,就可以指定当前对象在集合中的存放位置。一个对象可以被反复存储进 List 中,每调用一次 add 方法,这个对象就被插入进集合中一次,其实,并不是把这个对象本身存储进了集合中,而是在集合中用一个索引变量指向这个对象,当这个对象被 add多次时,即相当于集合中有多个索引指向了这个对象,如图 x所示。List 除了可以以 Iterator 接口取得所有的元素,再逐一遍历各个元素之外,还可以调用 get(index i)来明确说明取第几个。 Map 与 List 和 Set 不同,它是双列的集合,其中有 put 方法,定义如下:put(obj key,obj value),每次存储时,要存储一对key/value,不能存储重复的 key,这个重复的规则也是按 equals比较相等。取则可以根据 key 获得相应的 value,即 get(Objectkey)返回值为 key 所对应的 value。另外,也可以获得所有的key 的结合,还可以获得所有的 value 的结合,还可以获得 key和 value 组合成的 Map.Entry 对象的集合。List 以特定次序来持有元素,可有重复元素。Set 无法拥有重复元素,内部排序。Map 保存 key-value 值,value 可多值。 HashSet 按照 hashcode 值的某种运算方式进行存储,而不是直接按 hashCode 值的大小进行存储。例如,"abc" ---> 78,"def"---> 62,"xyz" ---> 65 在 hashSet 中的存储顺序不是 62,65,78,这些问题感谢以前一个叫崔健的学员提出,最后通过查看源代码给他解释清楚,看本次培训学员当中有多少能看懂源码。LinkedHashSet 按插入的顺序存储,那被存储对象的 hashcode方法还有什么作用呢?学员想想!hashset 集合比较两个对象是否相等,首先看 hashcode 方法是否相等,然后看 equals 方法是否相等。new 两个 Student 插入到 HashSet 中,看 HashSet 的size,实现 hashcode 和 equals 方法后再看 size。 同一个对象可以在 Vector 中加入多次。往集合里面加元素,相当于集合里用一根绳子连接到了目标对象。往 HashSet 中却加不了多次的 31:Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用 == 还是 equals()? 它们有何区别它们有何区别? ? Set 里的元素是不能重复的,元素重复与否是使用 equals()方法进行判断的。 equals()和==方法决定引用值是否指向同一对象 equals()在类中被覆盖,为的是当两个分离的对象的内容和类型相配的话,返回真值 第二十八题:你所知道的集合类都有哪些?主要方法? 最常用的集合类是 List 和 Map。 List 的具体实现包括ArrayList 和 Vector,它们是可变大小的列表,比较适合构建、存储和操作任何类型对象的元素列表。 List 适用于按数值索引访问元素的情形。 Map 提供了一个更通用的元素存储方法。 Map 集合类用于存储元素对(称作"键"和"值"),其中每个键映射到一个值。 ArrayList/Vector Collection HashSet/TreeSetSet PropetiesHashTable Map Treemap/HashMap 我记的不是方法名,而是思想,我知道它们都有增删改查的方法,但这些方法的具体名称,我记得不是很清楚,对于set,大概的方法是 add,remove, contains;对于 map,大概的方法就是 put,remove,contains 等,因为,我只要在 eclispe下按点操作符,很自然的这些方法就出来了。我记住的一些思想就是 List 类会有 get(int index)这样的方法,因为它可以按顺序取元素,而 set 类中没有 get(int index)这样的方法。List和 set 都可以迭代出所有元素,迭代时先要得到一个 iterator对象,所以,set 和 list 类都有一个 iterator 方法,用于返回那个 iterator 对象。map 可以返回三个集合,一个是返回所有的 key 的集合,另外一个返回的是所有 value 的集合,再一个返回的 key 和 value 组合成的 EntrySet 对象的集合,map 也有 get方法,参数是 key,返回值是 key 对应的 value。 32:java 中有几种类型的流?K JDK 为每种类型的流提供了一些抽象类以供继承,请说出他们分别是哪些类? 字节流,字符流。字节流继承于 InputStream OutputStream, 字符流继承于 InputStreamReader OutputStreamWriter。在java.io 包中还有许多其他的流,主要是为了提高性能和使用方便。 33:字节流与字符流的区别 要把一片二进制数据数据逐一输出到某个设备中,或者从某个设备中逐一读取一片二进制数据,不管输入输出设备是什么,我们要用统一的方式来完成这些操作,用一种抽象的方式进行描述,这个抽象描述方式起名为 IO 流,对应的抽象类为 OutputStream和 InputStream ,不同的实现类就代表不同的输入和输出设备,它们都是针对字节进行操作的。 在应用中,经常要完全是字符的一段文本输出去或读进来,用字节流可以吗?计算机中的一切最终都是二进制的字节形式存在。对于“中国”这些字符,首先要得到其对应的字节,然后将字节写入到输出流。读取时,首先读到的是字节,可是我们要把它显示为字符,我们需要将字节转换成字符。由于这样的需求很广泛,人家专门提供了字符流的包装类。 底层设备永远只接受字节数据,有时候要写字符串到底层设备,需要将字符串转成字节再进行写入。字符流是字节流的包装,字符流则是直接接受字符串,它内部将串转成字节,再写入底层设备,这为我们向 IO 设别写入或读取字符串提供了一点点方便。字符向字节转换时,要注意编码的问题,因为字符串转成字节数组,其实是转成该字符的某种编码的字节形式,读取也是反之的道理。 34:什么是 a java 序列化,如何实现 a java 序列化?或者请解释序列化?或者请解释 e Serializable 接口的作用 我们有时候将一个 java 对象变成字节流的形式传出去或者从一个字节流中恢复成一个 java 对象,例如,要将 java 对象存储到硬盘或者传送给网络上的其他计算机,这个过程我们可以自己写代码去把一个 java 对象变成某个格式的字节流再传输,但是,jre 本身就提供了这种支持,我们可以调用 OutputStream 的writeObject 方法来做,如果要让 java 帮我们做,要被传输的对象必须实现 serializable 接口,这样,javac 编译时就会进行特殊处理,编译的类才可以被 writeObject 方法操作,这就是所谓的序列化。需要被序列化的类必须实现 Serializable 接口,该接口是一个 mini 接口,其中没有需要实现的方法,implementsSerializable 只是为了标注该对象是可被序列化的。 例如,在 web 开发中,如果对象被保存在了 Session 中,tomcat在重启时要把 Session 对象序列化到硬盘,这个对象就必须实现Serializable 接口。如果对象要经过分布式系统进行网络传输或通过 rmi 等远程调用,这就需要在网络上传输对象,被传输的对象就必须实现 Serializable 接口。 第三十二题:能不能自己写个类,也叫 java.lang.String ? 可以,但在应用的时候,需要用自己的类加载器去加载,否则,系统的类加载器永远只是去加载 jre.jar 包中的那个java.lang.String。由于在 tomcat 的 web 应用程序中,都是由webapp 自己的类加载器先自己加载 WEB-INF/classess 目录中的类,然后才委托上级的类加载器加载,如果我们在 tomcat 的 web应用程序中写一个 java.lang.String,这时候 Servlet 程序加载的就是我们自己写的 java.lang.String,但是这么干就会出很多潜在的问题,原来所有用了 java.lang.String 类的都将出现问题。 虽然 java 提供了 endorsed 技术,可以覆盖 jdk 中的某些类,具体做法是….。但是,能够被覆盖的类是有限制范围,反正不包括 java.lang 这样的包中的类。 (下面的例如主要是便于大家学习理解只用,不要作为答案的一部分,否则,人家怀疑是题目泄露了)例如,运行下面的程序: package cn.itcast.entity; public class String { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub System.out.println("string"); } } 报告的错误如下: java.lang.NoSuchMethodError: main Exception in thread "main" 黑马程序员 这是因为加载了 jre 自带的 java.lang.String,而该类中没有 main 方法。 35:一个 ".java"源文件中是否可以包括多个类(不是内部类)?有什么限制?源文件中是否可以包括多个类(不是内部类)?有什么限制? 可以有多个类,但只能有一个 public 的类,并且 public 的类名必须与文件名相一致。 36:Jvm 如何调优? 观察内存释放情况、集合类检查、对象树可查看堆空间大小分配(年轻代、年老代、持久代分配)提供即时的垃圾回收功能垃圾监控(长时间监控回收情况)查看堆内类、对象信息查看:数量、类型等对象引用情况查看有了堆信息查看方面的功能,我们一般可以顺利解决以下问题: --年老代年轻代大小划分是否合理 --内存泄漏 --垃圾回收算法设置是否合理 线程信息监控:系统线程数量。 线程状态监控:各个线程都处在什么样的状态下 Dump 线程详细信息:查看线程内部运行情况 死锁检查 内存泄漏检查 内存泄漏是比较常见的问题,而且解决方法也比较通用,这里可以重点说一下,而线程、热点方面的问题则是具体问题具体分析了。 内存泄漏一般可以理解为系统资源(各方面的资源,堆、栈、线程等)在错误使用的情况下,导致使用完毕的资源无法回收(或没有回收),从而导致新的资源分配请求无法完成,引起系统错误。 内存泄漏对系统危害比较大,因为他可以直接导致系统的崩溃。 需要区别一下,内存泄漏和系统超负荷两者是有区别的,虽然可能导致的最终结果是一样的。内存泄漏是用完的资源没有回收引起错误,而系统超负荷则是系统确实没有那么多资源可以分配了(其他的资源都在使用) 解决: 这种方式解决起来也比较容易,一般就是根据垃圾回收前后情况 对比,同时根据对象引用情况(常见的集合对象引用)分析,基 本都可以找到泄漏点。 持久代被占满 异常:java.lang.OutOfMemoryError: PermGen space 说明: Perm 空间被占满。无法为新的 class 分配存储空间而引发的异常。这个异常以前是没有的,但是在 Java 反射大量使用的今天这个异常比较常见了。主要原因就是大量动态反射生成的类不断被加载,最终导致 Perm 区被占满。 更可怕的是,不同的 classLoader 即便使用了相同的类,但是都会对其进行加载,相当于同一个东西,如果有 N 个classLoader 那么他将会被加载 N 次。因此,某些情况下,这个问题基本视为无解。当然,存在大量 classLoader 和大量反射类的情况其实也不多。 解决: 1. -XX:MaxPermSize=16m 2. 换用 JDK。比如 JRocket。 堆栈溢出 异常:java.lang.StackOverflowError 说明:这个就不多说了,一般就是递归没返回,或者循环调用造成 线程堆栈满 异常:Fatal: Stack size too small 说明:java 中一个线程的空间大小是有限制的。JDK5.0 以后这个值是 1M。与这个线程相关的数据将会保存在其中。但是当线程空间满了以后,将会出现上面异常。解决:增加线程栈大小。-Xss2m。但这个配置无法解决根本问题,还要看代码部分是否有造成泄漏的部分。 系统内存被占满 异常:java.lang.OutOfMemoryError: unable to create new native thread 说明: 这个异常是由于操作系统没有足够的资源来产生这个线程造成的。系统创建线程时,除了要在 Java 堆中分配内存外,操作系统本身也需要分配资源来创建线程。因此,当线程数量大到一定程度以后,堆中或许还有空间,但是操作系统分配不出资源来了,就出现这个异常了。 分配给 Java 虚拟机的内存愈多,系统剩余的资源就越少,因此,当系统内存固定时,分配给 Java 虚拟机的内存越多,那么,系统总共能够产生的线程也就越少,两者成反比的关系。同时,可以通过修改-Xss 来减少分配给单个线程的空间,也可以增加系统总共内生产的线程数。 解决: 1. 重新设计系统减少线程数量。 2. 线程数量不能减少的情况下,通过-Xss 减小单个线程大小。以便能生产更多的线程。 37:Jvm 如何加载类?如何分配空间。 指的是将 class 文件的二进制数据读入到运行时数据区(JVM 在内存中划分的)中,并在方法区内创建一个 class 对象,JVM 运行起来时就给内存划分空间,这块空间就称为运行时数据区。 运行时数据区被划分为以下几块内容 1.栈: 每一个线程运行起来的时候就会对应一个栈(线程栈),栈中存放的数据被当前线程所独享(不会产生资源共享情况,所以线程是安全的)。而栈当中存放的是栈帧,当线程调用方法时,就是形成一个栈帧,并将这个栈帧进行压栈操作。方法执行完后,进行出栈操作。这个栈帧里面包括(局部变量,操作数栈,指向当前方法对应类的常量池引用,方法返回地址等信息)。 2.本地方法栈: 本地方法栈的机制和栈的相似,区别在于,栈运行的是 Java实现的方法,而本地方法栈运行的是本地方法。本地方法指的是 JVM 需要调用非 Java语言所实现的方法, 例如 C 语言。在 JVM 规范中,没有强化性要求实现方一定要划分出本地方法栈(例如:HotSpot 虚拟机将本地方法栈和栈合二为一)和具体实现(不同的操作系统,对 JVM规范的具体实现都不一样)。 3.程序计数器: 程序计数器也可以称为 PC 寄存器(通俗讲就是 指令缓存)。它主要用于缓存当前程序下一条指令的指令地址,CPU 根据这个地址找到将要执行的指令。这个寄存器是 JVM内部实现的,不是物理概念上的计数器,不过和 JVM 的实现逻辑一样。 4.堆: 堆内存主要存放创建的对象和数组。堆内存在 JVM 中是唯一的,能被多个线程所共享。堆里面的每一个对象都存放着实例的实例变量。堆内存的对象没有被引用,会自动被 Java垃圾回收机制回收。 当在方法中定义了局部变量,如果这个变量是基本数据类型,那么这个变量的值就直接存放在栈中;如果这个变量是引用数据类型,那么变量值就存放在堆内存中,而栈中存放的是指向堆中的引用地址。 5.方法区: 方法区在 JVM 也是一个非常重要的一块内存区域,和堆一样,可以被多个线程多共享。主要存放每一个加载 class 的信息。class 信息主要包含魔数(确定是否是一个 class 文件),常量池,访问标志(当前的类是普通类还是接口,是否是抽象类,是否被 public 修饰,是否使用了 final修饰等描述信息......),字段表集合信息(使用什么访问修饰符,是实例变量还是静态变量,是否使用了 final 修饰等描述信息.....),方法表集合信息(使用什么访问修饰符,是否静态方法,是否使用了 final 修饰,是否使用了 synchronized 修饰,是否是native 方法......)等内容。当一个类加载器加载了一个类的时候,会根据这个class文件创建一个class对象,class 对象就包含了上述的信息。后续要创建这个类的实例,都根据这个 class 对象创建出来的。 6.常量池: 常量池是方法区中的一部分,存放 class 对象中最重要的资源。JVM 为每一个 class 对象都维护一个常量池。 38:HashMap 、HashSet 、HashTable 的区别? 区别一:继承的父类不同 Hashtable 继承自 Dictionary 类,而 HashMap 继承自AbstractMap 类。但二者都实现了 Map 接口。 区别二:线程安全性不同 Hashtable 中的方法是 Synchronize 的,而 HashMap 中的方法在缺省情况下是非 Synchronize 的。 区别三:是否提供 contains 方法 HashMap 把 Hashtable 的 contains 方法去掉了,改成containsValue 和 containsKey,因为 contains 方法容易让人引起误解。 Hashtable 则保留了 contains,containsValue 和 containsKey三个方法,其中 contains 和 containsValue 功能相同。 区别四:**key 和 value 是否允许 null 值 (面试比较喜欢问) 其中 key 和 value 都是对象,并且不能包含重复 key,但可以包含重复的 value。 Hashtable 中,key 和 value 都不允许出现 null 值。 HashMap 中,null 可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为 null。当 get()方法返回null 值时,可能是 HashMap 中没有该键,也可能使该键所对应的值为 null。因此,在 HashMap 中不能由 get()方法来 判 断 HashMap 中 是 否 存 在 某 个 键 , 而 应 该 用containsKey()方法来判断。 区别五:哈希值的计算方法不同 Hashtable 直接使用的是对象的 hashCode,而 HashMap 则是在对象的 hashCode 的基础上还进行了一些变化。 区别六:内部实现使用的数组初始化和扩容方式不同 内存初始大小不同,HashTable 初始大小是 11,而 HashMap 初始大小是16 39:Hashcode 和 和 equals equals:Object 类中默认的实现方式是 : return this == obj 。那就是说,只有 this 和 obj 引用同一个对象,才会返回 true。而我们往往需要用 equals 来判断 2 个对象是否等价,而非验证他们的唯一性。这样我们在实现自己的类时,就要重写 equals按照约定,equals 要满足以下规则。 自反性: x.equals(x) 一定是 true 对 null: x.equals(null) 一定是 false 对称性: x.equals(y) 和 y.equals(x)结果一致 传递性: a 和 b equals , b 和 c equals,那么 a 和 c也一定 equals。 一致性: 在某个运行时期间,2 个对象的状态的改变不会不影响 equals 的决策结果,那么,在这个运行时期间,无论调用多少次 equals,都返回相同的结果。 Hashcode:这个方法返回对象的散列码,返回值是 int 类型的散列码。对象的散列码是为了更好的支持基于哈希机制的 Java 集合类,例如 Hashtable, HashMap, HashSet 等。 关于 hashCode 方法,一致的约定是: 重写了 euqls 方法的对象必须同时重写 hashCode()方法。 如果 2 个对象通过 equals 调用后返回是 true,那么这个 2 个对象的 hashCode 方法也必须返回同样的 int 型散列码 如果 2 个对象通过 equals 返回 false,他们的 hashCode 返回的值 允许相同。(然而,程序员必须意识到,hashCode 返回独一无二的散列码,会让存储这个对象的 hashtables 更好地工作。) 在上面的例子中,Test 类对象有 2 个字段,num 和 data,这 2个字段代表了对象的状态,他们也用在 equals 方法中作为评判的依据。那么, 在 hashCode 方法中,这 2 个字段也要参与 hash值的运算,作为 hash 运算的中间参数。这点很关键,这是为了遵守:2 个对象 equals,那么 hashCode 一定相同规则。也是说,参与 equals 函数的字段,也必须都参与 hashCode 的计算。 合乎情理的是:同一个类中的不同对象返回不同的散列码。典型的方式就是根据对象的地址来转换为此对象的散列码,但是这种方式对于 Java 来说并不是唯一的要求的的实现方式。通常也不是最好的实现方式。 相比 于 equals 公认实现约定,hashCode 的公约要求是很容易理解的。有 2 个重点是 hashCode 方法必须遵守的。约定的第 3点,其实就是第 2 点的细化,下面我们就来看看对 hashCode 方法的一致约定要求。 第一:在某个运行时期间,只要对象的(字段的)变化不会影响 equals 方法的决策结果,那么,在这个期间,无论调用多少次 hashCode,都必须返回同一个散列码。 第二:通过 equals 调用返回 true 的 2 个对象的 hashCode一定一样。 第三:通过 equasl 返回 false 的 2 个对象的散列码不需要不同,也就是他们的 hashCode 方法的返回值允许出现相同的情况。 总结一句话:等价的(调用 equals 返回 true)对象必须产生相同的散列码。不等价的对象,不要求产生的散列码不相同。 40:GC 是什么? 为什么要有 GC? GC 是垃圾收集的意思(Gabage Collection),内存处理是编程人员容易出现问题的地方,忘记或者错误的内存回收会导致程序或系统的不稳定甚至崩溃,Java 提供的 GC 功能可以自动监测对象是否超过作用域从而达到自动回收内存的目的,Java 语言没有提供释放已分配内存的显示操作方法。
|