Java基本知识点

/ Java / 没有评论 / 2074浏览

类的加载过程

以Person person = new Person()为例进行说明:

  1. 因为new用到了Person.class,所以会先找到Person.class文件,并加载到内存中;
  2. 执行该类中的static代码块,如果有的话,给Person.class类进行初始化;
  3. 在堆内存中开辟空间分配内存地址;
  4. 在堆内存中建立对象的特有属性,并进行默认初始化;
  5. 对属性进行显示初始化;
  6. 对对象进行构造代码块初始化;
  7. 对对象进行与之对应的构造函数进行初始化;
  8. 将内存地址付给栈内存中的p变量

JVM相关知识,GC机制

JVM基本构成

alt

从上图可知,JVM主要包括四个部分:

  1. 类加载器(ClassLoader):在JVM启动时或者在类运行时将需要的class加载到JVM中。(下图表示了从 java 源文件到 JVM 的整个过程,可配合理解。) alt
  2. 执行引擎:负责执行class文件中包含的字节码指令;
  3. 内存区(也叫运行时数据区):是在JVM运行的时候操作所分配的内存区。运行时内存区主要可以划分为5个区域,如图: alt
  1. 本地方法接口:主要是调用C或C++实现的本地方法及返回结果。

GC机制

垃圾收集器一般必须完成两件事:检测出垃圾;回收垃圾。怎么检测出垃圾?一般有以下几种方法:

引用计数法

给一个对象添加引用计数器,每当有个地方引用它,计数器就加1;引用失效就减1。好了,问题来了,如果我有两个对象A和B,互相引用,除此之外,没有其他任何对象引用它们,实际上这两个对象已经无法访问,即是我们说的垃圾对象。但是互相引用,计数不为0,导致无法回收,所以还有另一种方法:

可达性分析算法

以根集对象为起始点进行搜索,如果有对象不可达的话,即是垃圾对象。这里的根集一般包括java栈中引用的对象、方法区常良池中引用的对象、本地方法中引用的对象等。总之,JVM在做垃圾回收的时候,会检查堆中的所有对象是否会被这些根集对象引用,不能够被引用的对象就会被垃圾收集器回收。

一般回收算法也有如下几种:

  1. 标记-清除(Mark-sweep)
  2. 复制(Copying
  3. 标记-整理(Mark-Compact)
  4. 分代收集算法 具体的解释可以参考本篇文章还不点我?

类的加载器,双亲机制,Android的类加载器

类的加载器

大家都知道,当我们写好一个 Java 程序之后,不是管是 CS 还是 BS应用,都是由若干个 .class 文件组织而成的一个完整的 Java 应用程序,当程序在运行时,即会调用该程序的一个入口函数来调用系统的相关功能,而这些功能都被封装在不同的 class 文件当中,所以经常要从这个 class 文件中要调用另外一个 class 文件中的方法,如果另外一个文件不存在的,则会引发系统异常。而程序在启动的时候,并不会一次性加载程序所要用的所有 class 文件,而是根据程序的需要,通过 Java 的类加载机制(ClassLoader)来动态加载某个 class 文件到内存当中的,从而只有 class 文件被载入到了内存之后,才能被其它 class 所引用。所以 ClassLoader 就是用来动态加载 class 文件到内存当中用的。

双亲机制

原理介绍

ClassLoader 使用的是双亲委托模型来搜索类的,每个 ClassLoader实例都有一个父类加载器的引用(不是继承的关系,是一个包含的关系),虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。当一个ClassLoader实例需要加载某个类时,它会试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的,首先由最顶层的类加载器Bootstrap ClassLoader试图加载,如果没加载到,则把任务转交给Extension ClassLoader试图加载,如果也没加载到,则转交给App ClassLoader 进行加载,如果它也没有加载得到的话,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类。如果它们都没有加载到这个类时,则抛出ClassNotFoundException 异常。否则将这个找到的类生成一个类的定义,并将它加载到内存当中,最后返回这个类在内存中的 Class实例对象。

为什么要使用双亲委托这种模型呢?

因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的 String,除非你改变 JDK 中 ClassLoader 搜索类的默认算法。

但是 JVM 在搜索类的时候,又是如何判定两个 class 是相同的呢?

JVM 在判定两个 class 是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM 才认为这两个 class 是相同的。就算两个 class 是同一份 class 字节码,如果被两个不同的 ClassLoader 实例所加载,JVM 也会认为它们是两个不同 class。比如网络上的一个 Java 类org.classloader.simple.NetClassLoaderSimple,javac 编译之后生成字节码文件 NetClassLoaderSimple.class,ClassLoaderA 和ClassLoaderB 这两个类加载器并读取了 NetClassLoaderSimple.class文件,并分别定义出了 java.lang.Class 实例来表示这个类,对于 JVM 来说,它们是两个不同的实例对象,但它们确实是同一份字节码文件,如果试图将这个 Class 实例生成具体的对象进行转换时,就会抛运行时异常 java.lang.ClassCaseException,提示这是两个不同的类型。

Android类加载器

对于 Android 而言,最终的 apk 文件包含的是 dex 类型的文件,dex文件是将 class 文件重新打包,打包的规则又不是简单地压缩,而是完全对class 文件内部的各种函数表,变量表进行优化,产生一个新的文件,即dex文件。因此加载这种特殊的 Class 文件就需要特殊的类加载器DexClassLoader。

集合框架,list,map,set

list,map,set都有哪些具体的实现类,区别都是什么?

Set和List对比:

Map适合储存键值对的数据。

线程安全集合类与非线程安全集合类

具体的使用介绍

ArrayList与LinkedList的区别和适用场景

  1. Arraylist:
  1. LinkedList:

适用场景分析:

ArrayList 与 Vector 的区别和适用场景

  1. ArrayList有三个构造方法:
//构造一个具有指定初始容量的空列表
public ArrayList(int initialCapacity)
//构造一个初始容量为10的空列表
public ArrayList()
//构造一个包含指定 collection 的元素的列表
public ArrayList(Collection<? extends E> c)
  1. Vector有四个构造方法:
//使用指定的初始容量和等于零的容量增量构造一个空向量。
public Vector()
//构造一个空向量,使其内部数据数组的大小,其标准容量增量为零。
public Vector(int initialCapacity)
//构造一个包含指定 collection 中的元素的向量
public Vector(Collection<? extends E> c)
//使用指定的初始容量和容量增量构造一个空的向量
public Vector(int initialCapacity,int capacityIncrement)
  1. ArrayList 和 Vector 都是用数组实现的,主要有这么三个区别:

适用场景:

  1. Vector是线程同步的,所以它也是线程安全的,而ArrayList是线程异步的,是不安全的。如果不考虑到线程的安全因素,一般用ArrayList效率比较高。
  2. 如果集合中的元素的数目大于目前集合数组的长度时,在集合中使用数据量比较大的数据,用Vector有一定的优势。

3. HashSet与Treeset的适用场景

适用场景分析:

-HashSet是基于Hash算法实现的,其性能通常都优于TreeSet。

4. HashMap与TreeMap、HashTable的区别及适用场景

  1. HashMap: 非线程安全

适用场景分析:

concurrentHashmap原理,原子类

ConcurrentHashMap 作为一种线程安全且高效的哈希表的解决方案,尤其其中的"分段锁"的方案,相比 HashTable 的全表锁在性能上的提升非常之大.

volatile原理

在《Java并发编程:核心理论》一文中,我们已经提到过可见性、有序性及原子性问题。通常情况下我们可以通过Synchronized关键字来解决这些个问题,不过如果对Synchronized原理有了解的话,应该知道Synchronized是一个比较重量级的操作,对系统的性能有比较大的影响,所以,如果有其他解决方案,我们通常都避免使用Synchronized来解决问题。而volatile关键字就是Java中提供的另一种解决可见性和有序性问题的方案。对于原子性,需要强调一点,也是大家容易误解的一点:对volatile变量的单次读/写操作可以保证原子性的,如long和double类型变量,但是并不能保证i++这种操作的原子性,因为本质上i++是读、写两次操作。

参考文章

https://www.cnblogs.com/paddix/p/5428507.html

多线程的使用场景

使用多线程就一定效率高吗? 有时候使用多线程并不是为了提高效率,而是使得CPU能够同时处理多个事件。

  1. 为了不阻塞主线程,启动其他线程来做好事的事情,比如APP中耗时操作都不在UI中做.
  2. 实现更快的应用程序,即主线程专门监听用户请求,子线程用来处理用户请求,以获得大的吞吐量.感觉这种情况下,多线程的效率未必高。这种情况下的多线程是为了不必等待,可以并行处理多条数据。比如JavaWeb的就是主线程专门监听用户的HTTP请求,然后启动子线程去处理用户的HTTP请求。
  3. 某种虽然优先级很低的服务,但是却要不定时去做。比如Jvm的垃圾回收。
  4. 某种任务,虽然耗时,但是不耗CPU的操作时,开启多个线程,效率会有显著提高。比如读取文件,然后处理。 磁盘IO是个很耗费时间,但是不耗CPU计算的工作。所以可以一个线程读取数据,一个线程处理数据。肯定比一个线程读取数据,然后处理效率高。因为两个线程的时候充分利用了CPU等待磁盘IO的空闲时间。

JAVA常量池

Interger 中的128(-128~127)

  1. 当数值范围为-128~127时:如果两个 new 出来 Integer 对象,即使值相同,通过“==”比较结果为false,但两个对象直接赋值,则通过“==”比较结果为“true,这一点与String非常相似。
  2. 当数值不在-128~127时,无论通过哪种方式,即使两个对象的值相等,通过“==”比较,其结果为false;
  3. 当一个Integer对象直接与一个int基本数据类型通过“==”比较,其结果与第一点相同;
  4. Integer对象的hash值为数值本身;

为什么是-128-127?

在 Integer 类中有一个静态内部类 IntegerCache,在 IntegerCache类中有一个 Integer 数组,用以缓存当数值范围为-128~127时的Integer 对象。

简单介绍一下java中的泛型,泛型擦除以及相关的概念

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

在 Java SE 1.5 之前,没有泛型的情况的下,通过对类型 Object 的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

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

  1. 泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。
  2. 同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。
  3. 泛型的类型参数可以有多个。
  4. 泛型的参数类型可以使用extends语句,例如。习惯上称为“有界类型”。
  5. 泛型的参数类型还可以是通配符类型。例如Class<?> classType = Class.forName("java.lang.String");

泛型擦除以及相关的概念

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

类型擦除引起的问题及解决方法

  1. 先检查,在编译,以及检查编译的对象和引用传递的问题
  2. 自动类型转换
  3. 类型擦除与多态的冲突和解决方法
  4. 泛型类型变量不能是基本数据类型
  5. 运行时类型查询
  6. 异常中使用泛型的问题
  7. 数组(这个不属于类型擦除引起的问题)
  8. 类型擦除后的冲突
  9. 泛型在静态方法和静态类中的问题