Java⾯试题(JVM、并发、集合)
JVM:
Q:JVM内存是如何划分的?
Q:谈谈垃圾回收机制?为什么引⽤计数器判定对象是否回收不可⾏?知道哪些垃圾回收算法?
java中是通过引⽤来和对象进⾏关联的,也就是说如果要操作对象,必须通过引⽤来进⾏。那么很显然⼀个简单的办法就是通过引⽤计数来判断⼀个对象是否可以被回收。不失⼀般性,如果⼀个对象没有任何引⽤与之关联,则说明该对象基本不太可能在其他地⽅被使⽤到,那么这个对象就成为可被回收的对象了。这种⽅式成为引⽤计数法。
这种⽅式的特点是实现简单,⽽且效率较⾼,但是它⽆法解决循环引⽤的问题,因此在Java中并没有采⽤这种⽅式
Q:Java中引⽤有⼏种类型?在Android中常⽤于什么情景?
1)强引⽤,就是指在程序代码中普遍存在的,类似于Object obj=new Object()这类的引⽤,只要强引⽤还存在,垃圾收集器永远不会回收掉被引⽤的对象。
2)软引⽤是⽤来描述⼀些还有⽤但并⾮必要的对象,对于软引⽤关联的对象,在系统将要发⽣溢出异常之前,将会把这些对象列进回收范围之中进⾏⼆次回收。如果这次回收还没有⾜够内存,才会抛出内存溢出异常。
3)弱引⽤也⽤来描述⾮必须对象的,但是它的强度⽐软引⽤更弱⼀点,被弱引⽤关联的对象只能⽣存到下⼀次垃圾收集发⽣之前。当垃圾收集⼯作时,⽆论当前内存是否⾜够,都会回收掉只被弱引⽤的对象(Android多⽤于静态内部类+弱引⽤的⽅式创建Handler,防⽌内存泄漏)
4)虚引⽤也成为幽灵引⽤或者幻影引⽤,它是最弱的⼀种引⽤关系,⼀个对象是否有虚引⽤的存在,完全不会对其⽣存时间构成影响,也⽆法通过虚引⽤来取得⼀个对象实例。为⼀个对象设置成虚引⽤关联的唯⼀⽬的就是能够在这个对象被收集器回收时收到⼀个系统通知。虚引⽤与软引⽤和弱引⽤的
⼀个区别在于:虚引⽤必须和引⽤队列(ReferenceQueue)联合使⽤。当垃圾回收器准备回收⼀个对象时,如果发现它还有虚引⽤,就会在回收对象的内存之前,把这个虚引⽤加⼊到与之关联的引⽤队列中。程序可以通过判断引⽤队列中是否已经加⼊了虚引⽤,来了解被引⽤的对象是否将要被垃圾回收。程序如果发现某个虚引⽤已经被加⼊到引⽤队列,那么就可以在所引⽤的对象的内存被回收之前采取必要的⾏动。
Q:类加载的全过程是怎样的?什么是双亲委派模型?
1、类加载过程:加载、验证、准备、解析、初始化
加载在加载阶段,虚拟机主要完成三件事:
1.通过⼀个类的全限定名来获取定义此类的⼆进制字节流。
2.将这个字节流所代表的静态存储结构转化为⽅法区域的运⾏时。
3.在堆中⽣成⼀个代表这个类的java.lang.Class对象,作为⽅法区域数据的访问⼊⼝
验证阶段作⽤是保证Class⽂件的字节流包含的信息符合JVM规范,不会给JVM造成危害。如果验证失败,就会抛出⼀个java.lang.VerifyError异常或其⼦类异常。验证过程分为四个阶段
验证java.lang.VerifyError异常或其⼦类异常。验证过程分为四个阶段
1.⽂件格式验证:验证字节流⽂件是否符合Class⽂件格式的规范,并且能被当前虚拟机正确的处理。
2.元数据验证:是对字节码描述的信息进⾏语义分析,以保证其描述的信息符合Java语⾔的规范。
3.字节码验证:主要是进⾏数据流和控制流的分析,保证被校验类的⽅法在运⾏时不会危害虚拟机。
4.符号引⽤验证:符号引⽤验证发⽣在虚拟机将符号引⽤转化为直接引⽤的时候,这个转化动作将在解析阶段中发⽣。
准备准备阶段为变量分配内存并设置类变量的初始化。在这个阶段分配的仅为类的变量(static修饰的变量),⽽不包括类的实例变量。对已⾮final的变量,JVM会将其设置成“零值”,⽽不是其赋值语句的值:
pirvate static int size = 12;
那么在这个阶段,size的值为0,⽽不是12。 final修饰的类变量将会赋值成真实的值。
解
析
解析过程是将常量池内的符号引⽤替换成直接引⽤。主要包括四种类型引⽤的解析。类或接⼝的解析、字段解析、⽅法解析、接⼝⽅法解析。
初始化在准备阶段,类变量已经经过⼀次初始化了,在这个阶段,则是根据程序员通过程序制定的计划去初始化类的变量和其他资源。这些资源有static{}块,构造函数,⽗类的初始化等。
⾄于使⽤和卸载阶段阶段,这⾥不再过多说明,使⽤过程就是根据程序定义的⾏为执⾏,卸载由GC完成。
使
⽤
新线程---程序计数器----jvm栈执⾏(对象引⽤)-----堆内存(直接引⽤)----⽅法区
卸
载
GC垃圾回收
2、双亲委派机制、意义、⽅法
双亲委派模型的⼯作过程是:如果⼀个类加载器收到了类加载请求,它⾸先不会⾃⼰去加载这个类,⽽是将这个类委托给⽗加载器去完成,每⼀层加载器都是如此,因此所有加载请求最终应该传送到顶层的启动类加载器中,只有⽗加载器反馈⽆法加载时,⼦加载器才会⾃⼰尝试去加载。
机制启动(Bootstrap)类加载器----->标准扩展(Extension)类加载器--->系统(System)类加载器---->上下⽂(Custom)类加载器
从左到右加载:⾸先将加载任务委托给⽗类加载器,依次递归,如果⽗类加载器可以完成类加载任务,就成功返回;只有⽗类加载器⽆法完成此加载任务时,才⾃⼰去加载。
意
义
防⽌内存中出现多份同样的字节码
java定义一维数组并赋值
使⽤委托机制,会递归的向⽗类查,如果类A中引⽤了类B,Java虚拟机将使⽤加载类A的类加载器去加载类B,如果A加载器已加载类A,那么B使⽤A的类加载器进⾏加载时,就不会在加载类A的字节码了
⽅法《1》启动(Bootstrap)类加载器
《2》标准扩展(Extension)类加载器《3》应⽤程序类加载器(Application )《4》上下⽂(Custom)类加载器
Q:⼯作内存和主内存的关系?在Java内存模型有哪些可以保证并发过程的原⼦性、可见性和有序性的措施?所有线程共享主内存(主存);每个线程有⾃⼰的⼯作内存(寄存器)
保证措施:
Q:JVM、Dalvik、ART的区别?
什么是Dalvik:
Dalvik是Google公司⾃⼰设计⽤于Android平台的Java虚拟机。它可以⽀持已转换为.dex(即Dalvik Executable)格式的Java应⽤程序的运⾏,.dex格式是专为Dalvik应⽤设计的⼀种压缩格式,适合内存
和处理器速度有限的系统。Dalvik经过优化,允许在有限的内存中同时运⾏多个虚拟机的实例,并且每⼀个Dalvik应⽤作为独⽴的Linux进程执⾏。独⽴的进程可以防⽌在虚拟机崩溃的时候所有程序都被关闭。
什么是ART:
ART代表AndroidRuntime,Dalvik是依靠⼀个Just-In-Time(JIT)编译器去解释字节码,运⾏时编译后的应⽤代码都需要通过⼀个解释器在⽤户的设备上运⾏,这⼀机制并不⾼效,但让应⽤能更容易在不同硬件和架构上运⾏。
ART则完全改变了这种做法,在应⽤安装的时候就预编译字节码到机器语⾔,这⼀机制叫Ahead-Of-Time(AOT)预编译。在移除解释代码这⼀过程后,应⽤程序执⾏将更有效率,启动更快。
1. Dalvik与JVM的区别
(1)Dalvik指令集是基于寄存器的架构,执⾏特有的⽂件格式——dex字节码(适合内存和处理器速度有限的系统)。⽽JVM是基于栈的。相对于基于栈的JVM⽽⾔,基于寄存器的Dalvik VM实现虽然牺牲了⼀些平台⽆关性,但是它在代码的执⾏效率上要更胜⼀筹。
(2)每⼀个Android 的App是独⽴跑在⼀个VM中的。因此⼀个App crash只会影响到⾃⾝的VM,不会
影响到其他。Dalvik经过优化,允许在有限的内存中同时运⾏多个虚拟机的实例,并且每⼀个 Dalvik应⽤作为⼀个独⽴的Linux进程执⾏。
2. Dalvik与ART的区别
(1)在Dalvik下,应⽤每次运⾏都需要通过即时编译器(JIT)将字节码转换为机器码,即每次都要编译加运⾏,这虽然会使安装过程⽐较快,但是会拖慢应⽤的运⾏效率。⽽在ART 环境中,应⽤在第⼀次安装的时候,字节码就会预编译(AOT)成机器码,这样的话,虽然设备和应⽤的⾸次启动(安装慢了)会变慢,但是以后每次启动执⾏的时候,都可以直接运⾏,因此运⾏效率会提⾼。
(2)ART占⽤空间⽐Dalvik⼤(原⽣代码占⽤的存储空间更⼤,字节码变为机器码之后,可能会增加10%-20%),这也是著名的“空间换时间⼤法"。
(4)预编译也可以明显改善电池续航,因为应⽤程序每次运⾏时不⽤重复编译了,从⽽减少了 CPU 的使⽤频率,降低了能耗。
Q:Java中堆和栈的区别?
在函数中定义的⼀些基本类型的变量和对象的引⽤变量都是在函数的栈内存中分配,当在⼀段代码块定义⼀个变量时,Java 就在栈中为这个变量分配内存空间,当超过变量的作⽤域后,Java 会⾃动释
放掉为该变量分配的内存空间,该内存空间可以⽴即被另作它⽤。
堆内存⽤来存放由 new 创建的对象和数组,在堆中分配的内存,由 Java 虚拟机的⾃动垃圾回收器来管理。在堆中产⽣了⼀个数组或者对象之后,还可以在栈中定义⼀个特殊的变量,让栈中的这个变量的取值等于数组或对象在堆内存中的⾸地址,栈中的这个变量就成了数组或对象的引⽤变量,以后就可以在程序中使⽤栈中的引⽤变量来访问堆中的数组或者对象,引⽤变量就相当于是为数组或者对象起的⼀个名称。引⽤变量是普通的变量,定义时在栈中分配,引⽤变量在程序运⾏到其作⽤域之外后被释放。⽽数组和对象本⾝在堆中分配,即使程序运⾏到使⽤ new 产⽣数组或者对象的语句所在的代码块之外,数组和对象本⾝占据的内存不会被释放,数组和对象在没有引⽤变量指向它的时候,才变为垃圾,不能在被使⽤,但仍然占据内存空间不放,在随后的⼀个不确定的时间被垃圾回收器收⾛(释放掉)。
这也是 Java ⽐较占内存的原因,实际上,栈中的变量指向堆内存中的变量,这就是 Java 中的指针!
并发:
Q:开启⼀个线程的⽅法有哪些?销毁⼀个线程的⽅法呢?
创建线程的最简单的⽅法就是创建⼀个实现Runnable 接⼝的类。Runnable抽象了⼀个执⾏代码单元。你可以通过实现Runnable接⼝的⽅法创建每⼀个对象的线程。为实现Runnable 接⼝,⼀个类仅需实现⼀个run()的简单⽅法
创建线程的另⼀个途径是创建⼀个新类来扩展Thread类,然后创建该类的实例。当⼀个类继承Thread时,它必须重载run()⽅法
销毁线程:
(1)设置退出标志,使线程正常退出,也就是当run()⽅法完成后线程终⽌
(2)使⽤interrupt()⽅法中断线程
(3)使⽤stop⽅法强⾏终⽌线程(不推荐使⽤,Thread.stop, Thread.suspend, sume 和
Runtime.runFinalizersOnExit 这些终⽌线程运⾏的⽅法已经被废弃,使⽤它们是极端不安全的!)
stop()⽅法太过于暴⼒,会强⾏把执⾏⼀半的线程终⽌。这样会就不会保证线程的资源正确释放,通常是没有给与线程完成资源释放⼯作的机会,因此会导致程序⼯作在不确定的状态下
Q:同步和⾮同步、阻塞和⾮阻塞的概念
当两个或两个以上的线程需要共享资源,它们需要某种⽅法来确定资源在某⼀刻仅被⼀个线程占⽤。达到此⽬的的过程叫做同步(synchronization)
阻塞是指在某个动作未执⾏完之前,线程⼀直处于挂起状态
Q:Thread的join()有什么作⽤?
如果所调⽤线程仍在运⾏,isAlive()⽅法返回true,如果不是则返回false。但isAlive()很少⽤到,等待线程结束的更常⽤的⽅法是调⽤join(),描述如下:
final void join( ) throws InterruptedException
该⽅法等待所调⽤线程结束。
Q:线程的有哪些状态?
运⾏,挂起,阻塞,终⽌
Q:什么是线程安全?保障线程安全有哪些⼿段?
多线程访问同⼀个资源时,得到的是正确的结果。
线程同步,⽤synchronized修饰,调⽤被synchronized关键字修饰的⽅法。当⼀个线程在⼀个同步⽅法内部,所有试图调⽤该⽅法(或其他同步⽅法)的同实例的其他线程必须等待:ReentrantLock和synchronized的区别?
Q:synchronized和volatile的区别?
1. volatile本质是在告诉jvm当前变量在寄存器(⼯作内存)中的值是不确定的,需要从主存中读取; synchronized则是锁定当前变
量,只有当前线程可以访问该变量,其他线程被阻塞住。
2. volatile仅能使⽤在变量级别;synchronized则可以使⽤在变量、⽅法、和类级别的
3. volatile仅能实现变量的修改可见性,不能保证原⼦性;⽽synchronized则可以保证变量的修改可见性和原⼦性
4. volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
5. volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化
Q:synchronized同步代码块还有同步⽅法本质上锁住的是谁?为什么?
锁住的是对象。
Q:sleep()和wait()的区别?
sleep()⽅法导致了程序暂停执⾏指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了⼜会⾃动恢复运⾏状态。
⽽当调⽤wait()⽅法的时候,线程会放弃对象锁,进⼊等待此对象的等待锁定池,只有针对此对象调⽤notify()⽅法后本线程才进⼊对象锁定池准备
sleep() 和 wait() 的区别就是 调⽤sleep⽅法的线程不会释放对象锁,⽽调⽤wait() ⽅法会释放对象锁
集合:
Q:Java集合框架中有哪些类?都有什么特点
Q:集合、数组、泛型的关系,并⽐较
集合可放任意类型的元素,会⾃动增⼤,取出时要做类型转换
泛型集合只能放定义类型的元素,会⾃动增⼤,取出时不⽤做类型转换
数组只能放定义类型的元素,不会⾃动增⼤,取出时不⽤做类型转换
集合⾥所有的元素都是Object,如果元素是值类型,会⾃动装箱。
泛型集合可以定义元素类型,相对于集合,泛型集合可以避免装箱拆箱,提⾼性能,同时程序具有更好的可读性。
数组本⾝可以认为是⼀种泛型集合结构体
Q:ArrayList和LinkList的区别?
1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
3.对于新增和删除操作add和remove,LinedList⽐较占优势,因为ArrayList要移动数据。
ArrayList内部是使⽤可増长数组实现的,所以是⽤get和set⽅法是花费常数时间的,但是如果插⼊元素和删除元素,除⾮插⼊和删除的位置都在表末尾,否则代码开销会很⼤,因为⾥⾯需要数组的移动。
LinkedList是使⽤双链表实现的,所以get会⾮常消耗资源,除⾮位置离头部很近。但是插⼊和删除元
素花费常数时间。
Q:ArrayList和Vector的区别?
Vector的⽅法都是同步的(Synchronized),是线程安全的(thread-safe),⽽ArrayList的⽅法不是,由于线程的同步必然要影响性能,因此,ArrayList的性能⽐Vector好。
当Vector或ArrayList中的元素超过它的初始⼤⼩时,Vector会将它的容量翻倍,⽽ArrayList只增加50%的⼤⼩,这样,ArrayList就有利于节约内存空间。
Q:HashSet和TreeSet的区别?
HashSet有以下特点
不能保证元素的排列顺序,顺序有可能发⽣变化
不是同步的
集合元素可以是null,但只能放⼊⼀个null
当向HashSet集合中存⼊⼀个元素时,HashSet会调⽤该对象的hashCode()⽅法来得到该对象的hash
Code值
TreeSet是SortedSet接⼝的唯⼀实现类,TreeSet可以确保集合元素处于排序状态。TreeSet⽀持两种排序⽅式,⾃然排序 和定制排序,其中⾃然排序为默认的排序⽅式。向TreeSet中加⼊的应该是同⼀个类的对象。
Q:HashMap和Hashtable的区别?
⽐较HashMap HashTable
存储结构数组 + 链表/红⿊树数组 + 链表
扩容⽅式oldCap * 2oldCap * 2 + 1
K,V能否为null key, value 均可以为 null key, value 均不可以为 null
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论