1. jdk 和 jre 的区别:
JDK:Java Development Kit 的简称,java 开发工具包,提供了 java 的开发环境和运行环境。
JRE:Java Runtime Environment 的简称,java 运行环境,为 java 的运行提供了所需环境。
2. == 和 equals
“==”
对基本类型来说:是比较的值是否相等
对引用类型来说:是比较的引用是否相等
equals
其实也是 值得比较,String 和 Integer 重写了 equals 方法 把它变成了值得比较
例如
String a1 = new String("a");
String b1 = new String(“a”);
a1.equals(b1); 显示是 true
A a1 = new A("a");
A b1 = new A(“a”);
a1.equals(b1); 显示是 false
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
3. final 的关键词在java 的作用
修饰类 则该类不可被继承
修饰方法 则该方法不可被重写
修饰成员变量 则被修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改
4. 抽象类必须要有抽象方法么?抽象类 可以用 final 修饰么?抽象类和普通类的区别是什么?
4.1 抽象类必须要有抽象方法么? 可以
4.2 抽象类 可以用 final 修饰么? 不可以, 被修饰了就不能被继承了
4.3 抽象类不能直接实例化,普通类可以直接实例化。
普通类不能包含抽象方法,抽象类可以包含抽象方法。
5.接口和抽象类的区别;
抽象类: 修饰符为 abstract
接口: interface
抽象类 被继承 extends ,只能被继承一次。 接口 要用 implements, 可以多个
抽象类 可以有构造函数, 接口没有
接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修饰符。
6.List、Set、Map 之间的区别是什么?

7.HashMap 和 Hashtable 有什么区别?
1)hashMap去掉了HashTable 的contains方法,但是加上了containsValue()和containsKey()方法。
2)hashTable同步的,而HashMap是非同步的,效率上比hashTable要高。
3)hashMap允许空键值,而hashTable不允许。
8.类的加载过程
java编译器将 .java 文件编译成扩展名为 .class 的文件。.class 文件中保存着java转换后,虚拟机将要执行的指令。当需要某个类的时候,java虚拟机会加载 .class 文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程被称为类的加载。
8.1 加载
ClassLoader 通过一个类的完全限定名(包含类名和包名)查找类的字节码文件,并利用字节码文件创建一个class对象
8.2 验证
为静态变量分配内存并设初始值,这里不包含final修饰的static ,因为final在编译的时候就已经分配了。这里不会为实例变量分配初始化,类变量会分配在方法区中,实例变量会随着对象分配到Java堆中。
8.3 解析
是把常量池中的符号引用替换成直接引用
8.4 初始化
是类加载的最后阶段,如果该类具有父类就进行对父类进行初始化,执行其静态初始化器(静态代码块)和静态初始化成员变量。(前面已经对static 初始化了默认值,这里我们对它进行赋值,成员变量也将被初始化)
9.forName和loaderClass区别
Class.forName()得到的class是已经初始化完成的。
Classloader.loaderClass得到的class是还没有 验证,准备,解析三个过程被的。
10.双亲委派
双亲委派模式要求除了顶层的启动类加载器之外,其余的类加载器都应该有自己的父类加载器,但是在双亲委派模式中父子关系采取的并不是继承的关系,而是采用组合关系来复用父类加载器的相关代码。
protected Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
// 增加同步锁,防止多个线程加载同一类
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
c = parent.loadClass(name, false);
} else { // ExtClassLoader没有继承BootStrapClassLoader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// AppClassLoader去我们项目中查找是否有这个文件,如有加载进来
// 没有就到用户自定义ClassLoader中加载。如果没有就抛出异常
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
工作原理
如果一个类收到了类加载的请求,它并不会自己先去加载,而是把这个请求委托给父类加载器去执行,如果父类加载器还存在父类加载器,则进一步向上委托,依次递归,请求最后到达顶层的启动类加载器,如果弗雷能够完成类的加载任务,就会成功返回,倘若父类加载器无法完成任务,子类加载器才会尝试自己去加载,这就是双亲委派模式。就是每个儿子都很懒,遇到类加载的活都给它爸爸干,直到爸爸说我也做不来的时候,儿子才会想办法自己去加载。
优势
采用双亲委派模式的好处就是Java类随着它的类加载器一起具备一种带有优先级的层次关系,通过这种层级关系可以避免类的重复加载,当父亲已经加载了该类的时候,就没有必要子类加载器(ClassLoader)再加载一次。其次是考虑到安全因素,Java核心API中定义类型不会被随意替换,假设通过网路传递一个名为java.lang.Integer的类,通过双亲委派的的模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字类,发现该类已经被加载,并不会重新加载网络传递过来的java.lang.Integer.而之际返回已经加载过的Integer.class,这样便可以防止核心API库被随意篡改。
类与类加载器
在JVM中标识两个Class对象,是否是同一个对象存在的两个必要条件
类的完整类名必须一致,包括包名。
加载这个ClassLoader(指ClassLoader实例对象)必须相同。
对象的创建过程
当虚拟机遇到一个new的指令的时候,首先去检查这个指令是否能在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经被加载,解析和初始化过。如果没有则执行相应初始化的过程。在类加载检查通过后,接下来虚拟机将为新生对象分配内存,对象所需要的内存的大小在类加载完成后便可以完成确定。内存分配完成以后,虚拟机需要将分配的内存空间都初始化为零值,保证了对象的实例字段在Java代码中可以不赋予初值就直接使用,程序能访问到这些字段的数据类型对应的零值。再接下来对象需要进行必要的设置,这个对象是哪个类的实例,如何才能找到这个类的元数据信息,如何找到对象的哈希码,对象的GC分带年龄。
Java堆如果是规整的采取:指针碰撞,
Java堆如果不是规整的话:空闲列表,在内存中直接分配一个足够大的内存空间划分给对象。
对象创建是非常平凡的,在多线程的程序中会产生线程安全的问题,所以解决这个问题有两种方式
使用CSA配上失败重试的方式来保证原子性
内存分配动作按照线程划分在不同的空间之中进行,即每个线程在java堆中预先分配一个小块的内存成为本地分配缓冲,TLAB,哪个线程需要分配内存就在哪个线程的TALB上分配,只有在TALB用完之后才会重新分配新的TALB的时候才会同步锁定。
网友评论