Java 中 InvokeDynamic指令是干什么的?
JVM字节码指令集一直比较稳定,一直到Java7中才增加了一个InvokeDynamic 指令,这是JAVA为了实现『动态类型语言』支持而做的一种改进;但是在Java7中并没有提供直接生成InvokeDynamic 指令的方法,需要借助ASM这种底层字节码工具来产生InvokeDynamic 指令;
而到了Java 8中,InvokeDynamic 则成为了实现高级平台特性的一个首要机制;
使用这个操作码的最明确、简单的例子便是lambda表达式;
Java 中 编写多线程程序的时候你会遵循哪些最佳实践?
这是我在写Java并发程序的时候遵循的一些最佳实践:
1、 给线程命名,这样可以帮助调试;
2、 最小化同步的范围,而不是将整个方法同步,只对关键部分做同步;
3、 如果可以,更偏向于使用 volatile 而不是 synchronized;
4、 使用更高层次的并发工具,而不是使用 wait() 和 notify() 来实现线程间通信,如 BlockingQueue,CountDownLatch 及 Semeaphore;
5、 优先使用并发集合,而不是对集合进行同步;并发集合提供更好的可扩展性;
Java 中 32 位 和 64 位 JVM 中,int 的长度是多数?
1、 理论上说上 32 位的 JVM 堆内存可以到达 2^32,即 4GB,但实际上会比这个小很多;
2、 不同操作系统之间不同,如 Windows 系统大约 1.5 GB,Solaris 大约 3GB;
3、 64 位 JVM允许指定最大的堆内存,理论上可以达到 2^64,这是一个非常大的数字,实际上你可以指定堆内存大小到 100GB;
4、 甚至有的 JVM,如 Azul,堆内存到 1000G 都是可能的;
5、 32 位和 64 位的 JVM 中,int 类型变量的长度是相同的,都是 32 位或者 4 个字节(一个字节8位);
5、 Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位;
意思就是说,在 32 位 和 64 位 的Java 虚拟机中,int 类型的长度是相同的;
Java 中 指出下面程序的运行结果?
class A {
static {
System.out.print("1");
}
public A() {
System.out.print("2");
}
}
class B extends A{
static {
System.out.print("a");
}
public B() {
System.out.print("b");
}
}
public class Hello {
public static void main(String[] args) {
A ab = new B();
ab = new B();
}
}
执行结果:1a2b2b;
创建对象时构造器的调用顺序是:
先初始化静态成员,然后调用父类构造器,再初始化非静态成员,最后调用自身构造器;
Java 中 我们开发过程中常用包有那些?
软件开发与应用中经常需要用到,其中有些包是必要的,若是离开它,还真不能做事情了;
1、java.lang包
该包提供了Java语言进行程序设计的基础类,它是默认导入的包;该包里面的Runnable接口和Object、Math、String、StringBuffer、System、Thread以及Throwable类需要重点掌握,因为它们应用很广;
2、java.util包
该包提供了包含集合框架、遗留的集合类、事件模型、日期和时间实施、国际化和各种实用工具类(字符串标记生成器、随机数生成器和位数组);
3、java.io包
该包通过文件系统、数据流和序列化提供系统的输入与输出;
4、java.net包
该包提供实现网络应用与开发的类;
5、java.sql包
该包提供了使用Java语言访问并处理存储在数据源(通常是一个关系型数据库)中的数据API;
6、java.awt包
AWT是用于创建用户图形界面的一个工具包,提供了一系列用于实现图形界面的组件,如窗口、按钮、文本框、对话框等,在JDK中针对每个组件都提供对应的Java类;
7、javax.swing包
这两个包提供了GUI设计与开发的类;java.awt包提供了创建界面和绘制图形图像的所有类,而javax.swing包提供了一组“轻量级”的组件,尽量让这些组件在所有平台上的工作方式相同;
8、java.text包
提供了与自然语言无关的方式来处理文本、日期、数字和消息的类和接口;
关于上述这些包结构,除了第一个包是自动导入外,其余的包都需要使用import语句导入,才可使用其包里面的类与接口;若想深入了解它们,请多阅读JDKAPI文档,同时,多使用这些包里的类与接口来解决问题和满足需求;
Java 中 Math.round(11.5) 等于多少?Math.round(-11.5)等于多少?
Math.round(11.5)的返回值是12,Math.round(-11.5)的返回值是-11;
四舍五入的原理是在参数上加0.5然后进行下取整;
Java 中 抽象类可以使用final修饰吗?
不能
定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此就会产生矛盾,所以 final 不能修饰抽象类;
可以自己试试,一般的编译器也会提示错误的;
Java 中 你是如何调用 wait() 方法的?使用 if 块还是循环?为什么?
处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出;
wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好;下面是一段标准的使用 wait 和 notify 方法的代码;
synchronized (monitor) {
// 判断条件谓词是否得到满足
while(!locked) {
// 等待唤醒
monitor.wait();
}
// 处理其他的业务逻辑
}
Java 中 对象的内存布局了解吗?
Java 中通过 new 关键字创建一个类的实例对象,对象存于内存的堆中并给其分配一个内存地址,那么是否想过如下这些问题:
1、 这个实例对象是以怎样的形态存在内存中的?
2、 一个Object对象在内存中占用多大?
3、 对象中的属性是如何在内存中分配的?
在 JVM 中,Java对象保存在堆中时,由以下三部分组成:
1、 对象头(object header) : 包括了关于堆对象的布局、类型、GC状态、同步状态和标识哈希码的基本信息;Java对象和vm内部对象都有一个共同的对象头格式;
2、 实例数据(Instance Data) :主要是存放类的数据信息,父类的信息,对象字段属性信息;
3、 对齐填充(Padding) :为了字节对齐,填充的数据,不是必须的;
如果对象是一个Java数组,那在对象头中还必须有一块用于记录数组长度的数据,因为虚拟机可以通过普通Java对象的元数据信息确定Java对象的大小,但是从数组的元数据中无法确定数组的大小;
Mark Word
用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、偏向时间戳等等;
Mark Word在32位JVM中的长度是32bit,在64位JVM中长度是64bit;我们打开openjdk的源码包,对应路径/openjdk/hotspot/src/share/vm/oops,Mark Word对应到C++的代码markOop.hpp,可以从注释中看到它们的组成,本文所有代码是基于Jdk1.8;
Mark Word在不同的锁状态下存储的内容不同;
在32位JVM中是这么存的
在64位JVM中是这么存的
虽然它们在不同位数的JVM中长度不一样,但是基本组成内容是一致的;
锁标志位(lock):区分锁状态,11时表示对象待GC回收状态, 只有最后2位锁标识(11)有效;
biased_lock:是否偏向锁,由于无锁和偏向锁的锁标识都是 01,没办法区分,这里引入一位的偏向锁标识位;
分代年龄(age):表示对象被GC的次数,当该次数到达阈值的时候,对象就会转移到老年代;
对象的hashcode(hash):运行期间调用System.identityHashCode()来计算,延迟计算,并把结果赋值到这里;当对象加锁后,计算的结果31位不够表示,在偏向锁,轻量锁,重量锁,hashcode会被转移到Monitor中;
偏向锁的线程ID(JavaThread):偏向模式的时候,当某个线程持有对象的时候,对象这里就会被置为该线程的ID; 在后面的操作中,就无需再进行尝试获取锁的动作;
epoch:偏向锁在CAS锁操作过程中,偏向性标识,表示对象更偏向哪个锁;
ptr_to_lock_record:轻量级锁状态下,指向栈中锁记录的指针;当锁获取是无竞争的时,JVM使用原子操作而不是OS互斥;这种技术称为轻量级锁定;在轻量级锁定的情况下,JVM通过CAS操作在对象的标题字中设置指向锁记录的指针;
ptr_to_heavyweight_monitor:重量级锁状态下,指向对象监视器Monitor的指针;如果两个不同的线程同时在同一个对象上竞争,则必须将轻量级锁定升级到Monitor以管理等待的线程;在重量级锁定的情况下,JVM在对象的ptr_to_heavyweight_monitor设置指向Monitor的指针;
Klass Pointer
即类型指针,是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例;
实例数据
如果对象有属性字段,则这里会有数据信息;如果对象无属性字段,则这里就不会有数据;根据字段类型的不同占不同的字节,例如boolean类型占1个字节,int类型占4个字节等等;
对齐数据
对象可以有对齐数据也可以没有;默认情况下,Java虚拟机堆中对象的起始地址需要对齐至8的倍数;
如果一个对象用不到8N个字节则需要对其填充,以此来补齐对象头和实例数据占用内存之后剩余的空间大小;
如果对象头和实例数据已经占满了JVM所分配的内存空间,那么就不用再进行对齐填充了;
所有的对象分配的字节总SIZE需要是8的倍数,如果前面的对象头和实例数据占用的总SIZE不满足要求,则通过对齐数据来填满;
为什么要对齐数据?
字段内存对齐的其中一个原因,是让字段只出现在同一CPU的缓存行中;如果字段不是对齐的,那么就有可能出现跨缓存行的字段;也就是说,该字段的读取可能需要替换两个缓存行,而该字段的存储也会同时污染两个缓存行;这两种情况对程序的执行效率而言都是不利的;其实对其填充的最终目的是为了计算机高效寻址;
至此,我们已经了解了对象在堆内存中的整体结构布局,如下图所示;
Java 中 乐观锁和悲观锁的理解及如何实现,有哪些实现方式?
悲观锁:
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁;传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁;再比如 Java里面的同步原语 synchronized 关键字的实现也是悲观锁;
乐观锁:
顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制;乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于 write_condition 机制,其实都是提供的乐观锁;在 Java中java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的;