栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

java的东西

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

java的东西

● 请你说一下多态 多态就是指程序中定义的引用变量所指向的具体类型和通过该引用变量发出的方法调用在编程时并不确定,而是在程序运行期间才确定,即一个引用变量倒底会指向哪个类的实例对象,该引用变量发出的方法调用到底是哪个类中实现的方法,必须在由程序运行期间才能决定。因为在程序运行时才确定具体的类,这样,不用修改源程序代码,就可以让引用变量绑定到各种不同的类实现上,从而导致该引用调用的具体方法随之改变,即不修改程序代码就可以改变程序运行时所绑定的具体代码,让程序可以选择多个运行状态,这就是多态性。 Java实现多态有三个必要条件:继承、重写、向上转型。 继承:在多态中必须存在有继承关系的子类和父类。 重写:子类对父类中某些方法进行重新定义,在调用这些方法时就会调用子类的方法。 向上转型:在多态中需要将子类的引用赋给父类对象,只有这样该引用才能够具备技能调用父类的方法和子类的方法。 Java中有两种形式可以实现多态,继承和接口: 基于继承的实现机制主要表现在父类和继承该父类的一个或多个子类对某些方法的重写,多个子类对同一方法的重写可以表现出不同的行为。 基于接口的多态中,指向接口的引用必须是指定这实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。

● 请问Java中接口与抽象类是否相同? 不同, 抽象类是用来捕捉子类的通用特性的。它不能被实例化,只能被用作子类的超类。抽象类是被用来创建继承层级里子类的模板。 接口是抽象方法的集合。如果一个类实现了某个接口,那么它就继承了这个接口的抽象方法。 抽象类和接口的对比:
参数 抽象类 接口
默认的方法实现 它可以有默认的方法实现 接口完全是抽象的,不存在方法的实现
实现 子类使用extends关键字来继承抽象类。如果子类不是抽象类的话,它需要提供抽象类中所有声明的方法的实现。 子类使用关键字implements来实现接口。它需要提供接口中所有声明的方法的实现
构造器 抽象类可以有构造器 接口不能有构造器
与正常Java类的区别 除了你不能实例化抽象类之外,它和普通Java类没有任何区别 接口是完全不同的类型
访问修饰符 抽象方法可以有public、protected和default这些修饰符 接口方法默认修饰符是public,不可以使用其它修饰符。
main方法 抽象方法可以有main方法并且可以运行 接口没有main方法。
多继承 抽象方法可以继承一个类和实现多个接口 接口只可以继承一个或多个其它接口
速度 它比接口速度要快 接口是稍微有点慢的,因为它需要时间去寻找在类中实现的方法。
添加新方法 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。
什么时候使用抽象类和接口: 如果拥有一些方法并且想让它们中的一些有默认实现,使用抽象类。 如果想实现多重继承,必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。 如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。

● 请你说一下java jvm的内存机制 Java内存区域划分 正在上传…重新上传取消转存失败重新上传取消 1.程序计数器: 可以看做是当前线程所执行的字节码的行号指示器。在JVM的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。 每条线程都有一个独立的程序计数器,所以程序计数器是线程私有的内存区域。 如果线程执行的是一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果线程执行的是一个Native方法,计数器的值为空。 Java虚拟机规范中唯一一个没有规定任何OutOfMemoryError情况的区域。 2.Java虚拟机栈: 描述Java方法执行的内存模型,每个方法执行的同时会创建一个栈帧,栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 正在上传…重新上传取消转存失败重新上传取消 Java虚拟机栈是线程私有的,它的生命周期与线程相同。 局部变量表存放了编译时期可知的各种基本数据类型和对象引用。局部变量表所需的内存空间在编译时期完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。 Java虚拟机规范对这个区域规定了两种异常情况: 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常; 如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常; 3.本地方法栈: 本地方法栈与虚拟机栈的区别:虚拟机栈为虚拟机执行Java方法服务(也就是字节码),而本地方法栈为虚拟机使用到的Native方法服务。 Java虚拟机规范对这个区域规定了两种异常情况:StackOverflowError 和 OutOfMemoryError异常。 4.Java堆: Java堆是被所有的线程共享的一块内存区域,在虚拟机启动时创建。Java堆的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 Java堆是垃圾回收器管理的主要区域,从内存回收的角度看,由于现在收集器基本都采用分代收集算法,所以Java堆可以细分为:新生代、老生代;从内存分配的角度看,线程共享的Java堆可能划分出多个线程私有的分配缓冲区(TLAB)。 Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。 Java虚拟机规范规定,如果在堆上没有内存完成实例分配,并且堆上也无法再扩展时,将会抛出OutOfMemoryError异常。 Java堆内存的OOM异常: 内存泄露:指程序中一些对象不会被GC所回收,它始终占用内存,即被分配的对象引用链可达但已无用。 内存溢出:程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。 5.方法区: 被所有的线程共享的一块内存区域。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 不需要连续的内存和可以选择固定大小或者可扩展之外,还可以选择不实现垃圾回收。 Java虚拟机规范规定,当方法区无法满足内存分配的需求时,将抛出OutOfMemoryError异常。

● 请你说一说垃圾收集机制

参考回答:Collection)是Java虚拟机(JVM)垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象占据的内存空间的一种机制。 引用:如果Reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。 (1)强引用(Strong Reference):如“Object obj = new Object()”,这类引用是Java程序中最普遍的。只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象。 (2)软引用(Soft Reference):它用来描述一些可能还有用,但并非必须的对象。在系统内存不够用时,这类引用关联的对象将被垃圾收集器回收。JDK1.2之后提供了SoftReference类来实现软引用。 (3)弱引用(Weak Reference):它也是用来描述非须对象的,但它的强度比软引用更弱些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK1.2之后,提供了WeakReference类来实现弱引用。 (4)虚引用(Phantom Reference):最弱的一种引用关系,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的是希望能在这个对象被收集器回收时收到一个系统通知。JDK1.2之后提供了PhantomReference类来实现虚引用。 垃圾:无任何对象引用的对象。 判断对象是否是垃圾的算法: 引用计数算法(Reference Counting Collector)、根搜索算法(Tracing Collector): 回收:清理“垃圾”占用的内存空间而非对象本身。 Tracing算法(Tracing Collector) 标记—清除算法:分为“标记”和“清除”两个阶段:首先标记出所需回收的对象,在标记完成后统一回收掉所有被标记的对象,它的标记过程其实就是前面的根搜索算法中判定垃圾对象的标记过程。 Compacting算法(Compacting Collector)标记—整理算法:标记的过程与标记—清除算法中的标记过程一样,但对标记后出的垃圾对象的处理情况有所不同,它不是直接对可回收对象进行清理,而是让所有的对象都向一端移动,然后直接清理掉端边界以外的内存。在基于Compacting算法的收集器的实现中,一般增加句柄和句柄表。 Copying算法(Copying Collector):将内存按容量分为大小相等的两块,每次只使用其中的一块(对象面),当这一块的内存用完了,就将还存活着的对象复制到另外一块内存上面(空闲面),然后再把已使用过的内存空间一次清理掉。 Adaptive算法(Adaptive Collector):监控当前堆的使用情况,并将选择适当算法的垃圾收集器。 发生地点:一般发生在堆内存中,因为大部分的对象都储存在堆内存中。 (堆内存为了配合垃圾回收有什么不同区域划分,各区域有什么不同?) Java的堆内存基于Generation算法(Generational Collector)划分为新生代、年老代和持久代。新生代又被进一步划分为Eden和Survivor区,最后Survivor由FromSpace(Survivor0)和ToSpace(Survivor1)组成。所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。分代收集基于这样一个事实:不同的对象的生命周期是不一样的。因此,可以将不同生命周期的对象分代,不同的代采取不同的回收算法进行垃圾回收(GC),以便提高回收效率。 按执行机制划分Java有四种类型的垃圾回收器: (1)串行垃圾回收器(Serial Garbage Collector) (2)并行垃圾回收器(Parallel Garbage Collector) (3)并发标记扫描垃圾回收器(CMS Garbage Collector) (4)G1垃圾回收器(G1 Garbage Collector) 发生时间:程序空闲时间不定时回收。

● 请你说一下OOM可能发生在哪,怎么查看,怎么调优 除了程序计数器不会抛出OOM外,其他各个内存区域都可能会抛出OOM。 最常见的OOM情况有以下三种: •    java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。 •    java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。 •    java.lang.StackOverflowError ------> 不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。 OOM分析--heapdump 要dump堆的内存镜像,可以采用如下两种方式: •    设置JVM参数-XX:+HeapDumpOnOutOfMemoryError,设定当发生OOM时自动dump出堆信息。不过该方法需要JDK5以上版本。 •    使用JDK自带的jmap命令。"jmap -dump:format=b,file=heap.bin "   其中pid可以通过jps获取。 dump堆内存信息后,需要对dump出的文件进行分析,从而找到OOM的原因。常用的工具有: •    mat: eclipse memory analyzer, 基于eclipse RCP的内存分析工具。 •    jhat:JDK自带的java heap analyze tool,可以将堆中的对象以html的形式显示出来,包括对象的数量,大小等等,并支持对象查询语言OQL,分析相关的应用后,可以通过http://localhost:7000来访问分析结果。不推荐使用,因为在实际的排查过程中,一般是先在生产环境 dump出文件来,然后拉到自己的开发机器上分析,所以,不如采用高级的分析工具比如前面的mat来的高效。

● 请你说一下类加载 1、什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。 加载.class文件的方式 – 从本地系统中直接加载 – 通过网络下载.class文件 – 从zip,jar等归档文件中加载.class文件 – 从专有数据库中提取.class文件 – 将Java源文件动态编译为.class文件 2、类的生命周期 类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。 A.加载:查找并加载类的二进制数据 加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情: 1、通过一个类的全限定名来获取其定义的二进制字节流。 2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。 相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。 B.连接 – 验证:确保被加载的类的正确性 验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作: •    文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。 •    元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。 •    字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。 •    符号引用验证:确保解析动作能正确执行。 验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。 – 准备:为类的静态变量分配内存,并将其初始化为默认值 准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。 – 解析:把类中的符号引用转换为直接引用 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。 C.初始化 初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式: ①声明类变量是指定初始值 ②使用静态代码块为类变量指定初始值 JVM初始化步骤 1、假如这个类还没有被加载和连接,则程序先加载并连接该类 2、假如该类的直接父类还没有被初始化,则先初始化其直接父类 3、假如类中有初始化语句,则系统依次执行这些初始化语句 类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种: – 创建类的实例,也就是new的方式 – 访问某个类或接口的静态变量,或者对该静态变量赋值 – 调用类的静态方法 – 反射(如Class.forName(“com.shengsiyuan.Test”)) – 初始化某个类的子类,则其父类也会被初始化 – Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类 D.结束生命周期 在如下几种情况下,Java虚拟机将结束生命周期 – 执行了System.exit()方法 – 程序正常执行结束 – 程序在执行过程中遇到了异常或错误而异常终止 – 由于操作系统出现错误而导致Java虚拟机进程终止 3、类的加载 类加载有三种方式: •    命令行启动应用时候由JVM初始化加载 •    通过Class.forName()方法动态加载 •    通过ClassLoader.loadClass()方法动态加载 4、双亲委派模型 双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。 双亲委派机制: •    当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。 •    当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。 •    如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载; •    若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

● 请你说一下死锁的原因,以及如何打破,如何查看死锁进程状态 1、死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。死锁的四个必要条件: •    互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。 •    请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。 •    非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。 •    循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。 java中产生死锁可能性的最根本原因是:1)是多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖的闭环;2)默认的锁申请操作是阻塞的。 如,线程在获得一个锁L1的情况下再去申请另外一个锁L2,也就是锁L1想要包含了锁L2,在获得了锁L1,并且没有释放锁L1的情况下,又去申请获得锁L2,这个是产生死锁的最根本原因。 2、避免死锁: •    方案一:破坏死锁的循环等待条件。 •    方法二:破坏死锁的请求与保持条件,使用lock的特性,为获取锁操作设置超时时间。这样不会死锁(至少不会无尽的死锁) •    方法三:设置一个条件遍历与一个锁关联。该方法只用一把锁,没有chopstick类,将竞争从对筷子的争夺转换成了对状态的判断。仅当左右邻座都没有进餐时才可以进餐。提升了并发度。 3、linux中查看死锁进程状态 使用pstack 和 gdb 工具对死锁程序进行分析 pstack 进程号 查看各个线程的堆栈信息 当进程吊死的时候,多次使用,死锁的线程将一直处于等锁的状态,确定某些线程一直没有变化,一直处于等锁的状态。那么这些线程很可能是死锁了。如果怀疑哪些线程发生死锁了,可以采用gdb 进一步attach线程并进行分析。 执行命令gdb attach 进程号,进入gdb调试终端 运行:(gdb) info thread

● 请你说一下内存泄漏 当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。内存泄漏是造成应用程序OOM的主要原因之一。我们知道Android系统为每个应用程序分配的内存是有限的,而当一个应用中产生的内存泄漏比较多时,这就难免会导致应用所需要的内存超过系统分配的内存限额,这就造成了内存溢出从而导致应用Crash。 常见的内存泄漏: 1、单例造成的内存泄漏 由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。 2、非静态内部类创建静态实例造成的内存泄漏 非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,从而导致Activity的内存资源不能被正常回收。 3、Handler造成的内存泄漏 4、线程造成的内存泄漏 如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。 5、资源未关闭造成的内存泄漏 对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。 6、使用ListView时造成的内存泄漏 7、集合容器中的内存泄露 8、WebView造成的泄露 避免内存泄漏: 1、在涉及使用Context时,对于生命周期比Activity长的对象应该使用Application的Context。 2、对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏。 3、对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null。 4、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。 5、对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏: 1)将内部类改为静态内部类 2)静态内部类中使用弱引用来引用外部类的成员变量

● 请你说一说class和interface的区别 1、接口类似于类,但接口的成员都没有执行方式,它只是方法、属性、事件和索引的组合而已,并且也只能包含这四种成员;类除了这四种成员之外还可以有别的成员(如字段)。 2、不能实例化一个接口,接口只包括成员的签名;而类可以实例化(abstract类除外)。 3、接口没有构造函数,类有构造函数。 4、接口不能进行运算符的重载,类可以进行运算符重载。 5、接口的成员没有任何修饰符,其成员总是公共的,而类的成员则可以有修饰符(如:虚拟或者静态)。 6、派生于接口的类必须实现接口中所有成员的执行方式,而从类派生则不然。

● 请你说一说强引用和弱引用 强引用: 强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。如下: Object o=new Object(); // 强引用 当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。如果不使用时,要通过如下方式来弱化引用,如下: o=null; // 帮助垃圾收集器回收此对象 显式地设置o为null,或超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于gc的算法。 举例: public void test(){ Object o=new Object(); } 在一个方法的内部有一个强引用,这个引用保存在栈中,而真正的引用内容(Object)保存在堆中。当这个方法运行完成后就会退出方法栈,则引用内容的引用不存在,这个Object会被回收。但是如果这个o是全局的变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收。 弱引用: 弱引用也是用来描述非必需对象的,只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。提供WeakReference类实现弱引用。 例子: String str=new String("abc"); WeakReference abcWeakRef = new WeakReference(str); str=null; 当垃圾回收器进行扫描回收时等价于: str = null; System.gc(); 如果这个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用Weak Reference 来记住此对象。 String abc = abcWeakRef.get(); //该代码会让str再次变为一个强引用: 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候你就是用弱引用。这个引用不会在对象的垃圾回收判断中产生任何附加的影响。

● 手写代码:求n以内的最大质数

● 请你说一下java里内存泄漏和溢出的区别 1、内存泄漏memory leak :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。 2、内存溢出 out of memory :指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。

● 你知道java里面的内存管理机制吗,比如创建和回收是怎么管理的? java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域存储不同类型的数据,这些区域的内存分配和销毁的时间也不同,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范(第2版)》的规定,Java虚拟机管理的内存包括五个运行时数据区域, 1、方法区 方法区(Method Area)是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息(包括类的名称、方法信息、成员变量信息)、常量、静态变量、以及编译器编译后的代码等数据 2、虚拟机栈和本地方法栈 虚拟机栈是线程私有的内存空间,每个线程都有一个线程栈,本地方法栈也是线程私有的内存空间,本地方法栈与Java栈所发挥的作用是非常相似的,它们之间的区别不过是Java栈执行Java方法,本地方法栈执行的是本地方法,有的虚拟机直接把本地方法栈和虚拟机栈合二为一。 3、堆 Java堆是Java虚拟机所管理的内存中最大的一块,在虚拟机启动时创建,此内存区域的目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(TLAB)。Java堆可以处于物理上不连续的内存空间,只要逻辑上连续即可,在实现上,既可以实现固定大小的,也可以是扩展的。如果堆中没有足够的内存分配给实例,并且堆也无法再拓展时,将会抛出OutOfMemeryError异常。 Java内存回收: 对于栈空间,当方法调用结束后,基本类型变量,引用类型变量,形参占据的空寂会被自动释放,但引用类型指向的对象在堆中,堆中的无用内存有垃圾回收线程回收,GC线程优先级最低,只有当没有工作线程存在时GC线程才会执行,或者堆空间不足时会自动出发GC线程工作,

● 请你说一下Java里integer和int的区别,以及如何比较相等 Integer和int的区别: 1、integer是int的包装类,int是Java的一种基本数据结构 2、integer变量必须实例化后才能使用,int变量不需要 3、integer实际是对象的引用,int是直接存储数据值 4、integer的默认值是null,int的默认值是0 如何比较相等,首先要明白equals和==的区别 Equals通常用来比较两个对象的内容是否相等,==用来比较两个对象的地址是否相等,Object类中的equals方法定义为判断两个对象的地址是否相等(可以理解成是否是同一个对象),地址相等则认为是对象相等。这也就意味着,我们新建的所有类如果没有复写equals方法,那么判断两个对象是否相等时就等同于“==”,也就是两个对象的地址是否相等。但在我们的实际开发中,通常会认为两个对象的内容相等时,则两个对象相等,equals返回true。对象内容不同,则返回false。 所以可以总结为两种情况 1、类未复写equals方法,则使用equals方法比较两个对象时,相当于==比较,即两个对象的地址是否相等。地址相等,返回true,地址不相等,返回false。 2、类复写equals方法,比较两个对象时,则走复写之后的判断方式。通常,我们会将equals复写成:当两个对象内容相同时,则equals返回true,内容不同时,返回false。

● 请你回答一下protected,public,private的区别

参考回答: 作用域     当前类  同一package  子孙类  其它package public       √                     √              √            √ protected    √                √                 √                  × friendly     √                     √                  ×                 × private    √               ×                  ×                 ×

● 请你说一下抽象类和接口的区别 从语法层面来说, 1、抽象类可以提供成员方法的实现细节,而接口中只能存在抽象方法 2、抽象类中成员变量可以是多种类型,接口中成员变量必须用public,static,final修饰 3、一个类只能继承一个抽象类,但可以实现多个接口 4、抽象类中允许含有静态代码块和静态方法,接口不能 从设计层面而言 1.抽象类是对整一个类的属性,行为等方面进行抽象,而接口则是对行为抽象。就好比飞机和鸟,抽象类抽象出的是飞行物类。而接口则是抽闲出飞行方法。 2.抽象类是一个模板式的设计,当在开发过程中出现需求更改的情况,只需要更改抽象类而不需要更改它的子类。接口是一种辐射性设计,当接口的内容发生改变时,需要同时对实现它的子类进行相应的修改。 3.抽象类可以类比为模板,而接口可以类比为协议

● 请你说一下List和ArrayList的区别,以及arrayList和HashSet区别

参考回答: List:是一个有序的集合,可以包含重复的元素。提供了按索引访问的方式。它继承 Collection。 List有两个重要的实现类:ArrayList 和 LinkedList ArrayList:我们可以将其看作是能够自动增长容量的数组。 利用ArrayList的toArray()返回一个数组。 Arrays.asList()返回一个列表。 1.ArrayList底层采用数组实现,当使用不带参数的构造方法生成ArrayList对象时,实际上会在底层生成一个长度为10的Object类型数组 2.如果增加的元素个数超过了10个,那么ArrayList底层会新生成一个数组,长度为原数组的1.5倍+1,然后将原数组的内容复制到新数组当中,并且后续增加的内容都会放到新数组当中。当新数组无法容纳增加的元素时,重复该过程。 3.对于ArrayList元素的删除操作,需要将被删除元素的后续元素向前移动,代价比较高。 4.集合当中只能放置对象的引用,无法放置原生数据类型,我们需要使用原生数据类型的包装类才能加入到集合当中。 5.集合当中放置的都是Object类型,因此取出来的也是Object类型,那么必须要使用强制类型转换将其转换为真正的类型(放置进去的类型)

● 请你回答一下Java的内存结构是什么,全局变量,临时变量,静态变量分别存在哪里,堆分为哪几块,比如说新生代老生代,那么新生代又分为什么

参考回答: 代码区:是编译器生成的一个exe区段,存放函数体的二进制代码 栈区:存放函数的参数,局部变量的值等,其操作方式类似于数据结构中的栈,const局部变量也是放在栈里 堆区:就是malloc和new之类的内存所在区段,一般由程序员分配释放,分配方式类似于链表 静态数据区:是编译器生成的一个exe区段,初始和未初始化的全局变量和局部变量都放在这里, 常量区:是编译器生成的一个exe区段,const全局变量也放在常量区。 全局变量,临时变量,静态变量分别存在哪里 局部变量保存在栈中,全局变量和静态变量存储在静态数据区。 堆分为哪几块,比如说新生代老生代,那么新生代又分为什么? java垃圾收集管理器的主要区域,因此很多时候被称为“GC堆”。 分为新生代和老年代; 新生代分为:Eden和Survivor。

● 手写代码:给出一个int类型123,写一个函数,返回反转的值321 void my_itoa(int x,char *s,char radix) {  char zm[37]="0123456789abcdefghijklmnopqrstuvwxyz";  int i=0;  int sum=x;  while(sum>0)  {  s[i++]=zm[sum%radix];  sum/=radix;  }  }  int atoi_my( char *str)  {  int s=0;  while(*str>='0'&&*str<='9')  {  s=s*10+*str-'0';  str++;  if(s<0)  {  s=2147483647;  break;  }  }  return s; //  }

● 请你说一说接口有什么限制 1、变量会被隐式地指定为public static final变量,并且只能是public static final变量,用private修饰会报编译错误 2、方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误) 3、接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法

● 请你说一说内存溢出和内存泄漏是怎么回事 内存溢出out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。 内存泄露memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。 内存泄漏可以分为四类: 1、常发性内存泄漏,发生内存泄漏的代码会被多次执行到,每次执行都会导致内存泄漏 2、偶发性内存泄漏:发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生, 3、一次性内存泄漏,发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。 4、隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。 从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。 内存溢出常见原因:

1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据; 2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收; 3.代码中存在死循环或循环产生过多重复的对象实体; 4.使用的第三方软件中的BUG; 5.启动参数内存值设定的过小 解决方案: 1、修改JVM参数,直接增加内存 2、检查错误日志,查看内存溢出错误前是否有其他异常错误 3、对代码进行走查分析,找出可能发生内存溢出的位置

● 请你介绍一下HashMap,HashTable,ConcurrentHashMap

参考回答: 1、HashTable与HashMap (1)HashTable和HashMap都实现了Map接口,但是HashTable的实现是基于Dictionary抽象类。 (2)在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,既可以表示HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。而在HashTable中,无论是key还是value都不能为null。 (3)HashTable是线程安全的,它的方法是同步了的,可以直接用在多线程环境中。HashMap则不是线程安全的。在多线程环境中,需要手动实现同步机制。 2、更好的选择:ConcurrentHashMap Java 5中新增了ConcurrentMap接口和它的实现类ConcurrentHashMap。 ConcurrentHashMap提供了和HashTable以及SynchronizedMap中所不同的锁机制。HashTable中采用的锁机制是一次锁住整个hash表,从而同一时刻只能有一个线程对其进行操作;而ConcurrentHashMap中则是一次锁住一个桶。ConcurrentHashMap默认将hash表分为16个桶,诸如get,put,remove等常用操作只锁住当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。 上面说到的16个线程指的是写线程,而读操作大部分时候都不需要用到锁。只有在size等操作时才需要锁住整个hash表。 在迭代方面,ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是,在改变时new新的数据从而不影响原有的数据,iterator完成后再将头指针替换为新的数据,这样iterator可以使用原来老的数据,而写线程也可以并发的完成改变。

● 请你说一下Hashset有什么特性,以及hashset判断存入的对象是否重复是如何比较的 HashSet是Set接口的实现类,因此,HashSet中的元素也是不能重复的。HashCode判断元素重复的标准时,首先计算新添加元素的hashCode值,当不重复是,则直接加入到该集合中,若发生重复,也称发生了碰撞,则进一步调用equals判断元素是否在逻辑上相同。

● 请你说一下Java的反射,你目前主要用他做什么,以及Java的泛型,他的主要作用是什么 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。Java反射可以用来获取一个class对象或实例化一个class表示的类的对象,还可以获取构造方法,成员变量,成员方法。 java中泛型的引入主要是为了解决两个方面的问题:1.集合类型元素在运行期出现类型装换异常,增加编译时类型的检查,2. 解决的时重复代码的编写,能够复用算法。下面通过例子来说明编译器的类型检查。 JDK 和 JRE 的区别是什么? JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。 JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。 总结: JDK 包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具。简单来说:如果你需要运行 java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,就需要安装 JDK。 . Java中有哪些数据类型? Java中数据类型分为基本数据类型和引用数据类型2种   基本类型:byte(默认值0,占1字节)、short(默认值0,占2字节)、int(默认值0,占4字节)、long(默认值0,占8字节)、float(默认值0.0,占4字节)、double(默认值0.0,占8字节)、char(默认值u0000,占2字节)、boolean(默认值false) 引用类型:类(默认值null)、接口(默认值null)、数组(默认值null)  == 和 equals 的区别是什么? 基本类型和引用类型比较,== 的作用效果是不同的。   基本类型:比较的是值是否相同 引用类型:比较的是引用是否相同 总结:== 对于基本类型比较的是值,对于引用类型比较的是引用;而 equals 默认情况下是引用比较,只是很多类重写了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。  Java 中 final 关键字的作用是什么? final 修饰的类叫最终类,不能被继承 final 修饰的方法叫最终方法,不能被重写,但可以被继承 final 修饰的变量叫常量,必须初始化,初始化之后值不能被修改 String、StringBuffer、StringBuilder 的区别是什么? String 是字符串常量,每次操作都会生产新的对象,适用于少量字符串操作的情况;StringBuffer、StringBuilder 是字符串变量,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。 String 类中常用方法都有哪些? String str = " app le "; // indexOf(): 返回指定字符的索引 // charAt(): 返回指定索引处的字符 // replace(): 字符串替换 // trim(): 去除字符串两端空白 // split(): 分割字符串,返回一个分割后的字符串数组 // getBytes(): 返回字符串的 byte 类型数组 // length(): 返回字符串长度、 // toLowerCase(): 将字符串转成小写字母 // toUpperCase(): 将字符串转成大写字符 // substring(): 截取字符串 // equals(): 字符串比较 普通类和抽象类的区别是什么? 普通类不能包含抽象方法,抽象类可以有抽象方法 普通类可以直接实例化,抽象类不能直接实例化  抽象类能使用 final 修饰吗? 不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类。 Java 中 IO 流分几种? 按功能分:输入流(input)、输出流(output) 按类型分:字节流、字符流 字节流和字符流的区别:字节流按 8 位传输,以字节为单位输入输出数据;字符流按 16 位传输,以字符为单位输入输出数据。 BIO、NIO、AIO 有什么区别? BIO:同步阻塞 IO,就是我们平常使用的传统 IO,服务器的实现模式是一个请求连接一个线程,并发处理能力低,可能造成不必要的线程开销,严重的还将导致服务器内存溢出。 NIO:同步非阻塞 IO,是传统 IO 的升级,服务器的实现模式是多个请求一个线程,即请求会注册到多路复用器Selector上,多路复用器轮询到连接有IO请求时才启动一个线程处理。 AIO:异步非阻塞 IO,是 NIO 的升级,也叫 NIO2,服务器的实现模式为多个有效请求一个线程,客户端的IO请求都是由OS先完成再通知服务器应用去启动线程处理(回调)。  Files 的常用方法都有哪些? // Files.exists():检测文件路径是否存在 // Files.createFile():创建文件 Path path2 = Paths.get("C:\Users\e-yangfangchao\Desktop\test\a.txt"); // Files.createDirectory():创建文件夹 // Files.delete():删除一个文件或目录 // Files.copy():复制文件 // Files.move():移动文件 // Files.size():查看文件个数 // Files.read():读取文件 // Files.write():写入文件 Collection 和 Collections 的区别是什么? java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection 接口在 Java 类库中有很多具体的实现。Collection 接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有 List 与 Set。 Collections 则是集合类的一个工具类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。 List、Set、Map 之间的区别是什么? 1) 元素重复性: ① List允许有重复的元素。任何数量的重复元素都可以在不影响现有重复元素的值及其索引的情况下插入到List集合中; ② Set集合不允许元素重复。Set以及所有实现了Set接口的类都不允许重复值的插入,若多次插入同一个元素时,在该集合中只显示一个; ③ Map以键值对的形式对元素进行存储。Map不允许有重复键,但允许有不同键对应的重复的值; 2) 元素的有序性: ① List及其所有实现类保持了每个元素的插入顺序; ② Set中的元素都是无序的;但是某些Set的实现类以某种殊形式对其中的元素进行排序,如:LinkedHashSet按照元素的插入顺序进行排序; ③ Map跟Set一样对元素进行无序存储,但其某些实现类对元素进行了排序。如:TreeMap根据键对其中的元素进行升序排序; 3) 元素是否为空值: ① List允许任意数量的空值; ② Set最多允许一个空值的出现;[ 当向Set集合中添加多个null值时,在该Set集合中只会显示一个null元素] ③  Map只允许出现一个空键,但允许出现任意数量的空值 总结: List中的元素,有序、可重复、可为空;     Set中的元素,无序、不重复、只有一个空元素;     Map中的元素,无序、键不重,值可重、可一个空键、多可空值;  HashMap 和 Hashtable 的区别是什么? hashMap 去除了 HashTable 的 contains() 方法,但是加上了 containsValue() 和 containsKey() 方法。 hashTable 是线程同步的,而 HashMap 是非同步的,HashMap 效率上比 hashTable 要高。 hashMap 允许空键空值,而 hashTable 不允许。  如何决定使用 HashMap 还是 TreeMap? 在 Map 中做插入、删除和定位元素这类操作,HashMap 是最好的选择。假如你需要对一个有序的 key 集合进行遍历,TreeMap 是更好的选择。 HashMap 的实现原理是什么? HashMap概述: HashMap 是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 HashMap的数据结构: HashMap 实际上是一个“链表散列”的数据结构,即数组和链表的结合体。 当我们往 HashMap 中 put 元素时,首先根据 key 的 hashcode 重新计算 hash 值,根据 hash 值得到这个元素在数组中的位置(下标),如果该数组在该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾。如果数组中该位置没有元素,就直接将该元素放到数组的该位置上。 Jdk1.8 中对 HashMap 的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的 O(n) 到 O(logn)。 HashSet 的实现原理是什么? HashSet 实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变;此类允许使用 null 元素;HashSet 中不允许有重复元素。 HashSet 是基于 HashMap 实现的,HashSet 中的元素都存放在 HashMap 的 key 上面,而 value 中的值都是统一的一个 private static final Object PRESENT = new Object()。HashSet跟HashMap一样,都是一个存放链表的数组。  ArrayList 和 LinkedList 的区别是什么? ArrayList 底层基于动态数组,随机访问元素效率高,向集合尾部添加元素效率高,删除或者在其他位置添加元素效率低(需要移动数组);LinkedList 基于链表的动态数组,数据添加和删除效率高,只需要改变指针指向即可,但是访问数据的平均效率低,需要对链表进行遍历。  如何实现数组和 List 之间的转换? // List 转 数组 String[] strArr = {"apple","pear","banana","peach"}; List list = Arrays.asList(strArr); // 数组 转 List String[] arr = (String[]) list.toArray(); Array 和 ArrayList 的区别是什么? Array 可以容纳基本类型和对象,而 ArrayList 只能容纳对象。 Array 是指定大小的,而 ArrayList 初始化大小是固定的。 Array 没有提供 ArrayList 那么多功能,比如addAll、removeAll和iterator等。  Iterator 怎么使用?有什么特点? Iterator 功能比较简单,并且只能单向移动 使用方法 iterator() 要求容器返回一个 Iterator。第一次调用 Iterator 的 next() 方法时,它返回序列的第一个元素。注意:iterator() 方法是 java.lang.Iterable 接口,被 Collection 继承。 使用 hasNext() 检查序列中是否还有元素 使用 next() 获得序列中的下一个元素 使用 remove() 将迭代器新返回的元素删除 Iterator 是 Java 迭代器最简单的实现,为 List 设计的 ListIterator 具有更多的功能,它可以从两个方向遍历 List,也可以从 List 中插入和删除元素。     public static void main(String[] args) {         // List         ArrayList list = new ArrayList<>();         list.add("apple");         list.add("pear");         list.add("banana");         Iterator iterator = list.iterator();         while (iterator.hasNext()){             String s = iterator.next();             if ("apple".equals(s)){                 iterator.remove();             }         }         list.forEach(item -> System.out.println(item));         // Map         Map map=new HashMap<>();         map.put("pig","猪");         map.put("cat","猫");         map.put("dog","狗");         Iterator iterator1 = map.keySet().iterator();         Iterator iterator2 = map.values().iterator();         while (iterator1.hasNext()){             System.out.println(iterator1.next());         }         while (iterator2.hasNext()){             System.out.println(iterator2.next());         }     } 并行和并发的区别是什么? 并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生 并行是在不同实体上的多个事件,并发是在同一实体上的多个事件 在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如 hadoop 分布式集群 普通解释: 并发:交替做不同事情的能力 并行:同时做不同事情的能力 专业术语: 并发:不同的代码块交替执行 并行:不同的代码块同时执行 线程和进程的区别是什么? 进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。线程是进程的一个实体,是 cpu 调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。 sleep()和wait()的区别是什么? sleep():是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。因为 sleep() 是 static 静态的方法,他不能改变对象的锁,当一个 synchronized 块中调用了 sleep() 方法,线程虽然进入休眠,但是对象的锁没有被释放,其他线程依然无法访问这个对象。 wait():是 Object 类的方法,当一个线程执行到 wait 方法时,它就进入到一个和该对象相关的等待池,同时释放对象的锁,使其他线程能够访问,可以通过 notify,notifyAll 方法来唤醒等待的线程。 子类继承父类,都存在构造方法,静态方法和非静态方法,那么执行顺序是什么

● 请你来聊一聊集合类 Java中的集合包含多种数据结构,如链表、队列、哈希表等。从类的继承结构来说,可以分为两大类,一类是继承自Collection接口,这类集合包含List、Set和Queue等集合类。另一类是继承自Map接口,这主要包含了哈希表相关的集合类。 1、List、Set和Queue类的继承结构图:绿色的虚线代表实现,绿色实线代表接口之间的继承,蓝色实线代表类之间的继承。 Collection接口除了实现映射的集合类之外的所有集合类定义了一些方法 List集合类型:描述了一种按位置存储数据的对象,是有序的。用的比较多List包括ArrayList和LinkedList,这两者的区别:ArrayList的底层的通过数组实现,所以其随机访问的速度比较快,但是对于需要频繁的增删的情况,效率就比较低了。而对于LinkedList,底层通过链表来实现,所以增删操作比较容易完成,但是对于随机访问的效率比较低。 Queue:一般可以直接使用LinkedList完成,LinkedList继承自Deque,所以LinkedList具有双端队列的功能。PriorityQueue是为每个元素提供一个优先级,优先级高的元素会优先出队列。 Set:Set与List的主要区别是Set是不允许元素是重复的,而List则可以允许元素是重复的。HashSet和LinkedHashSet的区别在于后者可以保证元素插入集合的元素顺序与输出顺序保持一致。而TresSet的区别在于其排序是按照Comparator来进行排序的,默认情况下按照字符的自然顺序进行升序排列。 Iterable:Collection类继承自Iterable,该接口的作用是提供元素遍历的功能,也就是说所有的集合类(除Map相关的类)都提供元素遍历的功能。Iterable里面包含了Iterator的迭代器。 2、Map类型的集合:最大的优点在于其查找效率比较高,理想情况下可以实现O(1)的时间复杂度。Map中最常用的是HashMap,LinkedHashMap与HashMap的区别在于前者能够保证插入集合的元素顺序与输出顺序一致。这两者与TreeMap的区别在于TreeMap是根据键值进行排序的,其底层的实现也有本质的区别,HashMap底层是一个哈希表,而TreeMap的底层数据结构是一棵树。  

什么时候使用抽象类和接口: 如果拥有一些方法并且想让它们中的一些有默认实现,使用抽象类。 如果想实现多重继承,必须使用接口。由于Java不支持多继承,子类不能够继承多个类,但可以实现多个接口。 如果基本功能在不断改变,那么就需要使用抽象类。如果不断改变基本功能并且使用接口,那么就需要改变所有实现了该接口的类。

● 请你说一下Java中的异常处理机制 在Java 应用程序中,异常处理机制为:抛出异常,捕捉异常。 抛出异常:当一个方法出现错误引发异常时,方法创建异常对象并交付运行时系统,异常对象中包含了异常类型和异常出现时的程序状态等异常信息。运行时系统负责寻找处置异常的代码并执行。 捕获异常:在方法抛出异常之后,运行时系统将转为寻找合适的异常处理器(exception handler)。潜在的异常处理器是异常发生时依次存留在调用栈中的方法的集合。当异常处理器所能处理的异常类型与方法抛出的异常类型相符时,即为合适 的异常处理器。运行时系统从发生异常的方法开始,依次回查调用栈中的方法,直至找到含有合适异常处理器的方法并执行。当运行时系统遍历调用栈而未找到合适 的异常处理器,则运行时系统终止。同时,意味着Java程序的终止。 对于运行时异常、错误或可查异常,Java技术所要求的异常处理方式有所不同: 由于运行时异常的不可查性,为了更合理、更容易地实现应用程序,Java规定,运行时异常将由Java运行时系统自动抛出,允许应用程序忽略运行时异常。 对于方法运行中可能出现的Error,当运行方法不欲捕捉时,Java允许该方法不做任何抛出声明。因为,大多数Error异常属于永远不能被允许发生的状况,也属于合理的应用程序不该捕捉的异常。 对于所有的可查异常,Java规定:一个方法必须捕捉,或者声明抛出方法之外。也就是说,当一个方法选择不捕捉可查异常时,它必须声明将抛出异常。 通常使用关键字try、catch、finally来捕获异常: try块:用于捕获异常。其后可接零个或多个catch块,如果没有catch块,则必须跟一个finally块。 catch块:用于处理try捕获到的异常。 finally块:无论是否捕获或处理异常,finally块里的语句都会被执行。当在try块或catch块中遇到return语句时,finally语句块将在方法返回之前被执行。在以下4种特殊情况下,finally块不会被执行: 1)在finally语句块中发生了异常。 2)在前面的代码中用了System.exit()退出程序。 3)程序所在的线程死亡。 4)关闭CPU。 try、catch、finally语句块的执行顺序: 1)当try没有捕获到异常时:try语句块中的语句逐一被执行,程序将跳过catch语句块,执行finally语句块和其后的语句; 2)当try捕获到异常,catch语句块里没有处理此异常的情况:当try语句块里的某条语句出现异常时,而没有处理此异常的catch语句块时,此异常将会抛给JVM处理,finally语句块里的语句还是会被执行,但finally语句块后的语句不会被执行; 3)当try捕获到异常,catch语句块里有处理此异常的情况:在try语句块中是按照顺序来执行的,当执行到某一条语句出现异常时,程序将跳到catch语句块,并与catch语句块逐一匹配,找到与之对应的处理程序,其他的catch语句块将不会被执行,而try语句块中,出现异常之后的语句也不会被执行,catch语句块执行完后,执行finally语句块里的语句,最后执行finally语句块后的语句;

● 请问多线程是什么?

参考回答: 最开始,线程只是用于分配单个处理器的处理时间的一种工具。但假如操作系统本身支持多个处理器,那么每个线程都可分配给一个不同的处理器,真正进入“并行运算”状态。从程序设计语言的角度看,多线程操作最有价值的特性之一就是程序员不必关心到底使用了多少个处理器。程序在逻辑意义上被分割为数个线程;假如机器本身安装了多个处理器,那么程序会运行得更快,毋需作出任何特殊的调校。根据前面的论述,大家可能感觉线程处理非常简单。但必须注意一个问题:共享资源!如果有多个线程同时运行,而且它们试图访问相同的资源,就会遇到一个问题。举个例子来说,两个线程不能将信息同时发送给一台打印机。为解决这个问题,对那些可共享的资源来说(比如打印机),它们在使用期间必须进入锁定状态。所以一个线程可将资源锁定,在完成了它的任务后,再解开(释放)这个锁,使其他线程可以接着使用同样的资源。 多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。 一个采用了多线程技术的应用程序可以更好地利用系统资源。其主要优势在于充分利用了CPU的空闲时间片,可以用尽可能少的时间来对用户的要求做出响应,使得进程的整体运行效率得到较大提高,同时增强了应用程序的灵活性。更为重要的是,由于同一进程的所有线程是共享同一内存,所以不需要特殊的数据传送机制,不需要建立共享存储区或共享文件,从而使得不同任务之间的协调操作与运行、数据的交互、资源的分配等问题更加易于解决。

● 请你来聊一聊集合类 Java中的集合包含多种数据结构,如链表、队列、哈希表等。从类的继承结构来说,可以分为两大类,一类是继承自Collection接口,这类集合包含List、Set和Queue等集合类。另一类是继承自Map接口,这主要包含了哈希表相关的集合类。 1、List、Set和Queue类的继承结构图:绿色的虚线代表实现,绿色实线代表接口之间的继承,蓝色实线代表类之间的继承。 转存失败重新上传取消 Collection接口除了实现映射的集合类之外的所有集合类定义了一些方法 List集合类型:描述了一种按位置存储数据的对象,是有序的。用的比较多List包括ArrayList和LinkedList,这两者的区别:ArrayList的底层的通过数组实现,所以其随机访问的速度比较快,但是对于需要频繁的增删的情况,效率就比较低了。而对于LinkedList,底层通过链表来实现,所以增删操作比较容易完成,但是对于随机访问的效率比较低。 Queue:一般可以直接使用LinkedList完成,LinkedList继承自Deque,所以LinkedList具有双端队列的功能。PriorityQueue是为每个元素提供一个优先级,优先级高的元素会优先出队列。 Set:Set与List的主要区别是Set是不允许元素是重复的,而List则可以允许元素是重复的。HashSet和LinkedHashSet的区别在于后者可以保证元素插入集合的元素顺序与输出顺序保持一致。而TresSet的区别在于其排序是按照Comparator来进行排序的,默认情况下按照字符的自然顺序进行升序排列。 Iterable:Collection类继承自Iterable,该接口的作用是提供元素遍历的功能,也就是说所有的集合类(除Map相关的类)都提供元素遍历的功能。Iterable里面包含了Iterator的迭代器。 2、Map类型的集合:最大的优点在于其查找效率比较高,理想情况下可以实现O(1)的时间复杂度。Map中最常用的是HashMap,LinkedHashMap与HashMap的区别在于前者能够保证插入集合的元素顺序与输出顺序一致。这两者与TreeMap的区别在于TreeMap是根据键值进行排序的,其底层的实现也有本质的区别,HashMap底层是一个哈希表,而TreeMap的底层数据结构是一棵树。 转存失败重新上传取消

● 请你说一下java jvm的内存机制 Java内存区域划分 转存失败重新上传取消 1.程序计数器: 可以看做是当前线程所执行的字节码的行号指示器。在JVM的概念模型里,字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令。 每条线程都有一个独立的程序计数器,所以程序计数器是线程私有的内存区域。 如果线程执行的是一个Java方法,计数器记录的是正在执行的虚拟机字节码指令的地址;如果线程执行的是一个Native方法,计数器的值为空。 Java虚拟机规范中唯一一个没有规定任何OutOfMemoryError情况的区域。 2.Java虚拟机栈: 描述Java方法执行的内存模型,每个方法执行的同时会创建一个栈帧,栈帧用于存储局部变量表、操作数栈、动态链接、方法出口等信息。每个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。 转存失败重新上传取消 Java虚拟机栈是线程私有的,它的生命周期与线程相同。 局部变量表存放了编译时期可知的各种基本数据类型和对象引用。局部变量表所需的内存空间在编译时期完成分配,当进入一个方法时,这个方法需要在栈帧中分配多大的局部变量空间是完全确定的,在方法运行期间不会改变局部变量表的大小。 Java虚拟机规范对这个区域规定了两种异常情况: 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常; 如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常; 3.本地方法栈: 本地方法栈与虚拟机栈的区别:虚拟机栈为虚拟机执行Java方法服务(也就是字节码),而本地方法栈为虚拟机使用到的Native方法服务。 Java虚拟机规范对这个区域规定了两种异常情况:StackOverflowError 和 OutOfMemoryError异常。 4.Java堆: Java堆是被所有的线程共享的一块内存区域,在虚拟机启动时创建。Java堆的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。 Java堆是垃圾回收器管理的主要区域,从内存回收的角度看,由于现在收集器基本都采用分代收集算法,所以Java堆可以细分为:新生代、老生代;从内存分配的角度看,线程共享的Java堆可能划分出多个线程私有的分配缓冲区(TLAB)。 Java堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。 Java虚拟机规范规定,如果在堆上没有内存完成实例分配,并且堆上也无法再扩展时,将会抛出OutOfMemoryError异常。 Java堆内存的OOM异常: 内存泄露:指程序中一些对象不会被GC所回收,它始终占用内存,即被分配的对象引用链可达但已无用。 内存溢出:程序运行过程中无法申请到足够的内存而导致的一种错误。内存溢出通常发生于OLD段或Perm段垃圾回收后,仍然无内存空间容纳新的Java对象的情况。 5.方法区: 被所有的线程共享的一块内存区域。它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。 不需要连续的内存和可以选择固定大小或者可扩展之外,还可以选择不实现垃圾回收。 Java虚拟机规范规定,当方法区无法满足内存分配的需求时,将抛出OutOfMemoryError异常。

● 请你说一说垃圾收集机制

参考回答:Collection)是Java虚拟机(JVM)垃圾回收器提供的一种用于在空闲时间不定时回收无任何对象引用的对象占据的内存空间的一种机制。 引用:如果Reference类型的数据中存储的数值代表的是另外一块内存的起始地址,就称这块内存代表着一个引用。 (1)强引用(Strong Reference):如“Object obj = new Object()”,这类引用是Java程序中最普遍的。只要强引用还存在,垃圾收集器就永远不会回收掉被引用的对象。 (2)软引用(Soft Reference):它用来描述一些可能还有用,但并非必须的对象。在系统内存不够用时,这类引用关联的对象将被垃圾收集器回收。JDK1.2之后提供了SoftReference类来实现软引用。 (3)弱引用(Weak Reference):它也是用来描述非须对象的,但它的强度比软引用更弱些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK1.2之后,提供了WeakReference类来实现弱引用。 (4)虚引用(Phantom Reference):最弱的一种引用关系,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的是希望能在这个对象被收集器回收时收到一个系统通知。JDK1.2之后提供了PhantomReference类来实现虚引用。 垃圾:无任何对象引用的对象。 判断对象是否是垃圾的算法: 引用计数算法(Reference Counting Collector)、根搜索算法(Tracing Collector): 回收:清理“垃圾”占用的内存空间而非对象本身。 Tracing算法(Tracing Collector) 标记—清除算法:分为“标记”和“清除”两个阶段:首先标记出所需回收的对象,在标记完成后统一回收掉所有被标记的对象,它的标记过程其实就是前面的根搜索算法中判定垃圾对象的标记过程。 Compacting算法(Compacting Collector)标记—整理算法:标记的过程与标记—清除算法中的标记过程一样,但对标记后出的垃圾对象的处理情况有所不同,它不是直接对可回收对象进行清理,而是让所有的对象都向一端移动,然后直接清理掉端边界以外的内存。在基于Compacting算法的收集器的实现中,一般增加句柄和句柄表。 Copying算法(Copying Collector):将内存按容量分为大小相等的两块,每次只使用其中的一块(对象面),当这一块的内存用完了,就将还存活着的对象复制到另外一块内存上面(空闲面),然后再把已使用过的内存空间一次清理掉。 Adaptive算法(Adaptive Collector):监控当前堆的使用情况,并将选择适当算法的垃圾收集器。 发生地点:一般发生在堆内存中,因为大部分的对象都储存在堆内存中。 (堆内存为了配合垃圾回收有什么不同区域划分,各区域有什么不同?) Java的堆内存基于Generation算法(Generational Collector)划分为新生代、年老代和持久代。新生代又被进一步划分为Eden和Survivor区,最后Survivor由FromSpace(Survivor0)和ToSpace(Survivor1)组成。所有通过new创建的对象的内存都在堆中分配,其大小可以通过-Xmx和-Xms来控制。分代收集基于这样一个事实:不同的对象的生命周期是不一样的。因此,可以将不同生命周期的对象分代,不同的代采取不同的回收算法进行垃圾回收(GC),以便提高回收效率。 按执行机制划分Java有四种类型的垃圾回收器: (1)串行垃圾回收器(Serial Garbage Collector) (2)并行垃圾回收器(Parallel Garbage Collector) (3)并发标记扫描垃圾回收器(CMS Garbage Collector) (4)G1垃圾回收器(G1 Garbage Collector) 发生时间:程序空闲时间不定时回收。

● 请你说一下OOM可能发生在哪,怎么查看,怎么调优 除了程序计数器不会抛出OOM外,其他各个内存区域都可能会抛出OOM。 最常见的OOM情况有以下三种: •    java.lang.OutOfMemoryError: Java heap space ------>java堆内存溢出,此种情况最常见,一般由于内存泄露或者堆的大小设置不当引起。对于内存泄露,需要通过内存监控软件查找程序中的泄露代码,而堆大小可以通过虚拟机参数-Xms,-Xmx等修改。 •    java.lang.OutOfMemoryError: PermGen space ------>java永久代溢出,即方法区溢出了,一般出现于大量Class或者jsp页面,或者采用cglib等反射机制的情况,因为上述情况会产生大量的Class信息存储于方法区。此种情况可以通过更改方法区的大小来解决,使用类似-XX:PermSize=64m -XX:MaxPermSize=256m的形式修改。另外,过多的常量尤其是字符串也会导致方法区溢出。 •    java.lang.StackOverflowError ------> 不会抛OOM error,但也是比较常见的Java内存溢出。JAVA虚拟机栈溢出,一般是由于程序中存在死循环或者深度递归调用造成的,栈大小设置太小也会出现此种溢出。可以通过虚拟机参数-Xss来设置栈的大小。 OOM分析--heapdump 要dump堆的内存镜像,可以采用如下两种方式: •    设置JVM参数-XX:+HeapDumpOnOutOfMemoryError,设定当发生OOM时自动dump出堆信息。不过该方法需要JDK5以上版本。 •    使用JDK自带的jmap命令。"jmap -dump:format=b,file=heap.bin "   其中pid可以通过jps获取。 dump堆内存信息后,需要对dump出的文件进行分析,从而找到OOM的原因。常用的工具有: •    mat: eclipse memory analyzer, 基于eclipse RCP的内存分析工具。 •    jhat:JDK自带的java heap analyze tool,可以将堆中的对象以html的形式显示出来,包括对象的数量,大小等等,并支持对象查询语言OQL,分析相关的应用后,可以通过http://localhost:7000来访问分析结果。不推荐使用,因为在实际的排查过程中,一般是先在生产环境 dump出文件来,然后拉到自己的开发机器上分析,所以,不如采用高级的分析工具比如前面的mat来的高效。

● 请你说一下类加载 1、什么是类的加载 类的加载指的是将类的.class文件中的二进制数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象,Class对象封装了类在方法区内的数据结构,并且向Java程序员提供了访问方法区内的数据结构的接口。类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动使用该类时才报告错误(LinkageError错误)如果这个类一直没有被程序主动使用,那么类加载器就不会报告错误。 加载.class文件的方式 – 从本地系统中直接加载 – 通过网络下载.class文件 – 从zip,jar等归档文件中加载.class文件 – 从专有数据库中提取.class文件 – 将Java源文件动态编译为.class文件 2、类的生命周期 类加载的过程包括了加载、验证、准备、解析、初始化五个阶段。在这五个阶段中,加载、验证、准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定)。另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常在一个阶段执行的过程中调用或激活另一个阶段。 A.加载:查找并加载类的二进制数据 加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情: 1、通过一个类的全限定名来获取其定义的二进制字节流。 2、将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。 3、在Java堆中生成一个代表这个类的java.lang.Class对象,作为对方法区中这些数据的访问入口。 相对于类加载的其他阶段而言,加载阶段(准确地说,是加载阶段获取类的二进制字节流的动作)是可控性最强的阶段,因为开发人员既可以使用系统提供的类加载器来完成加载,也可以自定义自己的类加载器来完成加载。加载阶段完成后,虚拟机外部的二进制字节流就按照虚拟机所需的格式存储在方法区之中,而且在Java堆中也创建一个java.lang.Class类的对象,这样便可以通过该对象访问方法区中的这些数据。 B.连接 – 验证:确保被加载的类的正确性 验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。验证阶段大致会完成4个阶段的检验动作: •    文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。 •    元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。 •    字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。 •    符号引用验证:确保解析动作能正确执行。 验证阶段是非常重要的,但不是必须的,它对程序运行期没有影响,如果所引用的类经过反复验证,那么可以考虑采用-Xverifynone参数来关闭大部分的类验证措施,以缩短虚拟机类加载的时间。 – 准备:为类的静态变量分配内存,并将其初始化为默认值 准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。 – 解析:把类中的符号引用转换为直接引用 解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。 C.初始化 初始化,为类的静态变量赋予正确的初始值,JVM负责对类进行初始化,主要对类变量进行初始化。在Java中对类变量进行初始值设定有两种方式: ①声明类变量是指定初始值 ②使用静态代码块为类变量指定初始值 JVM初始化步骤 1、假如这个类还没有被加载和连接,则程序先加载并连接该类 2、假如该类的直接父类还没有被初始化,则先初始化其直接父类 3、假如类中有初始化语句,则系统依次执行这些初始化语句 类初始化时机:只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下六种: – 创建类的实例,也就是new的方式 – 访问某个类或接口的静态变量,或者对该静态变量赋值 – 调用类的静态方法 – 反射(如Class.forName(“com.shengsiyuan.Test”)) – 初始化某个类的子类,则其父类也会被初始化 – Java虚拟机启动时被标明为启动类的类(Java Test),直接使用java.exe命令来运行某个主类 D.结束生命周期 在如下几种情况下,Java虚拟机将结束生命周期 – 执行了System.exit()方法 – 程序正常执行结束 – 程序在执行过程中遇到了异常或错误而异常终止 – 由于操作系统出现错误而导致Java虚拟机进程终止 3、类的加载 类加载有三种方式: •    命令行启动应用时候由JVM初始化加载 •    通过Class.forName()方法动态加载 •    通过ClassLoader.loadClass()方法动态加载 4、双亲委派模型 双亲委派模型的工作流程是:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把请求委托给父加载器去完成,依次向上,因此,所有的类加载请求最终都应该被传递到顶层的启动类加载器中,只有当父加载器在它的搜索范围中没有找到所需的类时,即无法完成该加载,子加载器才会尝试自己去加载该类。 双亲委派机制: •    当AppClassLoader加载一个class时,它首先不会自己去尝试加载这个类,而是把类加载请求委派给父类加载器ExtClassLoader去完成。 •    当ExtClassLoader加载一个class时,它首先也不会自己去尝试加载这个类,而是把类加载请求委派给BootStrapClassLoader去完成。 •    如果BootStrapClassLoader加载失败(例如在$JAVA_HOME/jre/lib里未查找到该class),会使用ExtClassLoader来尝试加载; •    若ExtClassLoader也加载失败,则会使用AppClassLoader来加载,如果AppClassLoader也加载失败,则会报出异常ClassNotFoundException。

● 请你说一下死锁的原因,以及如何打破,如何查看死锁进程状态 1、死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。死锁的四个必要条件: •    互斥条件(Mutual exclusion):资源不能被共享,只能由一个进程使用。 •    请求与保持条件(Hold and wait):已经得到资源的进程可以再次申请新的资源。 •    非剥夺条件(No pre-emption):已经分配的资源不能从相应的进程中被强制地剥夺。 •    循环等待条件(Circular wait):系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。 java中产生死锁可能性的最根本原因是:1)是多个线程涉及到多个锁,这些锁存在着交叉,所以可能会导致了一个锁依赖的闭环;2)默认的锁申请操作是阻塞的。 如,线程在获得一个锁L1的情况下再去申请另外一个锁L2,也就是锁L1想要包含了锁L2,在获得了锁L1,并且没有释放锁L1的情况下,又去申请获得锁L2,这个是产生死锁的最根本原因。 2、避免死锁: •    方案一:破坏死锁的循环等待条件。 •    方法二:破坏死锁的请求与保持条件,使用lock的特性,为获取锁操作设置超时时间。这样不会死锁(至少不会无尽的死锁) •    方法三:设置一个条件遍历与一个锁关联。该方法只用一把锁,没有chopstick类,将竞争从对筷子的争夺转换成了对状态的判断。仅当左右邻座都没有进餐时才可以进餐。提升了并发度。 3、linux中查看死锁进程状态 使用pstack 和 gdb 工具对死锁程序进行分析 pstack 进程号 查看各个线程的堆栈信息 当进程吊死的时候,多次使用,死锁的线程将一直处于等锁的状态,确定某些线程一直没有变化,一直处于等锁的状态。那么这些线程很可能是死锁了。如果怀疑哪些线程发生死锁了,可以采用gdb 进一步attach线程并进行分析。 执行命令gdb attach 进程号,进入gdb调试终端 运行:(gdb) info thread

● 请你说一下内存泄漏 当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致它不能被回收,这导致本该被回收的对象不能被回收而停留在堆内存中,这就产生了内存泄漏。内存泄漏是造成应用程序OOM的主要原因之一。我们知道Android系统为每个应用程序分配的内存是有限的,而当一个应用中产生的内存泄漏比较多时,这就难免会导致应用所需要的内存超过系统分配的内存限额,这就造成了内存溢出从而导致应用Crash。 常见的内存泄漏: 1、单例造成的内存泄漏 由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。 2、非静态内部类创建静态实例造成的内存泄漏 非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,从而导致Activity的内存资源不能被正常回收。 3、Handler造成的内存泄漏 4、线程造成的内存泄漏 如果任务在Activity销毁之前还未完成,那么将导致Activity的内存资源无法被回收,从而造成内存泄漏。 5、资源未关闭造成的内存泄漏 对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,从而造成内存泄漏。 6、使用ListView时造成的内存泄漏 7、集合容器中的内存泄露 8、WebView造成的泄露 避免内存泄漏: 1、在涉及使用Context时,对于生命周期比Activity长的对象应该使用Application的Context。 2、对于需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏。 3、对于不再需要使用的对象,显示的将其赋值为null,比如使用完Bitmap后先调用recycle(),再赋为null。 4、保持对对象生命周期的敏感,特别注意单例、静态对象、全局性集合等的生命周期。 5、对于生命周期比Activity长的内部类对象,并且内部类中使用了外部类的成员变量,可以这样做避免内存泄漏: 1)将内部类改为静态内部类 2)静态内部类中使用弱引用来引用外部类的成员变量

● 请你说一说class和interface的区别 1、接口类似于类,但接口的成员都没有执行方式,它只是方法、属性、事件和索引的组合而已,并且也只能包含这四种成员;类除了这四种成员之外还可以有别的成员(如字段)。 2、不能实例化一个接口,接口只包括成员的签名;而类可以实例化(abstract类除外)。 3、接口没有构造函数,类有构造函数。 4、接口不能进行运算符的重载,类可以进行运算符重载。 5、接口的成员没有任何修饰符,其成员总是公共的,而类的成员则可以有修饰符(如:虚拟或者静态)。 6、派生于接口的类必须实现接口中所有成员的执行方式,而从类派生则不然。

● 请你说一说强引用和弱引用 强引用: 强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。如下: Object o=new Object(); // 强引用 当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足的问题。如果不使用时,要通过如下方式来弱化引用,如下: o=null; // 帮助垃圾收集器回收此对象 显式地设置o为null,或超出对象的生命周期范围,则gc认为该对象不存在引用,这时就可以回收这个对象。具体什么时候收集这要取决于gc的算法。 举例: public void test(){ Object o=new Object(); } 在一个方法的内部有一个强引用,这个引用保存在栈中,而真正的引用内容(Object)保存在堆中。当这个方法运行完成后就会退出方法栈,则引用内容的引用不存在,这个Object会被回收。但是如果这个o是全局的变量时,就需要在不用这个对象时赋值为null,因为强引用不会被垃圾回收。 弱引用: 弱引用也是用来描述非必需对象的,只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。提供WeakReference类实现弱引用。 例子: String str=new String("abc"); WeakReference abcWeakRef = new WeakReference(str); str=null; 当垃圾回收器进行扫描回收时等价于: str = null; System.gc(); 如果这个对象是偶尔的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用Weak Reference 来记住此对象。 String abc = abcWeakRef.get(); //该代码会让str再次变为一个强引用: 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。当你想引用一个对象,但是这个对象有自己的生命周期,你不想介入这个对象的生命周期,这时候你就是用弱引用。这个引用不会在对象的垃圾回收判断中产生任何附加的影响。

● 手写代码:求n以内的最大质数

● 请你说一下java里内存泄漏和溢出的区别 1、内存泄漏memory leak :是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄漏似乎不会有大的影响,但内存泄漏堆积后的后果就是内存溢出。 2、内存溢出 out of memory :指程序申请内存时,没有足够的内存供申请者使用,或者说,给了你一块存储int类型数据的存储空间,但是你却存储long类型的数据,那么结果就是内存不够用,此时就会报错OOM,即所谓的内存溢出。

● 你知道java里面的内存管理机制吗,比如创建和回收是怎么管理的? java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域存储不同类型的数据,这些区域的内存分配和销毁的时间也不同,有的区域随着虚拟机进程的启动而存在,有些区域则是依赖用户线程的启动和结束而建立和销毁。根据《Java虚拟机规范(第2版)》的规定,Java虚拟机管理的内存包括五个运行时数据区域, 1、方法区 方法区(Method Area)是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息(包括类的名称、方法信息、成员变量信息)、常量、静态变量、以及编译器编译后的代码等数据 2、虚拟机栈和本地方法栈 虚拟机栈是线程私有的内存空间,每个线程都有一个线程栈,本地方法栈也是线程私有的内存空间,本地方法栈与Java栈所发挥的作用是非常相似的,它们之间的区别不过是Java栈执行Java方法,本地方法栈执行的是本地方法,有的虚拟机直接把本地方法栈和虚拟机栈合二为一。 3、堆 Java堆是Java虚拟机所管理的内存中最大的一块,在虚拟机启动时创建,此内存区域的目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。从内存分配的角度来看,线程共享的Java堆中可能划分出多个线程私有的分配缓冲区(TLAB)。Java堆可以处于物理上不连续的内存空间,只要逻辑上连续即可,在实现上,既可以实现固定大小的,也可以是扩展的。如果堆中没有足够的内存分配给实例,并且堆也无法再拓展时,将会抛出OutOfMemeryError异常。 Java内存回收: 对于栈空间,当方法调用结束后,基本类型变量,引用类型变量,形参占据的空寂会被自动释放,但引用类型指向的对象在堆中,堆中的无用内存有垃圾回收线程回收,GC线程优先级最低,只有当没有工作线程存在时GC线程才会执行,或者堆空间不足时会自动出发GC线程工作,

● 请你说一下Java里integer和int的区别,以及如何比较相等 Integer和int的区别: 1、integer是int的包装类,int是Java的一种基本数据结构 2、integer变量必须实例化后才能使用,int变量不需要 3、integer实际是对象的引用,int是直接存储数据值 4、integer的默认值是null,int的默认值是0 如何比较相等,首先要明白equals和==的区别 Equals通常用来比较两个对象的内容是否相等,==用来比较两个对象的地址是否相等,Object类中的equals方法定义为判断两个对象的地址是否相等(可以理解成是否是同一个对象),地址相等则认为是对象相等。这也就意味着,我们新建的所有类如果没有复写equals方法,那么判断两个对象是否相等时就等同于“==”,也就是两个对象的地址是否相等。但在我们的实际开发中,通常会认为两个对象的内容相等时,则两个对象相等,equals返回true。对象内容不同,则返回false。 所以可以总结为两种情况 1、类未复写equals方法,则使用equals方法比较两个对象时,相当于==比较,即两个对象的地址是否相等。地址相等,返回true,地址不相等,返回false。 2、类复写equals方法,比较两个对象时,则走复写之后的判断方式。通常,我们会将equals复写成:当两个对象内容相同时,则equals返回true,内容不同时,返回false。

● 请你回答一下protected,public,private的区别

参考回答: 作用域     当前类  同一package  子孙类  其它package public       √                     √              √            √ protected    √                √                 √                  × friendly     √                     √                  ×                 × private    √               ×                  ×                 ×

● 请你说一下抽象类和接口的区别 从语法层面来说, 1、抽象类可以提供成员方法的实现细节,而接口中只能存在抽象方法 2、抽象类中成员变量可以是多种类型,接口中成员变量必须用public,static,final修饰 3、一个类只能继承一个抽象类,但可以实现多个接口 4、抽象类中允许含有静态代码块和静态方法,接口不能 从设计层面而言 1.抽象类是对整一个类的属性,行为等方面进行抽象,而接口则是对行为抽象。就好比飞机和鸟,抽象类抽象出的是飞行物类。而接口则是抽闲出飞行方法。 2.抽象类是一个模板式的设计,当在开发过程中出现需求更改的情况,只需要更改抽象类而不需要更改它的子类。接口是一种辐射性设计,当接口的内容发生改变时,需要同时对实现它的子类进行相应的修改。 3.抽象类可以类比为模板,而接口可以类比为协议

● 请你说一下List和ArrayList的区别,以及arrayList和HashSet区别

参考回答: List:是一个有序的集合,可以包含重复的元素。提供了按索引访问的方式。它继承 Collection。 List有两个重要的实现类:ArrayList 和 LinkedList ArrayList:我们可以将其看作是能够自动增长容量的数组。 利用ArrayList的toArray()返回一个数组。 Arrays.asList()返回一个列表。 1.ArrayList底层采用数组实现,当使用不带参数的构造方法生成ArrayList对象时,实际上会在底层生成一个长度为10的Object类型数组 2.如果增加的元素个数超过了10个,那么ArrayList底层会新生成一个数组,长度为原数组的1.5倍+1,然后将原数组的内容复制到新数组当中,并且后续增加的内容都会放到新数组当中。当新数组无法容纳增加的元素时,重复该过程。 3.对于ArrayList元素的删除操作,需要将被删除元素的后续元素向前移动,代价比较高。 4.集合当中只能放置对象的引用,无法放置原生数据类型,我们需要使用原生数据类型的包装类才能加入到集合当中。 5.集合当中放置的都是Object类型,因此取出来的也是Object类型,那么必须要使用强制类型转换将其转换为真正的类型(放置进去的类型)

● 请你回答一下Java的内存结构是什么,全局变量,临时变量,静态变量分别存在哪里,堆分为哪几块,比如说新生代老生代,那么新生代又分为什么

参考回答: 代码区:是编译器生成的一个exe区段,存放函数体的二进制代码 栈区:存放函数的参数,局部变量的值等,其操作方式类似于数据结构中的栈,const局部变量也是放在栈里 堆区:就是malloc和new之类的内存所在区段,一般由程序员分配释放,分配方式类似于链表 静态数据区:是编译器生成的一个exe区段,初始和未初始化的全局变量和局部变量都放在这里, 常量区:是编译器生成的一个exe区段,const全局变量也放在常量区。 全局变量,临时变量,静态变量分别存在哪里 局部变量保存在栈中,全局变量和静态变量存储在静态数据区。 堆分为哪几块,比如说新生代老生代,那么新生代又分为什么? java垃圾收集管理器的主要区域,因此很多时候被称为“GC堆”。 分为新生代和老年代; 新生代分为:Eden和Survivor。

● 手写代码:给出一个int类型123,写一个函数,返回反转的值321 void my_itoa(int x,char *s,char radix) {  char zm[37]="0123456789abcdefghijklmnopqrstuvwxyz";  int i=0;  int sum=x;  while(sum>0)  {  s[i++]=zm[sum%radix];  sum/=radix;  }  }  int atoi_my( char *str)  {  int s=0;  while(*str>='0'&&*str<='9')  {  s=s*10+*str-'0';  str++;  if(s<0)  {  s=2147483647;  break;  }  }  return s; //  }

● 请你说一说接口有什么限制 1、变量会被隐式地指定为public static final变量,并且只能是public static final变量,用private修饰会报编译错误 2、方法会被隐式地指定为public abstract方法且只能是public abstract方法(用其他关键字,比如private、protected、static、 final等修饰会报编译错误) 3、接口中所有的方法不能有具体的实现,也就是说,接口中的方法必须都是抽象方法

● 请你说一说内存溢出和内存泄漏是怎么回事 内存溢出out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。 内存泄露memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。 内存泄漏可以分为四类: 1、常发性内存泄漏,发生内存泄漏的代码会被多次执行到,每次执行都会导致内存泄漏 2、偶发性内存泄漏:发生内存泄漏的代码只有在某些特定环境或操作过程下才会发生, 3、一次性内存泄漏,发生内存泄漏的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄漏。 4、隐式内存泄漏。程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。 从用户使用程序的角度来看,内存泄漏本身不会产生什么危害,真正有危害的是内存泄漏的堆积,这会最终消耗尽系统所有的内存。 内存溢出常见原因:

1.内存中加载的数据量过于庞大,如一次从数据库取出过多数据; 2.集合类中有对对象的引用,使用完后未清空,使得JVM不能回收; 3.代码中存在死循环或循环产生过多重复的对象实体; 4.使用的第三方软件中的BUG; 5.启动参数内存值设定的过小 解决方案: 1、修改JVM参数,直接增加内存 2、检查错误日志,查看内存溢出错误前是否有其他异常错误 3、对代码进行走查分析,找出可能发生内存溢出的位置

● 请你介绍一下HashMap,HashTable,ConcurrentHashMap

参考回答: 1、HashTable与HashMap (1)HashTable和HashMap都实现了Map接口,但是HashTable的实现是基于Dictionary抽象类。 (2)在HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,既可以表示HashMap中没有该键,也可以表示该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。而在HashTable中,无论是key还是value都不能为null。 (3)HashTable是线程安全的,它的方法是同步了的,可以直接用在多线程环境中。HashMap则不是线程安全的。在多线程环境中,需要手动实现同步机制。 2、更好的选择:ConcurrentHashMap Java 5中新增了ConcurrentMap接口和它的实现类ConcurrentHashMap。 ConcurrentHashMap提供了和HashTable以及SynchronizedMap中所不同的锁机制。HashTable中采用的锁机制是一次锁住整个hash表,从而同一时刻只能有一个线程对其进行操作;而ConcurrentHashMap中则是一次锁住一个桶。ConcurrentHashMap默认将hash表分为16个桶,诸如get,put,remove等常用操作只锁住当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。 上面说到的16个线程指的是写线程,而读操作大部分时候都不需要用到锁。只有在size等操作时才需要锁住整个hash表。 在迭代方面,ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是,在改变时new新的数据从而不影响原有的数据,iterator完成后再将头指针替换为新的数据,这样iterator可以使用原来老的数据,而写线程也可以并发的完成改变。

● 请你说一下Hashset有什么特性,以及hashset判断存入的对象是否重复是如何比较的 HashSet是Set接口的实现类,因此,HashSet中的元素也是不能重复的。HashCode判断元素重复的标准时,首先计算新添加元素的hashCode值,当不重复是,则直接加入到该集合中,若发生重复,也称发生了碰撞,则进一步调用equals判断元素是否在逻辑上相同。

● 请你说一下Java的反射,你目前主要用他做什么,以及Java的泛型,他的主要作用是什么 JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。Java反射可以用来获取一个class对象或实例化一个class表示的类的对象,还可以获取构造方法,成员变量,成员方法。 java中泛型的引入主要是为了解决两个方面的问题:1.集合类型元素在运行期出现类型装换异常,增加编译时类型的检查,2. 解决的时重复代码的编写,能够复用算法。下面通过例子来说明编译器的类型检查。

JDK 和 JRE 的区别是什么? JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。 JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。 总结: JDK 包含了 JRE,同时还包含了编译 java 源码的编译器 javac,还包含了很多 java 程序调试和分析的工具。简单来说:如果你需要运行 java 程序,只需安装 JRE 就可以了,如果你需要编写 java 程序,就需要安装 JDK。

. Java中有哪些数据类型? Java中数据类型分为基本数据类型和引用数据类型2种   基本类型:byte(默认值0,占1字节)、short(默认值0,占2字节)、int(默认值0,占4字节)、long(默认值0,占8字节)、float(默认值0.0,占4字节)、double(默认值0.0,占8字节)、char(默认值u0000,占2字节)、boolean(默认值false) 引用类型:类(默认值null)、接口(默认值null)、数组(默认值null)

 == 和 equals 的区别是什么? 基本类型和引用类型比较,== 的作用效果是不同的。   基本类型:比较的是值是否相同 引用类型:比较的是引用是否相同 总结:== 对于基本类型比较的是值,对于引用类型比较的是引用;而 equals 默认情况下是引用比较,只是很多类重写了 equals 方法,比如 String、Integer 等把它变成了值比较,所以一般情况下 equals 比较的是值是否相等。

 Java 中 final 关键字的作用是什么? final 修饰的类叫最终类,不能被继承 final 修饰的方法叫最终方法,不能被重写,但可以被继承 final 修饰的变量叫常量,必须初始化,初始化之后值不能被修改

String、StringBuffer、StringBuilder 的区别是什么? String 是字符串常量,每次操作都会生产新的对象,适用于少量字符串操作的情况;StringBuffer、StringBuilder 是字符串变量,StringBuffer 是线程安全的,而 StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。

String 类中常用方法都有哪些? String str = " app le "; // indexOf(): 返回指定字符的索引 // charAt(): 返回指定索引处的字符 // replace(): 字符串替换 // trim(): 去除字符串两端空白 // split(): 分割字符串,返回一个分割后的字符串数组 // getBytes(): 返回字符串的 byte 类型数组 // length(): 返回字符串长度、 // toLowerCase(): 将字符串转成小写字母 // toUpperCase(): 将字符串转成大写字符 // substring(): 截取字符串 // equals(): 字符串比较

普通类和抽象类的区别是什么? 普通类不能包含抽象方法,抽象类可以有抽象方法 普通类可以直接实例化,抽象类不能直接实例化

 抽象类能使用 final 修饰吗? 不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类。

Java 中 IO 流分几种? 按功能分:输入流(input)、输出流(output) 按类型分:字节流、字符流 字节流和字符流的区别:字节流按 8 位传输,以字节为单位输入输出数据;字符流按 16 位传输,以字符为单位输入输出数据。

BIO、NIO、AIO 有什么区别? BIO:同步阻塞 IO,就是我们平常使用的传统 IO,服务器的实现模式是一个请求连接一个线程,并发处理能力低,可能造成不必要的线程开销,严重的还将导致服务器内存溢出。 NIO:同步非阻塞 IO,是传统 IO 的升级,服务器的实现模式是多个请求一个线程,即请求会注册到多路复用器Selector上,多路复用器轮询到连接有IO请求时才启动一个线程处理。 AIO:异步非阻塞 IO,是 NIO 的升级,也叫 NIO2,服务器的实现模式为多个有效请求一个线程,客户端的IO请求都是由OS先完成再通知服务器应用去启动线程处理(回调)。

 Files 的常用方法都有哪些? // Files.exists():检测文件路径是否存在 // Files.createFile():创建文件 Path path2 = Paths.get("C:\Users\e-yangfangchao\Desktop\test\a.txt"); // Files.createDirectory():创建文件夹 // Files.delete():删除一个文件或目录 // Files.copy():复制文件 // Files.move():移动文件 // Files.size():查看文件个数 // Files.read():读取文件 // Files.write():写入文件

Collection 和 Collections 的区别是什么? java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行基本操作的通用接口方法。Collection 接口在 Java 类库中有很多具体的实现。Collection 接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有 List 与 Set。 Collections 则是集合类的一个工具类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。

List、Set、Map 之间的区别是什么? 1) 元素重复性: ① List允许有重复的元素。任何数量的重复元素都可以在不影响现有重复元素的值及其索引的情况下插入到List集合中; ② Set集合不允许元素重复。Set以及所有实现了Set接口的类都不允许重复值的插入,若多次插入同一个元素时,在该集合中只显示一个; ③ Map以键值对的形式对元素进行存储。Map不允许有重复键,但允许有不同键对应的重复的值; 2) 元素的有序性: ① List及其所有实现类保持了每个元素的插入顺序; ② Set中的元素都是无序的;但是某些Set的实现类以某种殊形式对其中的元素进行排序,如:LinkedHashSet按照元素的插入顺序进行排序; ③ Map跟Set一样对元素进行无序存储,但其某些实现类对元素进行了排序。如:TreeMap根据键对其中的元素进行升序排序; 3) 元素是否为空值: ① List允许任意数量的空值; ② Set最多允许一个空值的出现;[ 当向Set集合中添加多个null值时,在该Set集合中只会显示一个null元素] ③  Map只允许出现一个空键,但允许出现任意数量的空值 总结: List中的元素,有序、可重复、可为空;     Set中的元素,无序、不重复、只有一个空元素;     Map中的元素,无序、键不重,值可重、可一个空键、多可空值;

 HashMap 和 Hashtable 的区别是什么? hashMap 去除了 HashTable 的 contains() 方法,但是加上了 containsValue() 和 containsKey() 方法。 hashTable 是线程同步的,而 HashMap 是非同步的,HashMap 效率上比 hashTable 要高。 hashMap 允许空键空值,而 hashTable 不允许。

 如何决定使用 HashMap 还是 TreeMap? 在 Map 中做插入、删除和定位元素这类操作,HashMap 是最好的选择。假如你需要对一个有序的 key 集合进行遍历,TreeMap 是更好的选择。

HashMap 的实现原理是什么? HashMap概述: HashMap 是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 HashMap的数据结构: HashMap 实际上是一个“链表散列”的数据结构,即数组和链表的结合体。 当我们往 HashMap 中 put 元素时,首先根据 key 的 hashcode 重新计算 hash 值,根据 hash 值得到这个元素在数组中的位置(下标),如果该数组在该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾。如果数组中该位置没有元素,就直接将该元素放到数组的该位置上。 Jdk1.8 中对 HashMap 的实现做了优化,当链表中的节点数据超过八个之后,该链表会转为红黑树来提高查询效率,从原来的 O(n) 到 O(logn)。

HashSet 的实现原理是什么? HashSet 实现 Set 接口,由哈希表(实际上是一个 HashMap 实例)支持。它不保证 set 的迭代顺序;特别是它不保证该顺序恒久不变;此类允许使用 null 元素;HashSet 中不允许有重复元素。 HashSet 是基于 HashMap 实现的,HashSet 中的元素都存放在 HashMap 的 key 上面,而 value 中的值都是统一的一个 private static final Object PRESENT = new Object()。HashSet跟HashMap一样,都是一个存放链表的数组。

 ArrayList 和 LinkedList 的区别是什么? ArrayList 底层基于动态数组,随机访问元素效率高,向集合尾部添加元素效率高,删除或者在其他位置添加元素效率低(需要移动数组);LinkedList 基于链表的动态数组,数据添加和删除效率高,只需要改变指针指向即可,但是访问数据的平均效率低,需要对链表进行遍历。

 如何实现数组和 List 之间的转换? // List 转 数组 String[] strArr = {"apple","pear","banana","peach"}; List list = Arrays.asList(strArr); // 数组 转 List String[] arr = (String[]) list.toArray();

Array 和 ArrayList 的区别是什么? Array 可以容纳基本类型和对象,而 ArrayList 只能容纳对象。 Array 是指定大小的,而 ArrayList 初始化大小是固定的。 Array 没有提供 ArrayList 那么多功能,比如addAll、removeAll和iterator等。

 Iterator 怎么使用?有什么特点? Iterator 功能比较简单,并且只能单向移动 使用方法 iterator() 要求容器返回一个 Iterator。第一次调用 Iterator 的 next() 方法时,它返回序列的第一个元素。注意:iterator() 方法是 java.lang.Iterable 接口,被 Collection 继承。 使用 hasNext() 检查序列中是否还有元素 使用 next() 获得序列中的下一个元素 使用 remove() 将迭代器新返回的元素删除 Iterator 是 Java 迭代器最简单的实现,为 List 设计的 ListIterator 具有更多的功能,它可以从两个方向遍历 List,也可以从 List 中插入和删除元素。     public static void main(String[] args) {         // List         ArrayList list = new ArrayList<>();         list.add("apple");         list.add("pear");         list.add("banana");         Iterator iterator = list.iterator();         while (iterator.hasNext()){             String s = iterator.next();             if ("apple".equals(s)){                 iterator.remove();             }         }         list.forEach(item -> System.out.println(item));         // Map         Map map=new HashMap<>();         map.put("pig","猪");         map.put("cat","猫");         map.put("dog","狗");         Iterator iterator1 = map.keySet().iterator();         Iterator iterator2 = map.values().iterator();         while (iterator1.hasNext()){             System.out.println(iterator1.next());         }         while (iterator2.hasNext()){             System.out.println(iterator2.next());         }     }

并行和并发的区别是什么? 并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生 并行是在不同实体上的多个事件,并发是在同一实体上的多个事件 在一台处理器上“同时”处理多个任务,在多台处理器上同时处理多个任务。如 hadoop 分布式集群 普通解释: 并发:交替做不同事情的能力 并行:同时做不同事情的能力 专业术语: 并发:不同的代码块交替执行 并行:不同的代码块同时执行

线程和进程的区别是什么? 进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程。进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高。线程是进程的一个实体,是 cpu 调度和分派的基本单位,是比程序更小的能独立运行的基本单位。同一进程中的多个线程之间可以并发执行。

sleep()和wait()的区别是什么? sleep():是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。因为 sleep() 是 static 静态的方法,他不能改变对象的锁,当一个 synchronized 块中调用了 sleep() 方法,线程虽然进入休眠,但是对象的锁没有被释放,其他线程依然无法访问这个对象。 wait():是 Object 类的方法,当一个线程执行到 wait 方法时,它就进入到一个和该对象相关的等待池,同时释放对象的锁,使其他线程能够访问,可以通过 notify,notifyAll 方法来唤醒等待的线程。

子类继承父类,都存在构造方法,静态方法和非静态方法,那么执行顺序是什么

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/871527.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号