问题1:谈谈你对Java平台的理解,“Java是解释执行”这句话正确吗?

对于Java平台的理解,可以从很多方面谈一下。上图是一个相对宽泛的蓝图,可以作为回答这个问题的蓝图。

这个说法是不准确的。Java的源代码,首先经过Javac编译器编译成字节码文件,然后,在运行时,通过JVM内嵌的解释器将字节码转换为最终的机器码。但是,常见的JVM,比如我们大多数情况使用的Oracle JDK提供的Hotspot JVM都提供了JIT(just in time)编译器,也就是通常所说的动态编译器,JIT能够在运行时将热点代码编译成机器码,这种情况下部分热点代码就属于编译执行,而不是解释执行。

继续说一下,解释执行和编译执行的问题。字节码 + JVM是Java实现跨平台的基础。JVM会通过类加载器(双亲委托机制)加载字节码,解释或者编译执行。主流的Java版本中,如JDK8实际是解释和编译混合的一种模式。通常,运行在server模式的JVM,会进行上万次调用以收集足够的信息进行高效的编译,client模式的这个上线是1500次。Hotspot JVM内置了两个JIT compiler,c1(client模式 - 适用于对启动速度敏感的应用,比如Java桌面应用) 和 c2(server模式 - 适用于长时间运行的服务器端应用设计)。默认采用所谓的分层编译。

其实还有一种新的编译方式,即AOT(Ahead of time),直接将字节码编译成机器码,这样就避免了JIT预热等各个方面的开销。Android 的ART使用的就是类似AOT的方式,就是比较占用空间。

另外,JVM作为一个强大的平台,不仅仅只有Java语言可以运行在JVM上,本质上合规的字节码都可以运行,Java语言自身也为此提供了便利。我们可以看到类似Groovy、JRuby、Jython、Scala、Clojure等大量JVM语言,活跃在不同的场景。

问题2:Exception和Error有什么区别,另外,运行时异常与一般异常有什么区别?

 

java核心类库在哪个文件夹 java的核心代码在哪里_字符串

 1、NoClassDefFoundError和ClassNotFoundException有什么区别?

最明显的区别就是:一个是Exception,一个是Error。

JDK API里面的解释:
a)NoClassDefFoundError 
当 Java 虚拟机或 ClassLoader 实例试图在类的定义中加载(作为通常方法调用的一部分或者作为使用 new 表达式创建的新实例的一部分),但无法找到该类的定义时,抛出此异常。 
当前执行的类被编译时,所搜索的类定义存在,但运行时无法再找到该定义。

b)ClassNotFoundException 
当应用程序试图使用以下方法通过字符串名加载类时,抛出该异常: 
* Class 类中的 forName 方法。 
* ClassLoader 类中的 findSystemClass 方法。 
* ClassLoader 类中的 loadClass 方法。 
2、随着Java语言得发展,引入了一些更加便利的特性,比如try-with-resources和multiple catch。示例如下:

try (BufferedReader br = new BufferedReader(…);
     BufferedWriter writer = new BufferedWriter(…)) {// Try-with-resources
// do something
catch ( IOException | XEception e) {// Multiple catch
   // Handle it
}

3、异常处理的基本原则

  • 尽量不要捕获类似Exception这样的通用异常,而是应该捕获特定异常;进一步讲,除非深思熟虑了,否则不要捕获Throwable或者Error,这样很难保证我们能够正确处理OOM;
  • 不要生吞异常;因为如果不把异常跑出来,或者也没有输出日志之类的,可能会导致非常难以诊断的诡异情况;

看下如下代码:

try {
   // 业务代码
   // …
} catch (IOException e) {
    e.printStackTrace();
}

记住:在产品代码中,通常是不允许这样处理的。因为在稍微复杂一点的生产系统中,标准出错不是合适的输出选项,因为你很难判断到底输出到哪里去了。尤其是分布式系统,如果发生异常,但是无法找到堆栈轨迹,这纯属为诊断设置障碍了。最好使用产品日志,详细地输出到日志系统里。

4、从性能的角度审视Java的异常处理机制

  • try-catch代码段会产生额外的性能开销,或者换个角度说,往往会影响JVM对代码进行优化,所以建议仅捕获有必要的代码段,尽量不要一个大的try包住整段的代码;同时,避免利用异常机制控制代码流程,这远比我们通常意义上的条件语句要低效。
  • Java每实例化一个Exception,都会对当时的栈进行快照,这是一个相对比较重的操作。如果发生的非常频繁,开销就不能被忽略了。当我们的服务出现反应变慢、吞吐量下降的时候,检查发生最频发的Exception也是一种思路。

5、匿名内部类,访问局部变量时,局部变量为啥要用final来修饰吗?

Java inner class实际会copy一份,不是去直接使用局部变量,final可以防止出现数据一致性问题。

问题3:强引用、软引用、弱引用、虚引用的区别

虚引用仅仅是提供了一种确保对象被finalize以后(处于虚可达状态),做某些事情的机制,比如,通常用来做所谓的Post-Mortem清理机制。Java平台自身的Cleaner机制等。

判断对象可达性,是JVM垃圾收集器决定如何处理对象的一部分考虑。

所有引用类型,都是抽象类java.lang.ref.Reference的子类,提供了get()方法。除了虚引用(因为get永远返回null),如果对象还没有被销毁,都可以通过get方法获取原有对象。这意味着,利用软引用和弱引用,我们可以将访问到的对象,重新指向强引用,也就是人为的改变对象的可达性状态。所以,对于软引用、弱引用之类,垃圾回收器可能会存在二次确认的问题,以保证处于该状态的对象,没有改变为强引用。

谈到各种引用的编程,就必然要提到引用队列。我们会创建各种引用并关联到响应对象,可以选择是否需要关联引用队列,JVM会在特定时机将引用enqueue到队列里,我们可以从队列里获取引用,然后进行相关后续逻辑。尤其是虚引用,get方法只返回null,如果再不指定引用队列,基本就没有意义了。

Object counter = new Object();
ReferenceQueue refQueue = new ReferenceQueue<>();
PhantomReference<Object> p = new PhantomReference<>(counter, refQueue);
counter = null;
System.gc();
try {
    // Remove 是一个阻塞方法,可以指定 timeout,或者选择一直阻塞
    Reference<Object> ref = refQueue.remove(1000L);
    if (ref != null) {
        // do something
    }
} catch (InterruptedException e) {
    // Handle it
}

Java软引用什么时候被回收

问题4:String、StringBuilder、StringBuffer

1、StringBuilder与StringBuffer(线程安全)

StringBuilder与StringBuffer底层都是利用可修改的(char,JDK9以后是byte)数组,二者都继承自AbstractStringBuilder,里面包含了基本的操作,区别在于最终的方法是否加了Synchronized。注意:数组的大小默认是16,超过则进行扩容。

2、字符串拼接

String strByBuilder  = new
StringBuilder().append("aa").append("bb").append("cc").append
            ("dd").toString();
             
String strByConcat = "aa" + "bb" + "cc" + "dd";

非静态的拼接逻辑在JDK8中会自动被javac转换为StringBuilder操作;而在JDK9里面,则体现了思路的变化。Java9利用InvokeDynamic,将字符串拼接的优化与javac生成的字节码解耦,假设未来JVM增强相关运行时实现,将不再需要依赖javac的任何修改。

3、字符串缓存String.intern()

String在Java6以后提供了intern()方法,目的是提示JVM把相应字符串缓存起来,以备重复使用。在我们创建字符串对象并调用intern()方法的时候,如果已经有缓存的字符串,就会返回缓存里的实例,否则将其缓存起来。

上述看起来不错,实际却是大跌眼镜。一般使用Java6这种历史版本并不推荐大量使用intern(),为什么?魔鬼存在于细节之中,被缓存的字符串是保存在所谓PermGen里的,也就是臭名昭著的“永久代”,这个空间是很有限的,也基本不会被FullGC之外的垃圾收集照顾。所以,如果使用不当,OOM就会光顾。

在后续的版本中,这个缓存被放置在了堆中,这样就极大的避免了永久代占满的问题,甚至永久代在JDK8中被MetaSpace(元数据区)替代了。而且,默认缓存大小也在不断地扩大中,从最初的1009,到7u40以后被修改为60013。

Intern是一种显式地重拍机制,但是有一定的副作用,因为需要开发者写代码时明确调用,一是不方便,每一个都显式调用非常麻烦;另外就是我们很难保证效率,应用开发阶段很难清楚地预计字符串的重复情况,有人认为这是一种污染代码的实践。

幸好在Oracle JDK 8u20之后,推出了一个新的特性,也就是G1 GC下的字符串排重。它是通过将相同数据的字符串指向同一份数据做出来的,是JVM的底层的改变,并不需要Java类库做什么修改。注意:这个功能默认是关闭的,需开启。

4、字符串创建的机理

由于String在Java世界中使用过于频繁,Java为了避免在一个系统中产生大量的String对象,引入了字符串常量池。其运行机制是:创建一个字符串时,首先检查池中是否有值相同的字符串对象,如果有则不需要创建直接从池中刚查找到的对象引用;如果没有则新建字符串对象,返回对象引用,并且将新创建的对象放入池中。但是,通过new方法创建的String对象是不检查字符串池的,而是直接在堆区或栈区创建一个新的对象,也不会把对象放入池中。上述原则只适用于通过直接量给String对象引用赋值的情况。

举例:String str1 = "123"; //通过直接量赋值方式,放入字符串常量池
 String str2 = new String(“123”);//通过new方式赋值方式,不放入字符串常量池



注意:String提供了inter()方法。调用该方法时,如果常量池中包括了一个等于此String对象的字符串(由equals方法确定),则返回池中的字符串。否则,将此String对象添加到池中,并且返回此池中对象的引用。