一 简单描述Java
Java 是一种面向对象的语言,最显著的特性有两个方面,一是所谓的“书写一次,到处运行”(Write once, run anywhere),能够非常容易地获得跨平台能力;另外就是垃圾收集(GC, Garbage Collection),Java 通过垃圾收集器(Garbage Collector)回收分配内存,大部分情况下,程序员不需要自己操心内存的分配和回收。
JRE(Java Runtime Environment)或者 JDK(Java Development Kit)。 JRE,也就是 Java 运行环境,包含了 JVM 和 Java 类库,以及一些模块等。而 JDK 可以看作是 JRE 的一个超集,提供了更多工具,比如编译器、各种诊断工具等。
Java 的源代码,首先通过 Javac 编译成为字节码(bytecode),然后,在运行时,通过 Java 虚拟机(JVM)内嵌的解释器将字节码转换成为最终的机器码。
常见的 JVM,比如我们大多数情况使用的 Oracle JDK 提供的 Hotspot JVM,都提供了 JIT(Just-In-Time)编译器,也就是通常所说的动态编译器,JIT 能够在运行时将热点代码编译成机器码,这种情况下部分热点代码就属于编译执行,而不是解释执行了。
Java 语言特性,包括泛型、Lambda 等语言特性;基础类库,包括集合、IO/NIO、网络、并发、安全等基础类库。
JVM 的一些基础概念和机制,比如 Java 的类加载机制,常用版本 JDK(如 JDK 8)内嵌的 Class-Loader,例如 Bootstrap、 Application 和 Extension Class-loader;类加载大致过程:加载、验证、链接、初始化(这里参考了周志明的《深入理解 Java 虚拟机》,非常棒的 JVM 上手书籍);自定义 Class-Loader 等。还有垃圾收集的基本原理,最常见的垃圾收集器,如 SerialGC、Parallel GC、 CMS、 G1 等,对于适用于什么样的工作负载最好也心里有数。这些都是可以扩展开的领域,我会在后面的专栏对此进行更系统的介绍。
二 Exception和Error
Exception和Error都继承了Throwable类。只有Throwable类型的实例才可以被抛出(throw)或者捕获(catch)。
Exception:程序正常运行过程中可以预料的意外情况,可能并且应该被捕获,进行相应处理
Error:在正常情况下不太可能出现的情况。绝大部分Error都会导致程序(比如JVM自身)处于非正常的不可恢复状态。常见的比如OutOfMemoryError之类,都是Error的子类。
Exception又分为可检查(checked)异常和不检查(unchecked)异常。
可检查异常必须 显示捕获这是编译期检查的一部分。
不检查异常就是所谓的运行时异常,类似NullPointerException、ArrayIndexOutOfBoundsException。通常是可以编码避免的逻辑错误。
关键字
try-catch-finally,throw,throws
try-with-resources,multiple catch
异常处理的基本原则
1,不要捕获类似Exception的通用异常,而是应该捕获特定异常。
2,不要生吞异常
3,throw early,catch late
4,自定义异常要考虑注意过滤敏感信息
5,尽量不要一个大的try包住整段代码(try-catch代码段会产生额外的性能开销或者说会影响JVM对代码进行优化),建议只捕获有必要的代码。
(Java每实例化一个Exception,都会对当时的栈进行快照,这是一个相对比较重的操作,如果发生的非常频繁,开销就不能被忽略)
三 final,finally,finalize
final 可以用来修饰类、方法、变量,分别有不同的意义,final 修饰的 class 代表不可以继承扩展,final 的变量是不可以修改的,而 final 的方法也是不可以重写的(override)。
finally 则是 Java 保证重点代码一定要被执行的一种机制。我们可以使用 try-finally 或者 try-catch-finally 来进行类似关闭 JDBC 连接、保证 unlock 锁等动作。
finalize 是基础类 java.lang.Object 的一个方法,它的设计目的是保证对象在被垃圾收集前完成特定资源的回收。finalize 机制现在已经不推荐使用,并且在 JDK 9 开始被标记为 deprecated。
final 相关
1,final不是immutable
2,要实现immutable的类,需要
a,Class自身声明为final
b,成员变量定义为private和final,不实现setter方法
c,构造对象时,成员变量使用深度拷贝来初始化,而不是直接赋值
d,如果确定要实现getter方法,或其他可能会返回内部状态的方法,使用copy-on-write原则,创建私有的copy。
finally
1,不要在finally中使用return语句
2,finally总是执行,除非程序或者线程被中断(System.exit/无限循环/线程被杀死)
finalize
finalize的执行是和垃圾收集关联在一起的,被设计为在对象被垃圾收集前调用。一旦实现了非空的finalize方法,JVM需要对它进行特殊处理,就会导致相应对象回收呈现数量级上的变慢。
finalize的执行,是不可预测不能保证的
推荐:资源用完即显示释放,或者利用资源池来尽量重用
替换finalize
Java平台目前正在逐步使用java.lang.ref.Cleaner来替换掉原有的finalize实现。Cleaner的实现利用了幻象引用(PhantomReference),这是一种常见的所谓post-mortem清理机制。(利用幻象引用和引用队列,可以保证对象被彻底销毁前做一些类似资源回收的工作,比如关闭文件描述符,操作系统的有限资源,它比finalize更加轻量更加可靠)
强引用,软应用,弱引用,幻想引用
不同的引用类型,体现的是对象的不同的可达性(reachable)状态和对垃圾收集的影响
强引用(Strong Reference):普通的对象引用
没有引用关系,超过了引用的作用域,或者被指向为null就可以被垃圾收集回收了。具体回收实际依赖垃圾收集策略
软应用(SoftReference):比强引用稍微弱点,可以豁免一些垃圾收集。只有JVM认为内存不足时,才会去试图回收软应用的对象。
JVM会确保在抛出OutofMemoryError之前,清理软引用指向的对象。
软引用常用来实现内存敏感的缓存,如果还有空闲空间,就可以暂时保留缓存,当内存不足时清理掉,这样就保证了使用缓存的同时,不会耗尽内存。
弱引用(WeakReference):不能是对象豁免垃圾收集,仅仅提供一种访问在弱引用状态下对象的途径。可以用来构建一种没有特定约束的关系,比如维护一种非强制的映射关系,如果试图获取对象还在,就是用它,否则重新实例化。它同样是很多缓存实现的选择。
幻象引用,也称为虚引用。不能通过它访问对象。幻象引用仅仅是提供了一种确保对象被finalize以后做某些事情的机制。比如通常用来做所谓的Post-Mortem清理机制,也有人利用幻象引用来监控对象的创建和销毁。
对象可达性状态流转分析
Java 定义的不同可达性级别(reachability level)
强可达(Strongly Reachable),就是当一个对象可以有一个或多个线程可以不通过各种引用访问到的情况。比如,新创建一个对象,那么创建它的线程对它就是强可达。
软可达(Softly Reachable),就是只能通过软引用才能访问到对象的状态。
弱可达(Weakly Reachable),类似前面提到的,就是无法通过强引用或者软引用访问,只能通过弱引用访问时的状态。这是十分临近 finalize 状态的时机,当弱引用被清除的时候,就符合 finalize 的条件了。
幻象可达(Phantom Reachable),就是没有强、软、弱引用关联,并且 finalize 过了,只有幻象引用指向这个对象的时候。
不可达(unreachable),意味着对象可以被清除了。
除了幻象引用(因为 get 永远返回 null),如果对象还没有被销毁,都可以通过 get 方法获取原有对象。这意味着,利用软引用和弱引用,我们可以将访问到的对象,重新指向强引用,也就是人为的改变了对象的可达性状态!
对于软引用、弱引用之类,垃圾收集器可能会存在二次确认的问题,以保证处于弱引用状态的对象,没有改变为强引用。
引用队列(ReferenceQueue)使用
谈到各种引用的编程,就必然要提到引用队列。我们在创建各种引用并关联到相应对象时,可以选择是否需要关联引用队列,JVM 会在特定时机将引用 enqueue 到队列里,我们可以从队列里获取引用(remove 方法在这里实际是有获取的意思)进行相关后续逻辑。尤其是幻象引用,get 方法只返回 null,如果再不指定引用队列,基本就没有意义了。看看下面的示例代码。利用引用队列,我们可以在对象处于相应状态时(对于幻象引用,就是前面说的被 finalize 了,处于幻象可达状态),执行后期处理逻辑。
Object counter = new Object();ReferenceQueue
refQueue = new ReferenceQueue<>();
PhantomReference
显式地影响软引用垃圾收集
软引用通常会在最后一次引用后,还能保持一段时间,默认值是根据堆剩余空间计算的(以 M bytes 为单位)。从 Java 1.3.1 开始,提供了 -XX:SoftRefLRUPolicyMSPerMB 参数,我们可以以毫秒(milliseconds)为单位设置。
这个剩余空间,其实会受不同 JVM 模式影响,对于 Client 模式,比如通常的 Windows 32 bit JDK,剩余空间是计算当前堆里空闲的大小,所以更加倾向于回收;而对于 server 模式 JVM,则是根据 -Xmx 指定的最大值来计算。本质上,这个行为还是个黑盒,取决于 JVM 实现,即使是上面提到的参数,在新版的 JDK 上也未必有效,另外 Client 模式的 JDK 已经逐步退出历史舞台。所以在我们应用时,可以参考类似设置,但不要过于依赖它。
可以通过底层 API 来达到强引用的效果,这就是所谓的设置 reachability fence。
为什么需要这种机制呢?考虑一下这样的场景,按照 Java 语言规范,如果一个对象没有指向强引用,就符合垃圾收集的标准,有些时候,对象本身并没有强引用,但是也许它的部分属性还在被使用,这样就导致诡异的问题,所以我们需要一个方法,在没有强引用情况下,通知 JVM 对象是在被使用的。