Java虚拟机载入(.class)与Java虚拟机加载(.class)是同一个过程同一个意思吗?谢谢各位牛人

Java虚拟机是如何加载Java类的?  这个问题也就是面试常问到的Java类加载机制。在年初面试百战之后,菜鸟喜鹊也是能把这流程倒背如流啊!但是,也只是字面上的背诵,根本就是像上学时背书考试一样。

tonight ! 我们把它映射到实战里,看看如何用代码说明这个流程。

一、类加载机制(理论部分)

 类加载机制有三大过程:加载、链接、初始化。其中链接又细分为验证、准备及解析。

Java语言的类型分为两大类:基本类型和引用类型。Java的基本类型是由Java虚拟机预先定义好的。而引用类型又分为:数组类、类、接口、泛型参数。在JVM中,只存在数组类、类、接口三类,而数组类是直接由Java虚拟机直接生成的,其他两类则有字节流而来。

字节流又是怎么来呢?最常见的还是从字节码文件而来(还可以从网络等而来)。所以,我就以字节码文件分析一下加载机制。

}

封装,继承,多态.这个应该是人人皆知.

允许不同类对象对同一消息做出响应,即同一消息可以根据发送对象的不同而采用多种不同的行为方式(发送消息就是函数调用).主要有以下优点:

1、 可替换性:多态对已存在代码具有可替换性.
2、 可扩充性:增加新的子类不影响已经存在的类结构.
3、 接口性:多态是超累通过方法签名,想子类提供一个公共接口,由子类来完善或者重写它来实现的.

实现多态主要有以下三种方式:
2、 继承父类重写方法
3、 同一类中进行方法重载

虚拟机是如何实现多态的

动态绑定技术(dynamic binding),执行期间判断所引用对象的实际类型,根据实际类型调用对应的方法.

接口的意义用三个词就可以概括:规范,扩展,回调.

抽象类的意义可以用三句话来概括:

1、 为其他子类提供一个公共的类型
2、 封装子类中重复定义的内容
3、 定义抽象方法,子类虽然有不同的实现,但是定义时一致的

抽象类可以有默认的方法实现 ,java 8之前,接口中不存在方法的实现.
子类使用extends关键字来继承抽象类.如果子类不是抽象类,子类需要提供抽象类中所声明方法的实现. 子类使用implements来实现接口,需要提供接口中所有声明的实现.
抽象类中可以有构造器,
接口则是完全不同的类型
接口默认是public,不能使用其他修饰符
一个子类只能存在一个父类 一个子类可以存在多个接口
想抽象类中添加新方法,可以提供默认的实现,因此可以不修改子类现有的代码 如果往接口中添加新方法,则子类中需要实现该方法.

父类的静态方法能否被子类重写

不能.子类继承父类后,有相同的静态方法和非静态,这是非静态方法覆盖父类中的方法(即方法重写),父类的该静态方法被隐藏(如果对象是父类则调用该隐藏的方法),另外子类可集成父类的静态与非静态方法,至于方法重载我觉得它其中一要素就是在同一类中,不能说父类中的什么方法与子类里的什么方法是方法重载的体现.

不可变对象指对象一旦被创建,状态就不能再改变。任何修改都会创建一个新的对象,如 String、Integer及其它包装类。

能否创建一个包含可变对象的不可变对象?

当然可以创建一个包含可变对象的不可变对象的,你只需要谨慎一点,不要共享可变对象的引用就可以了,如果需要变化时,就返回原对象的一个拷贝。最常见的例子就是对象中包含一个日期对象的引用.

java 创建对象的几种方式

前2者都需要显式地调用构造方法. 造成耦合性最高的恰好是第一种,因此你发现无论什么框架,只要涉及到解耦必先减少new的使用.

Object中有哪些公共方法?

java当中的四种引用

强引用,软引用,弱引用,虚引用.不同的引用类型主要体现在GC上:

1、 强引用:如果一个对象具有强引用,它就不会被垃圾回收器回收。即使当前内存空间不足,JVM也不会回收它,而是抛出 OutOfMemoryError 错误,使程序异常终止。如果想中断强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样一来的话,JVM在合适的时间就会回收该对象
2、 软引用:在使用软引用时,如果内存的空间足够,软引用就能继续被使用,而不会被垃圾回收器回收,只有在内存不足时,软引用才会被垃圾回收器回收。
3、 弱引用:具有弱引用的对象拥有的生命周期更短暂。因为当 JVM 进行垃圾回收,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。不过由于垃圾回收器是一个优先级较低的线程,所以并不一定能迅速发现弱引用对象
4、 虚引用:顾名思义,就是形同虚设,如果一个对象仅持有虚引用,那么它相当于没有引用,在任何时候都可能被垃圾回收器回收。

这点在四种引用类型中已经做了解释,这里简单说明一下即可:
虽然 WeakReference 与 SoftReference 都有利于提高 GC 和 内存的效率,但是 WeakReference ,一旦失去最后一个强引用,就会被 GC 回收,而软引用虽然不能阻止被回收,但是可以延迟到 JVM 内存不足的时候。

为什么要有不同的引用类型

不像,我们可以控制内存的申请和释放,在Java中有时候我们需要适当的控制对象被回收的时机,因此就诞生了不同的引用类型,可以说不同的引用类型实则是对GC回收时机不可控的妥协.有以下几个使用场景可以充分的说明:

1、 利用软引用和弱引用解决OOM问题:用一个HashMap来保存图片的路径和相应图片对象关联的软引用之间的映射关系,在内存不足时,JVM会自动回收这些缓存图片对象所占用的空间,从而有效地避免了OOM的问题.
2、 通过软引用实现Java对象的高速缓存:比如我们创建了一Person的类,如果每次需要查询一个人的信息,哪怕是几秒中之前刚刚查询过的,都要重新构建一个实例,这将引起大量Person对象的消耗,并且由于这些对象的生命周期相对较短,会引起多次GC影响性能。此时,通过软引用和 HashMap 的结合可以构建高速缓存,提供性能.

==是运算符,用于比较两个变量是否相等,而equals是Object类的方法,用于比较两个对象是否相等.默认Object类的equals方法是比较两个对象的地址,此时和==的结果一样.换句话说:基本类型比较用==,比较的是他们的值.默认下,对象用==比较时,比较的是内存地址,如果需要比较对象内容,需要重写equal方法

hashCode()是Object类的一个方法,返回一个哈希值.如果两个对象根据equal()方法比较相等,那么调用这两个对象中任意一个对象的hashCode()方法必须产生相同的哈希值.
如果两个对象根据eqaul()方法比较不相等,那么产生的哈希值不一定相等(碰撞的情况下还是会相等的.)

将对象放入到集合中时,首先判断要放入对象的hashcode是否已经在集合中存在,不存在则直接放入集合.如果hashcode相等,然后通过equal()方法判断要放入对象与集合中的任意对象是否相等:如果equal()判断不相等,直接将该元素放入集合中,否则不放入.

有没有可能两个不相等的对象有相同的hashcode

有可能,两个不相等的对象可能会有相同的 hashcode 值,这就是为什么在 hashmap 中会有冲突。相等 hashcode 值的规定只是说如果两个对象相等,必须有相同的hashcode 值,但是没有关于不相等对象的任何规定。

可以在hashcode中使用随机数字吗?

不行,因为同一对象的 hashcode 值必须是相同的

如果a 和b 都是对象,则 a==b 是比较两个对象的引用,只有当 a 和 b 指向的是堆中的同一个对象才会返回 true,而 a.equals(b) 是进行逻辑比较,所以通常需要重写该方法来提供逻辑一致性的比较。例如,String 类重写 equals() 方法,所以可以用于两个不同对象,但是包含的字母相同的比较。

false,因为有些浮点数不能完全精确的表示出来。

隐式的将加操作的结果类型强制转换为持有结果的类型。如果两这个整型相加,如 byte、short 或者 int,首先会将它们提升到 int 类型,然后在执行加法操作。如果加法操作的结果比 a 的最大值要大,则 a+b 会出现编译错误,但是 a += b 没问题,如下:
(译者注:这个地方应该表述的有误,其实无论 a+b 的值为多少,编译器都会报错,因为 a+b 操作会将 a、b 提升为 int 类型,所以将 int 类型赋值给 byte 就会编译出错)

内部类可以用多个实例,每个实例都有自己的状态信息,并且与其他外围对象的信息相互独立.在单个外围类当中,可以让多个内部类以不同的方式实现同一接口,或者继承同一个类.创建内部类对象的时刻病不依赖于外部类对象的创建.内部类并没有令人疑惑的”is-a”关系,它就像是一个独立的实体.

内部类提供了更好的封装,除了该外围类,其他类都不能访问

final 是一个修饰符,可以修饰变量、方法和类。如果 final 修饰变量,意味着该变量的值在初始化后不能被改变。finalize 方法是在对象被回收之前调用的方法,给对象自己最后一个复活的机会,但是什么时候调用 finalize 没有保证。finally 是一个关键字,与 try 和 catch 一起用于异常的处理。finally 块一定会被执行,无论在 try 块中是否有发生异常。

java.lang.Cloneable 是一个标示性接口,不包含任何方法,clone 方法在 object 类中定义。并且需要知道 clone() 方法是一个本地方法,这意味着它是由 c 或 c++ 或 其他本地语言实现的。

深拷贝和浅拷贝的区别是什么?

浅拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅拷贝仅仅复制所考虑的对象,而不复制它所引用的对象。

深拷贝:被复制对象的所有变量都含有与原来的对象相同的值,而那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深拷贝把要复制的对象所引用的对象都复制了一遍。


Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。意思就是说,在 32 位 和 64 位 的Java 虚拟机中,int 类型的长度是相同的。

Integer是int的包装类型,在拆箱和装箱中,而知自动转换.int是基本类型,直接存数值,而integer是对象,用一个引用指向这个对象.

Integer 对象会占用更多的内存。Integer是一个对象,需要存储对象的元数据。但是 int 是一个原始类型的数据,所以占用的空间更少。

String和StringBuffer主要区别是性能:String是不可变对象,每次对String类型进行操作都等同于产生了一个新的String对象,然后指向新的String对象.所以尽量不在对String进行大量的拼接操作,否则会产生很多临时对象,导致GC开始工作,影响系统性能.

StringBuffer是对对象本身操作,而不是产生新的对象,因此在通常在有大量拼接的情况下我们建议使用StringBuffer.

什么是编译器常量?使用它有什么风险?

公共静态不可变(public static final )变量也就是我们所说的编译期常量,这里的 public 可选的。实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道这些变量在运行时不能改变。这种方式存在的一个问题是你使用了一个内部的或第三方库中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端仍然在使用老的值,甚至你已经部署了一个新的jar。为了避免这种情况,当你在更新依赖 JAR 文件时,确保重新编译你的程序。

java当中使用什么类型表示价格比较好?

如果不是特别关心内存和性能的话,使用BigDecimal,否则使用预定义精度的 double 类型。

可以使用 String 接收 byte[] 参数的构造器来进行转换,需要注意的点是要使用的正确的编码,否则会使用平台默认编码,这个编码可能跟原来的编码相同,也可能不同。

可以将int强转为byte类型么?会产生什么问题?

我们可以做强制转换,但是Java中int是32位的而byte是8 位的,所以,如果强制转化int类型的高24位将会被丢弃,byte 类型的范围是从-128.到128


你知道哪些垃圾回收算法?

垃圾回收从理论上非常容易理解,具体的方法有以下几种:

如何判断一个对象是否应该被回收

这就是所谓的对象存活性判断,常用的方法有两种:1.引用计数法;2:对象可达性分析.由于引用计数法存在互相引用导致无法进行GC的问题,所以目前JVM虚拟机多使用对象可达性分析.

简单的解释一下垃圾回收

垃圾回收机制最基本的做法是分代回收。内存中的区域被划分成不同的世代,对象根据其存活的时间被保存在对应世代的区域中。一般的实现是划分成3个世代:年轻、年老和永久。内存的分配是发生在年轻世代中的。当一个对象存活时间足够长的时候,它就会被复制到年老世代中。对于不同的世代可以使用不同的垃圾回收算法。进行世代划分的出发点是对应用中对象存活时间进行研究之后得出的统计规律。一般来说,一个应用中的大部分对象的存活时间都很短。比如局部变量的存活时间就只在方法的执行过程中。基于这一点,对于年轻世代的垃圾回收算法就可以很有针对性.

通知GC开始工作,但是GC真正开始的时间不确定.


说说进程,线程,协程之间的区别

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

你了解守护线程吗?它和非守护线程有什么区别

程序运行完毕,jvm会等待非守护线程完成后关闭,但是jvm不会等待守护线程.守护线程最典型的例子就是GC线程

什么是多线程上下文切换

多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另外一个就绪并等待获取CPU执行权的线程的过程。

创建两种线程的方式?他们有什么区别?

1、 Java不支持多继承.因此扩展Thread类就代表这个子类不能扩展其他类.而实现Runnable接口的类还可能扩展另一个类.
2、 类可能只要求可执行即可,因此集成整个Thread类的开销过大.

Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
这其实是很有用的一个特性,因为多线程相比单线程更难、更复杂的一个重要原因就是因为多线程充满着未知性,某条线程是否执行了?某条线程执行了多久?某条线程执行的时候我们期望的数据是否已经赋值完毕?无法得知,我们能做的只是等待这条多线程的任务执行完毕而已。而Callable+Future/FutureTask却可以获取多线程运行的结果,可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务,真的是非常有用。

阻塞指的是暂停一个线程的执行以等待某个条件发生(如某资源就绪),学过的同学对它一定已经很熟悉了。Java 提供了大量方法来支持阻塞,下面让我们逐一分析。

sleep() 允许 指定以毫秒为单位的一段时间作为参数,它使得线程在指定的时间内进入阻塞状态,不能得到CPU 时间,指定的时间一过,线程重新进入可执行状态。 典型地,sleep() 被用在等待某个资源就绪的情形:测试发现条件不满足后,让线程阻塞一段时间后重新测试,直到条件满足为止
两个方法配套使用,suspend()使得线程进入阻塞状态,并且不会自动恢复,必须其对应的resume() 被调用,才能使得线程重新进入可执行状态。典型地,suspend() 和 resume() 被用在等待另一个线程产生的结果的情形:测试发现结果还没有产生后,让线程阻塞,另一个线程产生了结果后,调用 resume() 使其恢复。
yield() 使得线程放弃当前分得的 CPU 时间,但是不使线程阻塞,即线程仍处于可执行状态,随时可能再次分得 CPU 时间。调用 yield() 的效果等价于调度程序认为该线程已执行了足够的时间从而转到另一个线程
两个方法配套使用,wait() 使得线程进入阻塞状态,它有两种形式,一种允许 指定以毫秒为单位的一段时间作为参数,另一种没有参数,前者当对应的 notify() 被调用或者超出指定时间时线程重新进入可执行状态,后者则必须对应的 notify() 被调用.

初看起来它们与 suspend() 和 resume() 方法对没有什么分别,但是事实上它们是截然不同的。区别的核心在于,前面叙述的所有方法,阻塞时都不会释放占用的锁(如果占用了的话),而这一对方法则相反。上述的核心区别导致了一系列的细节上的区别。

首先,前面叙述的所有方法都隶属于 Thread 类,但是这一对却直接隶属于 Object 类,也就是说,所有对象都拥有这一对方法。初看起来这十分不可思议,但是实际上却是很自然的,因为这一对方法阻塞时要释放占用的锁,而锁是任何对象都具有的,调用任意对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放。而调用 任意对象的notify()方法则导致因调用该对象的 wait() 方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。

其次,前面叙述的所有方法都可在任何位置调用,但是这一对方法却必须在 synchronized 方法或块中调用,理由也很简单,只有在synchronized 方法或块中当前线程才占有锁,才有锁可以释放。同样的道理,调用这一对方法的对象上的锁必须为当前线程所拥有,这样才有锁可以释放。因此,这一对方法调用必须放置在这样的 synchronized 方法或块中,该方法或块的上锁对象就是调用这一对方法的对象。若不满足这一条件,则程序虽然仍能编译,但在运行时会出现IllegalMonitorStateException 异常。

wait() 和 notify() 方法的上述特性决定了它们经常和synchronized 方法或块一起使用,将它们和操作系统的进程间通信机制作一个比较就会发现它们的相似性:synchronized方法或块提供了类似于操作系统原语的功能,它们的执行不会受到多线程机制的干扰,而这一对方法则相当于 block 和wakeup 原语(这一对方法均声明为 synchronized)。它们的结合使得我们可以实现操作系统上一系列精妙的进程间通信的算法(如信号量算法),并用于解决各种复杂的线程间通信问题。

第一:调用 notify() 方法导致解除阻塞的线程是从因调用该对象的 wait() 方法而阻塞的线程中随机选取的,我们无法预料哪一个线程将会被选择,所以编程时要特别小心,避免因这种不确定性而产生问题。

第二:除了 notify(),还有一个方法 notifyAll() 也可起到类似作用,唯一的区别在于,调用 notifyAll() 方法将把因调用该对象的 wait() 方法而阻塞的所有线程一次性全部解除阻塞。当然,只有获得锁的那一个线程才能进入可执行状态。

谈到阻塞,就不能不谈一谈死锁,略一分析就能发现,suspend() 方法和不指定超时期限的 wait() 方法的调用都可能产生死锁。遗憾的是,Java 并不在语言级别上支持死锁的避免,我们在编程中必须小心地避免死锁。

以上我们对 Java 中实现线程阻塞的各种方法作了一番分析,我们重点分析了 wait() 和 notify() 方法,因为它们的功能最强大,使用也最灵活,但是这也导致了它们的效率较低,较容易出错。实际使用中我们应该灵活使用各种方法,以便更好地达到我们的目的。

wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法立即释放对象监视器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。

关于这两者已经在上面进行详细的说明,这里就做个概括好了:

  • sleep()睡眠后不出让系统资源,wait让其他线程可以占用CPU

这个其实前面有提到过,FutureTask表示一个异步运算的任务。FutureTask里面可以传入一个Callable的具体实现类,可以对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操作。当然,由于FutureTask也是Runnable接口的实现类,所以FutureTask也可以放入线程池中。

一个线程如果出现了运行时异常怎么办?

如果这个异常没有被捕获的话,这个线程就停止执行了。另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放

如何在两个线程间共享数据

wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。下面是一段标准的使用 wait 和 notify 方法的代码:

线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java提供ThreadLocal类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。

简单说ThreadLocal就是一种以空间换时间的做法在每个Thread里面维护了一个ThreadLocal.ThreadLocalMap把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了.

生产者消费者模型的作用是什么?

(1)通过平衡生产者的生产能力和消费者的消费能力来提升整个系统的运行效率,这是生产者消费者模型最重要的作用
(2)解耦,这是生产者消费者模型附带的作用,解耦意味着生产者和消费者之间的联系少,联系越少越可以独自发展而不需要收到相互的制约

写一个生产者-消费者队列

可以通过阻塞队列实现,也可以通过wait-notify来实现.

该种方式应该最经典,这里就不做说明了

这两个类非常类似,都在java.util.concurrent下,都可以用来表示代码运行到某个点上,二者的区别在于:

  • CyclicBarrier的某个线程运行到某个点上之后,该线程即停止运行,直到所有的线程都到达了这个点,所有线程才重新运行;CountDownLatch则不是,某线程运行到某个点上之后,只是给某个数值-1而已,该线程继续运行

java中的++操作符线程安全么?

不是线程安全的操作。它涉及到多个指令,如读取变量值,增加,然后存储回内存,这个过程可能会出现多个线程交差

你有哪些多线程开发良好的实践?

5、 优先使用并发容器而非同步容器.


Java 中可以创建 volatile类型数组,不过只是一个指向数组的引用,而不是整个数组。如果改变引用指向的数组,将会受到volatile 的保护,但是如果多个线程同时改变数组的元素,volatile标示符就不能起到之前的保护作用了

volatile能使得一个非原子操作变成原子操作吗?

一个典型的例子是在类中有一个 long 类型的成员变量。如果你知道该成员变量会被多个线程访问,如计数器、价格等,你最好是将其设置为 volatile。为什么?因为 Java 中读取 long 类型变量不是原子的,需要分成两步,如果一个线程正在修改该 long 变量的值,另一个线程可能只能看到该值的一半(前 32 位)。但是对一个 volatile 型的 long 或 double

一种实践是用 volatile 修饰 long 和 double 变量,使其能按原子类型来读写。double 和 long 都是64位宽,因此对这两种类型的读是分为两部分的,第一次读取第一个 32 位,然后再读剩下的 32 位,这个过程不是原子的,但 Java 中 volatile 型的 long 或 double 变量的读写是原子的。volatile 修复符的另一个作用是提供内存屏障(memory barrier),例如在分布式框架中的应用。简单的说,就是当你写一个 volatile 变量之前,Java 内存模型会插入一个写屏障(write barrier),读一个 volatile 变量之前,会插入一个读屏障(read barrier)。意思就是说,在你写一个 volatile 域时,能保证任何线程都能看到你写的值,同时,在写之前,也能保证任何数值的更新对所有线程是可见的,因为内存屏障会将其他所有写的值更新到缓存。

volatile类型变量提供什么保证?

volatile 主要有两方面的作用:1.避免指令重排2.可见性保证.例如,JVM 或者 JIT为了获得更好的性能会对语句重排序,但是 volatile 类型变量即使在没有同步块的情况下赋值也不会与其他语句重排序。 volatile 提供 happens-before 的保证,确保一个线程的修改能对其他线程是可见的。某些情况下,volatile 还能提供原子性,如读 64 位数据类型,像


Java中的集合及其继承关系

关于集合的体系是每个人都应该烂熟于心的,尤其是对我们经常使用的List,Map的原理更该如此.这里我们看这张图即可:

poll() 和 remove() 都是从队列中取出一个元素,但是 poll() 在获取元素失败的时候会返回空,但是 remove() 失败的时候会抛出异常。

PriorityQueue 是一个优先级队列,保证最高或者最低优先级的的元素总是在队列头部,但是 LinkedHashMap 维持的顺序是元素插入的顺序。当遍历一个 PriorityQueue 时,没有任何顺序保证,但是 LinkedHashMap 课保证遍历顺序是元素插入的顺序。

WeakHashMap 的工作与正常的 HashMap 类似,但是使用弱引用作为 key,意思就是当 key 对象没有任何引用时,key/value 将会被回收。

最明显的区别是 ArrrayList底层的是数组,支持随机访问,而 LinkedList 的底层数据结构是双向循环链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复杂度是 O(1),而 LinkedList 是 O(n)。

Comparable 接口用于定义对象的自然顺序,而 comparator 通常用于定义用户定制的顺序。Comparable 总是只有一个,但是可以有多个 comparator 来定义对象的顺序。

双向循环列表,具体实现自行查阅源码.

采用红黑树实现,具体实现自行查阅源码.

遍历ArrayList时如何正确移除一个元素

ArrayMap是用两个数组来模拟map,更少的内存占用空间,更高的效率.

1 HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
2 HashMap的数据结构: 在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

当我们往Hashmap中put元素时,首先根据key的hashcode重新计算hash值,根绝hash值得到这个元素在数组中的位置(下标),如果该数组在该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾.如果数组中该位置没有元素,就直接将该元素放到数组的该位置上.

Fail-Fast即我们常说的快速失败,更多内容参看


非常不幸,DateFormat 的所有实现,包括 SimpleDateFormat 都不是线程安全的,因此你不应该在多线程序中使用,除非是在对外线程安全的环境中使用,如 将 SimpleDateFormat 限制在 ThreadLocal 中。如果你不这么做,在解析或者格式化日期的时候,可能会获取到一个不正确的结果。因此,从日期、时间处理的所有实践来说,我强力推荐 joda-time

Java 中,可以使用 SimpleDateFormat 类或者 joda-time 库来格式日期。DateFormat 类允许你使用多种流行的格式来格式化日期。参见答案中的示例代码,代码中演示了将日期格式化成不同的格式,如 dd-MM-yyyy 或 ddMMyyyy。


简单描述java异常体系

相比没有人不了解异常体系,关于异常体系的更多信息可以见:

详情直接参见,不做解释了.


Serializable 接口是一个序列化 Java 类的接口,以便于它们可以在网络上传输或者可以将它们的状态保存在磁盘上,是 JVM 内嵌的默认序列化方式,成本高、脆弱而且不安全。Externalizable 允许你控制整个序列化过程,指定特定的二进制格式,增加安全机制。


Java语言的一个非常重要的特点就是与平台的无关性。而使用Java虚拟机是实现这一特点的关键。一般的高级语言如果要在不同的平台上运行,至少需要编译成不同的目标代码。而引入Java语言虚拟机后,Java语言在不同平台上运行时不需要重新编译。Java语言使用模式Java虚拟机屏蔽了与具体平台相关的信息,使得Java语言编译程序只需生成在Java虚拟机上运行的目标代码(字节码),就可以在多种平台上不加修改地运行。Java虚拟机在执行字节码时,把字节码解释成具体平台上的机器指令执行。

有关类加载器一般会问你四种类加载器的应用场景以及双亲委派模型,更多的内容参看

VM 中堆和栈属于不同的内存区域,使用目的也不同。栈常用于保存方法帧和局部变量,而对象总是在堆上分配。栈通常都比堆小,也不会在多个线程之间共享,而堆被整个 JVM 的所有线程共享。

1、 基本数据类型比变量和对象的引用都是在栈分配的
2、 堆内存用来存放由new创建的对象和数组
3、 类变量(static修饰的变量),程序在一加载的时候就在堆中为类变量分配内存,堆中的内存地址存放在栈中
4、 实例变量:当你使用java关键字new的时候,系统在堆中开辟并不一定是连续的空间分配给变量,是根据零散的堆内存地址,通过哈希算法换算为一长串数字以表征这个变量在堆中的”物理位置”,实例变量的生命周期–当实例变量的引用丢失后,将被GC(垃圾回收器)列入可回收“名单”中,但并不是马上就释放堆中内存
5、 局部变量: 由声明在某方法,或某代码段里(比如for循环),执行到它的时候在栈中开辟内存,当局部变量一但脱离作用域,内存立即释放


java当中采用的是大端还是小端?

XML解析的几种方式和特点

  • DOM:消耗内存:先把xml文档都读到内存中,然后再用DOM API来访问树形结构,并获取数据。这个写起来很简单,但是很消耗内存。要是数据过大,手机不够牛逼,可能手机直接死机
  • SAX:解析效率高,占用内存少,基于事件驱动的:更加简单地说就是对文档进行顺序扫描,当扫描到文档(document)开始与结束、元素(element)开始与结束、文档(document)结束等地方时通知事件处理函数,由事件处理函数做相应动作,然后继续同样的扫描,直至文档结束。
  • PULL:与 SAX 类似,也是基于事件驱动,我们可以调用它的next()方法,来获取下一个解析事件(就是开始文档,结束文档,开始标签,结束标签),当处于某个元素时可以调用XmlPullParser的getAttributte()方法来获取属性的值,也可调用它的nextText()获取本节点的值。

变量和文本。菱形操作符(\<>)用于类型推断,不再需要在变量声明的右边申明泛型,因此可以写出可读写更强、更简洁的代码

java 8 在 Java 历史上是一个开创新的版本,下面 JDK 8 中 5 个主要的特性:
Lambda 表达式,允许像对象一样传递匿名函数
Stream API,充分利用现代多核 CPU,可以写出很简洁的代码
Date 与 Time API,最终,有一个稳定、简单的日期和时间库可供你使用
扩展方法,现在,接口中可以有静态、默认方法。
重复注解,现在你可以将相同的注解在同一类型上使用多次。

虽然两者都是构建工具,都用于创建 Java 应用,但是 Maven 做的事情更多,在基于“约定优于配置”的概念下,提供标准的Java 项目结构,同时能为应用自动管理依赖(应用中所依赖的 JAR 文件),Maven 与 ANT 工具更多的不同之处请参见答案。
这就是所有的面试题,如此之多,是不是?我可以保证,如果你能回答列表中的所有问题,你就可以很轻松的应付任何核心 Java 或者高级 Java 面试。虽然,这里没有涵盖 Servlet、JSP、JSF、JPA,JMS,EJB 及其它 技术,也没有包含主流的框架如 MVC,Struts 2.0,,也没有包含 SOAP 和 RESTful web service,但是这份列表对做 Java 开发的、准备应聘 Java web 开发职位的人还是同样有用的,因为所有的 Java 面试,开始的问题都是 Java 基础和 JDK API 相关的。如果你认为我这里有任何应该在这份列表中而被我遗漏了的 Java 流行的问题,你可以自由的给我建议。我的目的是从最近的面试中创建一份最新的、最优的 Java 面试问题列表。

  • 优先使用批量操作来插入和更新数据

1、 使用有缓冲的IO类,不要单独读取字节或字符
4、 使用内存映射文件获取更快的IO

}

在Android开发中,无论是插件化仍是组件化,都是基于Android系统的类加载器ClassLoader来设计的。只不过Android平台上虚拟机运行的是Dex字节码,一种对class文件优化的产物,传统Class文件是一个Java源码文件会生成一个.class文件,而Android是把全部Class文件进行合并、优化,而后再生成一个最终的/")

在建立retrofit对象的时候用到了build()方法,该方法的实现以下:

该方法返回了一个Retrofit对象,经过retrofit对象建立网络请求的接口的方式以下:

图片函数库的选择须要根据APP的具体状况而定,对于严重依赖图片缓存的APP,例如壁纸类,图片社交类APP来讲,能够选择最专业的Fresco。对于通常的APP,选择Fresco会显得比较重,毕竟Fresco3.4M的体量摆在这。根据APP对图片的显示和缓存的需求从低到高,咱们能够对以上函数库作一个排序。

Picasso :和Square的网络库一块儿能发挥最大做用,由于Picasso能够选择将网络请求的缓存部分交给了okhttp实现。

FB的图片加载框架Fresco:最大的优点在于5.0如下(最低2.3)的bitmap加载。在5.0如下系统,Fresco将图片放到一个特别的内存区域(Ashmem区)。固然,在图片不显示的时候,占用的内存会自动被释放。这会使得APP更加流畅,减小因图片内存占用而引起的OOM。为何说是5.0如下,由于在5.0之后系统默认就是存储在Ashmem区了。

Picasso所能实现的功能,Glide都能作,无非是所需的设置不一样。可是Picasso体积比起Glide小太多若是项目中网络请求自己用的就是okhttp或者retrofit(本质仍是okhttp),那么建议用Picasso,体积会小不少(Square全家桶的干活)。Glide的好处是大型的图片流,好比gif、Video,若是大家是作美拍、爱拍这种视频类应用,建议使用。

不过在使用起来也有些不便(小建议:他只能用内置的一个ImageView来实现这些功能,用起来比较麻烦,咱们一般是根据Fresco本身改改,直接使用他的Bitmap层)

Gson是目前功能最全的Json解析神器,Gson当初是为因应Google公司内部需求而由Google自行研发而来,但自从在2008年五月公开发布初版后已被许多公司或用户应用。Gson的应用主要为toJson与fromJson两个转换函数,无依赖,不须要例外额外的jar,可以直接跑在JDK上。而在使用这种对象转换以前需先建立好对象的类型以及其成员才能成功的将JSON字符串成功转换成相对应的对象。类里面只要有get和set方法,Gson彻底能够将复杂类型的json到bean或bean到json的转换,是JSON解析的神器。Gson在功能上面无可挑剔,可是性能上面比FastJson有所差距。

Fastjson是一个Java语言编写的高性能的JSON处理器,由阿里巴巴公司开发。

无依赖,不须要例外额外的jar,可以直接跑在JDK上。FastJson在复杂类型的Bean转换Json上会出现一些问题,可能会出现引用的类型,致使Json转换出错,须要制定引用。FastJson采用首创的算法,将parse的速度提高到极致,超过全部json库。

综上Json技术的比较,在项目选型的时候能够使用Google的Gson和阿里巴巴的FastJson两种并行使用,若是只是功能要求,没有性能要求,能够使用google的Gson,若是有性能上面的要求能够使用Gson将bean转换json确保数据的正确,使用FastJson将Json转换Bean

组件化:是将一个APP分红多个module,每一个module都是一个组件,也能够是一个基础库供组件依赖,开发中能够单独调试部分组件,组件中不须要相互依赖可是能够相互调用,最终发布的时候全部组件以lib的形式被主APP工程依赖打包成一个apk。

  1. APP版本迭代,新功能不断增长,业务变得复杂,维护成本高
  2. 业务耦合度高,代码臃肿,团队内部多人协做开发困难
  3. Android编译代码卡顿,单一工程下代码耦合严重,修改一处须要从新编译打包,耗时耗力。
  4. 方便单元测试,单独改一个业务模块,不须要着重关注其余模块。
  1. 组件化将通用模块独立出来,统一管理,以提升复用,将页面拆分为粒度更小的组件,组件内部出了包含UI实现,还能够包含数据层和逻辑层
  2. 每一个组件度能够独立编译、加快编译速度、独立打包。
  3. 每一个工程内部的修改,不会影响其余工程。
  4. 业务库工程能够快速拆分出来,集成到其余App中。
  5. 迭代频繁的业务模块采用组件方式,业务线研发能够互不干扰、提高协做效率,并控制产品质量,增强稳定性。
  6. 并行开发,团队成员只关注本身的开发的小模块,下降耦合性,后期维护方便等。

组件化后的每个业务的module均可以是一个单独的APP(isModuleRun=false), release 包的时候各个业务module做为lib依赖,这里彻底由一个变量控制,在根项目

当咱们建立了多个Module的时候,如何解决相同资源文件名合并的冲突,业务Module和BaseModule资源文件名称重复会产生冲突,解决方案在于:

每一个 module 都有 app_name,为了避免让资源名重名,在每一个组件的 build.gradle 中增长 resourcePrefix “xxx_强行检查资源名称前缀。固定每一个组件的资源前缀。可是 resourcePrefix 这个值只能限定 xml 里面的资源,并不能限定图片资源。

多个Module之间如何引用一些共同的library以及工具类

组件化以后,Module之间是相互隔离的,如何进行UI跳转以及方法调用,具体能够使用阿里巴巴ARouter或者美团的WMRouter等路由框架。

各业务Module以前不须要任何依赖能够经过路由跳转,完美解决业务之间耦合。

咱们知道组件之间是有联系的,因此在单独调试的时候如何拿到其它的Module传递过来的参数

当组件单独运行的时候,每一个Module自成一个APK,那么就意味着会有多个Application,很显然咱们不肯意重复写这么多代码,因此咱们只须要定义一个BaseApplication便可,其它的Application直接继承此BaseApplication就OK了,BaseApplication里面还可定义公用的参数。

关于如何进行组件化,能够参考:

提到插件化,就不得不提起方法数超过65535的问题,咱们能够经过Dex分包来解决,同时也能够经过使用插件化开发来解决。插件化的概念就是由宿主APP去加载以及运行插件APP。

在一个大的项目里面,为了明确的分工,每每不一样的团队负责不一样的插件APP,这样分工更加明确。各个模块封装成不一样的插件APK,不一样模块能够单独编译,提升了开发效率。 解决了上述的方法数超过限制的问题。能够经过上线新的插件来解决线上的BUG,达到“热修复”的效果。 减少了宿主APK的体积。

插件化开发的APP不能在Google Play上线,也就是没有海外市场。

含义:手机对角线的物理尺寸 单位:英寸(inch),1英寸=2.54cm

Android手机常见的尺寸有5寸、5.5寸、6寸,6.5寸等等

含义:手机在横向、纵向上的像素点数总和

通常描述成屏幕的”宽x高”=AxB 含义:屏幕在横向方向(宽度)上有A个像素点,在纵向方向

(高)有B个像素点 例子:,即宽度方向上有1080个像素点,在高度方向上有1920个像素点

UI设计师的设计图会以px做为统一的计量单位

假设设备内每英寸有160个像素,那么该设备的屏幕像素密度=160dpi

2.使用相对布局,禁用绝对布局。

从这个角度咱们来解释一下上面的现象。在上面的代码中,咱们设置每一个Button的宽度都是match_parent,假设屏幕宽度为L,那么每一个Button的宽度也应该都为L,剩余宽度就等于L-(L+L)= -L。

参考连接: Android的性能优化,主要是从如下几个方面进行优化的: 稳定(内存溢出、崩溃) 流畅(卡顿) 耗损(耗电、流量) 安装包(APK瘦身) 影响稳定性的缘由不少,好比内存使用不合理、代码异常场景考虑不周全、代码逻辑不合理等,都会对应用的稳定性形成影响。其中最多见的两个场景是:Crash 和 ANR,这两个错误将会使得程序没法使用。因此作好Crash全局监控,处理闪退同时把崩溃信息、异常信息收集记录起来,以便后续分析;合理使用主线程处理业务,不要在主线程中作耗时操做,防止ANR程序无响应发生。

它是Android Studio自带的一个内存监视工具,它能够很好地帮助咱们进行内存实时分析。经过点击Android Studio右下角的Memory Monitor标签,打开工具能够看见较浅蓝色表明free的内存,而深色的部分表明使用的内存从内存变换的走势图变换,能够判断关于内存的使用状态,例如当内存持续增高时,可能发生内存泄漏;当内存忽然减小时,可能发生GC等,以下图所示。

Android Lint Tool 是Android Sutido种集成的一个Android代码提示工具,它能够给你布局、代码提供很是强大的帮助。硬编码会提示以级别警告,例如:在布局文件中写了三层冗余的LinearLayout布局、直接在TextView中写要显示的文字、字体大小使用dp而不是sp为单位,就会在编辑器右边看到提示。

卡顿的场景一般是发生在用户交互体验最直接的方面。影响卡顿的两大因素,分别是界面绘制和数据处理。

界面绘制:主要缘由是绘制的层级深、页面复杂、刷新不合理,因为这些缘由致使卡顿的场景更多出如今 UI 和启动后的初始界面以及跳转到页面的绘制上。

数据处理:致使这种卡顿场景的缘由是数据处理量太大,通常分为三种状况,一是数据在处理 UI 线程,二是数据处理占用 CPU 高,致使主线程拿不到时间片,三是内存增长致使 GC 频繁,从而引发卡顿。

在Android种系统对View进行测量、布局和绘制时,都是经过对View数的遍从来进行操做的。若是一个View数的高度过高就会严重影响测量、布局和绘制的速度。Google也在其API文档中建议View高度不宜哦过10层。如今版本种Google使用RelativeLayout替代LineraLayout做为默认根布局,目的就是下降LineraLayout嵌套产生布局树的高度,从而提升UI渲染的效率。

布局复用,使用标签重用layout; 提升显示速度,使用延迟View加载; 减小层级,使用标签替换父级布局; 注意使用wrap_content,会增长measure计算成本; 删除控件中无用属性;

过分绘制是指在屏幕上的某个像素在同一帧的时间内被绘制了屡次。在多层次重叠的 UI 结构中,若是不可见的 UI 也在作绘制的操做,就会致使某些像素区域被绘制了屡次,从而浪费了多余的 CPU 以及 GPU 资源。如何避免过分绘制?

布局上的优化。移除 XML 中非必须的背景,移除 Window 默认的背景、按需显示占位背景图片

自定义View优化。使用 canvas.clipRect() 帮助系统识别那些可见的区域,只有在这个区域内才会被绘制。

在 Android5.0 之前,关于应用电量消耗的测试即麻烦又不许确,而5.0 以后Google专门引入了一个获取设备上电量消耗信息的API—— Battery Historian。Battery Historian 是一款由 Google 提供的 Android 系统电量分析工具,直观地展现出手机的电量消耗过程,经过输入电量分析文件,显示消耗状况。

最后提供一些可供参考耗电优化的方法:

浮点运算:计算机里整数和小数形式就是按普通格式进行存储,例如102四、3.1415926等等,这个没什么特色,可是这样的数精度不高,表达也不够全面,为了可以有一种数的通用表示法,就发明了浮点数。浮点数的表示形式有点像科学计数法(.×10^),它的表示形式是0.×10^,在计算机中的形式为 .* e ±**),其中前面的星号表明定点小数,也就是整数部分为0的纯小数,后面的指数部分是定点整数。利用这样的形式就能表示出任意一个整数和小数,例如1024就能表示成0.,也就是 .,3.1415926就能表示成0.^1,也就是 .1,这就是浮点数。浮点数进行的运算就是浮点运算。浮点运算比常规运算更复杂,所以计算机进行浮点运算速度要比进行常规运算慢得多。

Lock是一种锁的机制,主要是相对系统的休眠而言的,,只要有人拿着这个锁,系统就没法进入休眠意思就是个人程序给CPU加了这个锁那系统就不会休眠了,这样作的目的是为了全力配合咱们程序的运行。有的状况若是不这么作就会出现一些问题,好比微信等及时通信的心跳包会在熄屏不久后中止网络访问等问题。因此微信里面是有大量使用到了Wake_Lock锁。系统为了节省电量,CPU在没有任务忙的时候就会自动进入休眠。有任务须要唤醒CPU高效执行的时候,就会给CPU加Wake_Lock锁。你们常常犯的错误,咱们很容易去唤醒CPU来工做,可是很容易忘记释放Wake_Lock。

在Android 5.0 API 21 中,google提供了一个叫作JobScheduler API的组件,来处理当某个时间点或者当知足某个特定的条件时执行一个任务的场景,例如当用户在夜间休息时或设备接通电源适配器链接WiFi启动下载更新的任务。这样能够在减小资源消耗的同时提高应用的效率。

assets文件夹。存放一些配置文件、资源文件,assets不会自动生成对应的 ID,而是经过 AssetManager 类的接口获取。

res。res 是 resource 的缩写,这个目录存放资源文件,会自动生成对应的 ID 并映射到 .R 文件中,访问直接使用资源 ID。

META-INF。保存应用的签名信息,签名信息能够验证 APK 文件的完整性。

AndroidManifest.xml。这个文件用来描述 Android 应用的配置信息,一些组件的注册信息、可以使用权限等。

resources.arsc。记录着资源文件和资源 ID 之间的映射关系,用来根据资源 ID 寻找资源。

代码混淆。使用IDE 自带的 proGuard 代码混淆器工具 ,它包括压缩、优化、混淆等功能。 资源优化。好比使用 Android Lint 删除冗余资源,资源文件最少化等。 图片优化。好比利用 PNG优化工具 对图片作压缩处理。推荐目前最早进的压缩工具Googlek开源库zopfli。若是应用在0版本以上,推荐使用 WebP图片格式。 避免重复或无用功能的第三方库。例如,百度地图接入基础地图便可、讯飞语音无需接入离线、图片库Glide\Picasso等。 插件化开发。好比功能模块放在服务器上,按需下载,能够减小安装包大小。 能够使用微信开源资源文件混淆工具——AndResGuard。通常能够压缩apk的1M左右大。

冷启动 在启动应用时,系统中没有该应用的进程,这时系统会建立一个新的进程分配给该应用;

热启动 在启动应用时,系统中已有该应用的进程(例:按back键、home键,应用虽然会退出,可是该应用的进程仍是保留在后台);

区别 冷启动:系统没有该应用的进程,须要建立一个新的进程分配给应用,因此会先建立和初始化Application类,再建立和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。 热启动: 从已有的进程中来启动,不会建立和初始化Application类,直接建立和初始化MainActivity类(包括一系列的测量、布局、绘制),最后显示在界面上。

冷启动优化 减小在Application和第一个Activity的onCreate()方法的工做量; 不要让Application参与业务的操做; 不要在Application进行耗时操做; 不要以静态变量的方式在Application中保存数据; 减小布局的复杂性和深度;

模型层(Model):主要是获取数据功能,业务逻辑和实体模型。

视图层(View):对应于Activity或Fragment,负责视图的部分展现和业务逻辑用户交互

控制层(Presenter):负责完成View层与Model层间的交互,经过P层来获取M层中数据后返回给V层,使得V层与M层间没有耦合。

,Presenter层彻底将View层和Model层进行了分离,把主要程序逻辑放在Presenter层实现,Presenter与具体的View层(Activity)是没有直接的关联,是经过定义接口来进行交互的,从而使得当View层(Activity)发生改变时,Persenter依然能够保持不变。View层接口类只应该只有set/get方法,及一些界面显示内容和用户输入,除此以外不该该有多余的内容。毫不容许View层直接访问Model层,这是与MVC最大区别之处,也是MVP核心优势。

Android4.4及之前使用的都是Dalvik虚拟机,咱们知道Apk在打包的过程当中会先将java等源码经过javac编译成.class文件,可是咱们的Dalvik虚拟机只会执行.dex文件,这个时候dx会将.class文件转换成Dalvik虚拟机执行的.dex文件。Dalvik虚拟机在启动的时候会先将.dex文件转换成快速运行的机器码,又由于65535这个问题,致使咱们在应用冷启动的时候有一个合包的过程,最后致使的一个结果就是咱们的app启动慢,这就是Dalvik虚拟机的JIT特性(Just

time),这个特性就是咱们在安装APK的时候就将dex直接处理成可直接供ART虚拟机使用的机器码,ART虚拟机将.dex文件转换成可直接运行的.oat文件,ART虚拟机天生支持多dex,因此也不会有一个合包的过程,因此ART虚拟机会很大的提高APP冷启动速度。

提供功能全面的Debug特性

APP安装速度慢,由于在APK安装的时候要生成可运行.oat文件

APK占用空间大,由于在APK安装的时候要生成可运行.oat文件

关于ART更详细的介绍,能够参考

熟悉Android性能分析工具、UI卡顿、APP启动、包瘦身和内存性能优化

熟悉Android APP架构设计,模块化、组件化、插件化开发

熟练掌握Java、设计模式、网络、多线程技术

jvm将.class类文件信息加载到内存并解析成对应的class对象的过程,注意:jvm并非一开始就把全部的类加载进内存中,只是在第一次遇到某个须要运行的类才会加载,而且只加载一次

主要分为三部分:一、加载,二、连接(1.验证,2.准备,3.解析),三、初始化

验证:(验证class文件的字节流是否符合jvm规范)

准备:为类变量分配内存,而且进行赋初值

解析:将常量池里面的符号引用(变量名)替换成直接引用(内存地址)过程,在解析阶段,jvm会把全部的类名、方法名、字段名、这些符号引用替换成具体的内存地址或者偏移量。

主要对类变量进行初始化,执行类构造器的过程,换句话说,只对static修试的变量或者语句进行初始化。

Java编程思想中的类的初始化过程主要有如下几点:

  1. 找到class文件,将它加载到内存
  2. 在堆内存中分配内存地址
  3. 将堆内存地址指给栈内存中的p变量

StringBuffer里面的不少方法添加了synchronized关键字,是能够表征线程安全的,因此多线程状况下使用它。

StringBuilder牺牲了性能来换取速度的,这两个是能够直接在原对象上面进行修改,省去了建立新对象和回收老对象的过程,而String是字符串常量(final)修试,另外两个是字符串变量,常量对象一旦建立就不能够修改,变量是能够进行修改的,因此对于String字符串的操做包含下面三个步骤:

  1. 建立一个新对象,名字和原来的同样

Java对象实例化过程当中,主要使用到虚拟机栈、Java堆和方法区。Java文件通过编译以后首先会被加载到jvm方法区中,jvm方法区中很重的一个部分是运行时常量池,用以存储class文件类的版本、字段、方法、接口等描述信息和编译期间的常量和静态常量。

类加载器classLoader,在JVM启动时或者类运行时将须要的.class文件加载到内存中。 执行引擎,负责执行class文件中包含的字节码指令。 本地方法接口,主要是调用C/C++实现的本地方法及返回结果。 内存区域(运行时数据区),是在JVM运行的时候操做所分配的内存区, 主要分为如下五个部分,以下图:

  • 方法区:用于存储类结构信息的地方,包括常量池、静态变量、构造函数等。
  • Java堆(heap):存储Java实例或者对象的地方。这块是gc的主要区域。
  • Java栈(stack):Java栈老是和线程关联的,每当建立一个线程时,JVM就会为这个线程建立一个对应的Java栈。在这个java栈中又会包含多个栈帧,每运行一个方法就建立一个栈帧,用于存储局部变量表、操做栈、方法返回值等。每个方法从调用直至执行完成的过程,就对应一个栈帧在java栈中入栈到出栈的过程。因此java栈是线程私有的。
  • 程序计数器:用于保存当前线程执行的内存地址,因为JVM是多线程执行的,因此为了保证线程切换回来后还能恢复到原先状态,就须要一个独立的计数器,记录以前中断的地方,可见程序计数器也是线程私有的。
  • 本地方法栈:和Java栈的做用差很少,只不过是为JVM使用到的native方法服务的。

垃圾收集器通常完成两件事

一般,Java对象的引用能够分为4类:强引用、软引用、弱引用和虚引用。 强引用:一般能够认为是经过new出来的对象,即便内存不足,GC进行垃圾收集的时候也不会主动回收。

软引用:在内存不足的时候,GC进行垃圾收集的时候会被GC回收。

弱引用:不管内存是否充足,GC进行垃圾收集的时候都会回收。

虚引用:和弱引用相似,主要区别在于虚引用必须和引用队列一块儿使用。

引用队列:若是软引用和弱引用被GC回收,JVM就会把这个引用加到引用队列里,若是是虚引用,在回收前就会被加到引用队列里。

引用计数法:给每一个对象添加引用计数器,每一个地方引用它,计数器就+1,失效时-1。若是两个对象互相引用时,就致使没法回收。 可达性分析算法:以根集对象为起始点进行搜索,若是对象不可达的话就是垃圾对象。根集(Java栈中引用的对象、方法区中常量池中引用的对象、本地方法中引用的对象等。JVM在垃圾回收的时候,会检查堆中全部对象是否被这些根集对象引用,不可以被引用的对象就会被垃圾回收器回收。)

常见的垃圾回收算法有:

标记:首先标记全部须要回收的对象,在标记完成以后统计回收全部被标记的对象,它的标记过程即为上面的可达性分析算法。 清除:清除全部被标记的对象 缺点: 效率不足,标记和清除效率都不高 空间问题,标记清除以后会产生大量不连续的内存碎片,致使大对象分配没法找到足够的空间,提早进行垃圾回收。

复制回收算法 将可用的内存按容量划分为大小相等的2块,每次只用一块,当这一块的内存用完了,就将存活的对象复制到另一块上面,而后把已使用过的内存空间一次清理掉。

将内存缩小了本来的通常,代价比较高 大部分对象是“朝生夕灭”的,因此没必要按照1:1的比例划分。 如今商业虚拟机采用这种算法回收新生代,但不是按1:1的比例,而是将内存区域划分为eden 空间、from 空间、to 空间 3 个部分。 其中 from 空间和 to 空间能够视为用于复制的两块大小相同、地位相等,且可进行角色互换的空间块。from 和 to 空间也称为 survivor 空间,即幸存者空间,用于存放未被回收的对象。

在垃圾回收时,eden 空间中的存活对象会被复制到未使用的 survivor 空间中 (假设是 to),正在使用的 survivor 空间 (假设是 from) 中的年轻对象也会被复制到 to 空间中 (大对象,或者老年对象会直接进入老年带,若是 to 空间已满,则对象也会直接进入老年代)。此时,eden 空间和 from 空间中的剩余对象就是垃圾对象,能够直接清空,to 空间则存放这次回收后的存活对象。这种改进的复制算法既保证了空间的连续性,又避免了大量的内存空间浪费。

在老年代的对象大都是存活对象,复制算法在对象存活率教高的时候,效率就会变得比较低。根据老年代的特色,有人提出了“标记-压缩算法(Mark-Compact)”

标记过程与标记-清除的标记同样,但后续不是对可回收对象进行清理,而是让全部的对象都向一端移动,而后直接清理掉端边界之外的内存。

这种方法既避免了碎片的产生,又不须要两块相同的内存空间,所以,其性价比比较高。

根据对象存活的周期不一样将内存划分为几块,通常是把Java堆分为老年代和新生代,这样根据各个年代的特色采用适当的收集算法。

新生代每次收集都有大量对象死去,只有少许存活,那就选用复制算法,复制的对象数较少就可完成收集。 老年代对象存活率高,使用标记-压缩算法,以提升垃圾回收效率。

程序在启动的时候,并不会一次性加载程序所要用的全部class文件,而是根据程序的须要,经过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的,从而只有class文件被载入到了内存以后,才能被其它class所引用。因此ClassLoader就是用来动态加载class文件到内存当中用的。

每一个ClassLoader实例都有一个父类加载器的引用(不是继承关系,是一个包含的关系),虚拟机内置的类加载器(Bootstrap ClassLoader)自己没有父类加载器,可是能够用作其余ClassLoader实例的父类加载器。

当一个ClassLoader 实例须要加载某个类时,它会试图在亲自搜索这个类以前先把这个任务委托给它的父类加载器,这个过程是由上而下依次检查的,首先由顶层的类加载器Bootstrap CLassLoader进行加载,若是没有加载到,则把任务转交给Extension CLassLoader视图加载,若是也没有找到,则转交给AppCLassLoader进行加载,仍是没有的话,则交给委托的发起者,由它到指定的文件系统或者网络等URL中进行加载类。尚未找到的话,则会抛出CLassNotFoundException异常。不然将这个类生成一个类的定义,并将它加载到内存中,最后返回这个类在内存中的Class实例对象。

JVM在判断两个class是否相同时,不只要判断两个类名是否相同,还要判断是不是同一个类加载器加载的。

避免重复加载,父类已经加载了,则子CLassLoader没有必要再次加载。 考虑安全因素,假设自定义一个String类,除非改变JDK中CLassLoader的搜索类的默认算法,不然用户自定义的CLassLoader如法加载一个本身写的String类,由于String类在启动时就被引导类加载器Bootstrap CLassLoader加载了。

关于Android的双亲委托机制,能够参考

Java集合类主要由两个接口派生出:Collection和Map,这两个接口是Java集合的根接口。

Collection接口是集合类的根接口,Java中没有提供这个接口的直接的实现类。可是却让其被继承产生了两个接口,就是 Set和List。Set中不能包含重复的元素。List是一个有序的集合,能够包含重复的元素,提供了按索引访问的方式。

Map是Java.util包中的另外一个接口,它和Collection接口没有关系,是相互独立的,可是都属于集合类的一部分。Map包含了key-value对。Map不能包含重复的key,可是能够包含相同的value。

List,Set都是继承自Collection接口,Map则不是; List特色:元素有放入顺序,元素可重复; Set特色:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(注意:元素虽然无放入顺序,可是元素在set中的位置是有该元素的HashCode决定的,其位置实际上是固定的,加入Set 的Object必须定义equals()方法;

Vector是多线程安全的,线程安全就是说多线程访问同一代码,不会产生不肯定的结果。而ArrayList不是,这个能够从源码中看出,Vector类中的方法不少有synchronized进行修饰,这样就致使了Vector在效率上没法与ArrayList相比; 两个都是采用的线性连续空间存储元素,可是当空间不足的时候,两个类的增长方式是不一样。 Vector能够设置增加因子,而ArrayList不能够。 Vector是一种老的动态数组,是线程同步的,效率很低,通常不同意使用。

在HashMap中进行查找是否存在这个key,value始终是同样的,主要有如下几种状况:

  • 若是hash码值不相同,说明是一个新元素,存;
  • 若是hash码值相同,且equles判断相等,说明元素已经存在,不存;
  • 若是hash码值相同,且equles判断不相等,说明元素不存在,存;
  • 若是有元素和传入对象的hash值相等,那么,继续进行equles()判断,若是仍然相等,那么就认为传入元素已经存在,再也不添加,结束,不然仍然添加;
  • HashSet是基于Hash算法实现的,其性能一般都优于TreeSet。为快速查找而设计的Set,咱们一般都应该使用HashSet,在咱们须要排序的功能时,咱们才使用TreeSet。
  • TreeSet 是二叉树(红黑树的树据结构)实现的,Treeset中的数据是自动排好序的,不容许放入null值
  • HashSet是哈希表实现的,HashSet中的数据是无序的,能够放入null,但只能放入一个null,二者中的值都不能重复,就如数据库中惟一约束。
  • HashSet是基于Hash算法实现的,其性能一般都优于TreeSet。为快速查找而设计的Set,咱们一般都应该使用HashSet,在咱们须要排序的功能时,咱们才使用TreeSet。

HashMap 非线程安全,基于哈希表(散列表)实现。使用HashMap要求添加的键类明肯定义了hashCode()和equals()[能够重写hashCode()和equals()],为了优化HashMap空间的使用,您能够调优初始容量和负载因子。其中散列表的冲突处理主要分两种,一种是开放定址法,另外一种是链表法。HashMap的实现中采用的是链表法。 TreeMap:非线程安全基于红黑树实现,TreeMap没有调优选项,由于该树总处于平衡状态

当数值范围为-128~127时:若是两个new出来Integer对象,即便值相同,经过“==”比较结果为false,但两个对象直接赋值,则经过“==”比较结果为“true,这一点与String很是类似。 当数值不在-128~127时,不管经过哪一种方式,即便两个对象的值相等,经过“==”比较,其结果为false; 当一个Integer对象直接与一个int基本数据类型经过“==”比较,其结果与第一点相同; Integer对象的hash值为数值自己;

泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操做的数据类型被指定为一个参数。这种参数类型能够用在类、接口和方法的建立中,分别称为泛型类、泛型接口、泛型方法。 Java语言引入泛型的好处是安全简单。

泛型的好处是在编译的时候检查类型安全,而且全部的强制转换都是自动和隐式的,提升代码的重用率。

它提供了编译期的类型安全,确保你只能把正确类型的对象放入 集合中,避免了在运行时出现ClassCastException。

使用Java的泛型时应注意如下几点:

  • 泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
  • 同一种泛型能够对应多个版本(由于参数类型是不肯定的),不一样版本的泛型类实例是不兼容的。
  • 泛型的类型参数能够有多个。
  • 泛型的参数类型能够使用extends语句,例如。习惯上称为“有界类型”。
  • ? 表示不肯定的java类型。

Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。

泛型是经过类型擦除来实现的,编译器在编译时擦除了全部类型相关的信息,因此在运行时不存在任何类型相关的信息。例如 List在运行时仅用一个List来表示。这样作的目的,是确保能和Java 5以前的版本开发二进制类库进行兼容。你没法在运行时访问到类型参数,由于编译器已经把泛型类型转换成了原始类型。

一种是<? extends T>它经过确保类型必须是T的子类来设定类型的上界, 另外一种是<? super T>它经过确保类型必须是T的父类来设定类型的下界。 另外一方面表 示了非限定通配符,由于能够用任意类型来替代。 例如List<? extends Number>能够接受List或List。

对任何一个不太熟悉泛型的人来讲,这个Java泛型题目看起来使人疑惑,由于乍看起来String是一种Object,因此 List应当能够用在须要List的地方,可是事实并不是如此。真这样作的话会致使编译错误。如 果你再深一步考虑,你会发现Java这样作是有意义的,由于List能够存储任何类型的对象包括String, Integer等等,而List却只能用来存储Strings。

JAVA反射机制是在运行状态中,对于任意一个类,都可以知道这个类的全部属性和方法;对于任意一个对象,都可以调用它的任意一个方法;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

Java反射机制主要提供了如下功能: 在运行时判断任意一个对象所属的类;在运行时构造任意一个类的对象;在运行时判断任意一个类所具备的成员变量和方法;在运行时调用任意一个对象的方法;生成动态代理。

排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳所有的排序记录,在排序过程当中须要访问外存。

将第一个数和第二个数排序,而后构成一个有序序列 将第三个数插入进去,构成一个新的有序序列。 对第四个数、第五个数……直到最后一个数,重复第二步。 代码:

首先设定插入次数,即循环次数,for(int i=1;i<length;i++),1个数的那次不用插入。 设定插入数和获得已经排好序列的最后一个数的位数。insertNum和j=i-1。

单例主要分为:懒汉式单例、饿汉式单例、登记式单例。

  1. 单例类必须本身建立本身的惟一实例
  2. 单例类必须给全部其余对象提供这一实例。

在计算机系统中,像线程池,缓存、日志对象、对话框、打印机等常被设计成单例。

Singleton经过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的惟一实例只能经过getInstance()方法访问。(事实上,经过Java反射机制是可以实例化构造方法为private的类的,那基本上会使全部的Java单例实现失效。

它是线程不安全的,并发状况下颇有可能出现多个Singleton实例,要实现线程安全,有如下三种方式: 1.在getInstance方法上加上同步

这种方式对比前两种,既实现了线程安全,又避免了同步带来的性能影响。

饿汉式在建立类的同时就已经建立好了一个静态的对象供系统使用,之后再也不改变,因此天生是系统安全。

}

我要回帖

更多关于 java生成class文件后怎么运行 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信