Java基础

整理了下Java相关的基础知识。有些内容参考了其他博客。有误之处,尽请斧正~

一、Java IO/NIO

1.ava IO 中有哪些常用的类,字节流、字符流、接口、实现类、方法阻塞

       java.io包中最重要的就是5个类和1个接口,5个类:File、OutPutStream、InPutStream、Reader、Writer;接口:Serializable.

  • File(文件特征与管理):用于文件和目录信息的描述,例如生成新目录、删除、修改文件,判断文件所在路径等。
  • InputStream(二进制格式操作):抽象类、基于字节的输入操作,是所有输入流的父类。
  • OutpuStream(二进制格式操作):抽象类、基于字节的输出操作,是所有输出流的父类。
  • Reader(文件格式操作):抽象类、基于字符的输入操作。
  • Writrer(文件格式操作):抽象类、基于字符的输出操作。
  • RandomAccessFile(随机文件操作):一个独立的类,直接继承于Object。可以直接从文件的任意位置进行输入输出操作。

2、Java IO流特性。

  • 先进先出。最先写入输出流的数据最先被输出流读到。
  • 顺序存取。可以一个接一个地往流中写入一串字节,读取时也按写入顺序读取一串字节,不能随机访问中间数据。
  • 只读或只写。每个流只能是输入或输出流的一种,不能同时具备两个功能。输入流只能进行读操作,输出流只能进行写操作。

3、字节流和字符流的区别?

  • 字节流没有缓冲区,是直接输出的。字符流是输出到缓冲区的。因此在输出时,字节流不调用close()方法时,信息已经输出了,而字符流只有在调用close()方法关闭缓冲区时,信息才输出。要想字符流在未关闭时输出信息,则需要手动调用flush()方法。
  • 读写单位不同。字节流以字节(8bit)为单位,字符流以字符为单位,根据码表映射字符,一次可能读取多个字节。
  • 处理对象不同。字节流能处理所有对象的数据,而字符流只能处理字符类型数据。

4、Java IO有哪些异常?

      主要是IOException异常类。IOException有以下子类:

  •  EOFException:非正常到达文件末尾或输入流尾时,抛出此类异常。
  •  FileNotFoundException:文件找不到时,抛出此类异常。
  •  InterruptedIOException:当I/O操作被中断时,抛出此类异常。

5、IO、NIO、AIO的区别?

  • IO: 服务器的实现模式是一个连接一个线程,这样的模式很明显的一个缺陷是:由于客户端连接数与服务器线程数成正比关系,可能造成不必要的线程开销,严重的还将导致服务器内存溢出。当然,这种情况可以通过线程池机制改善,但并不能从本质上消除这个弊端。
  • NIO:从JDK1.4开始,JDK引入的新的IO模型NIO,它是同步非阻塞的。而服务器的实现模式是多个请求一个线程,即请求会注册到多路复用器Selector上,多路复用器轮询到连接有IO请求时才启动一个线程处理。
  • AIO:JDK1.7发布了NIO2.0,这就是真正意义上的异步非阻塞,服务器的实现模式为多个有效请求一个线程,客户端的IO请求都是由OS先完成再通知服务器应用去启动线程处理(回调)。

6、IO和NIO的适用场景?

       并发连接数不多时采用BIO,因为它编程和调试都非常简单,但如果涉及到高并发的情况,应选择NIO或AIO,更好的建议是采用成熟的网络通信框架Netty。

7、Java IO模型?

  •  同步阻塞IO: 在此种方式下,用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后,用户进程才能运行。JAVA传统的IO模型属于此种方式!
  •  同步非阻塞IO: 在此种方式下,用户进程发起一个IO操作以后边可返回做其它事情,但是用户进程需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费。其中目前JAVA的NIO就属于同步非阻塞IO。
  •  异步阻塞IO:   此种方式下是指应用发起一个IO操作以后,不等待内核IO操作的完成,等内核完成IO操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或者主动的去询问IO是否完成,  那么为什么说是阻塞的呢?因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄(如果从UNP的角度看,select属于同步操作。因为select之后,进程还需要读写数据),从而提高系统的并发性!
  •  异步非阻塞IO:  在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作, 因为真正的IO读取或者写入操作已经由内核完成了。目前Java中还没有支持此种IO模型。

8、Reactor和Proactor模式?

  • Reactor模式:Reactor模式应用于同步I/O的场景
  • Proactor模式:Proactor运用于异步I/O操作
  • Reactor和Proactor模式的主要区别就是真正的读取和写入操作是有谁来完成的,Reactor中需要应用程序自己读取或者写入数据,而Proactor模式中,应用程序不需要进行实际的读写过程,它只需要从缓存区读取或者写入即可,操作系统会读取缓存区或者写入缓存区到真正的IO设备.

9、代码实现递归读取文件夹下的文件



import java.io.File;
import java.util.ArrayList;

public class ReadFile {
    private static ArrayList<String> listName = new ArrayList<>();

    public static void readFile(String filePath){
        File file = new File(filePath);

        if(!file.isDirectory()){
            listName.add(file.getName());
        }else{
            String[] fileList = file.list();
            for(int i=0; i<fileList.length; i++){
                File readFile = new File(filePath);
                if(!readFile.isDirectory()){
                    listName.add(readFile.getName());
                }else{
                    readFile(filePath + "\\" + fileList[i]);
                }
            }
        }

        for(String str : listName){
            System.out.println(str);
        }
    }

    public static void main(String[] args){
        readFile("E:\\test");
    }
}

二、Java 多线程

1、创建线程有哪些方式?有什么区别?

  • 继承Thread类:Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例,没有返回值。启动线程的唯一方法就是通过Thread类的start()方法。
  • 实现Runnable接口:没有返回值。
  • Callable和Future:Callable接口提供了一个call()方法作为线程执行体。call()方法有返回值,可以声明抛出异常。Java5提供了Future接口来代表Callable接口里call()方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务
  • 使用线程池:推荐使用ThreadPoolExecutor。避免使用Executors创建线程池,Executors创建线程池没有传入阻塞队列的长度,阻塞队列就是一个无边界队列,对于一个无边界队列来说是可以向其中无限添加任务的,这种情况下可能由于任务数太多而导致内存溢出

2、线程有哪几种状态?

新建、就绪、运行、阻塞、终止

3、线程池参数

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

 由ThreadPoolExecutor构造函数可以看出,线程池参数主要有以下几个:

  • corePoolSize:线程池核心线程数,当池中正在运行的线程数(包括空闲线程)小于corePoolSize时,新建线程执行任务。当池中正在运行的线程数大于等于corePoolSize时,新插入的任务进入workQueue排队(如果workQueue长度允许),等待空闲线程来执行。
  • maximumPoolSize:线程池最大数。当队列里的任务数达到上限,并且池中正在运行的线程数小于maximumPoolSize,对于新加入的任务,新建线程。当队列里的任务数达到上限,并且池中正在运行的线程数等于maximumPoolSize,对于新加入的任务,执行拒绝策略(线程池默认的拒绝策略是抛异常)
  • keepAliveTime:空闲线程存活时间
  • TimeUnit: 时间单位
  • workQueue: 线程池所使用的缓冲队列
  • threadFactory:线程池创建线程使用的工厂
  • handler:线程池对拒绝任务的处理策略

4、start()和run()有什么不同?

1、start方法用来启动相应的线程;

2、run方法只是thread的一个普通方法,在主线程里执行;

3、需要并行处理的代码放在run方法中,start方法启动线程后自动调用run方法;

4、run方法必去是public的访问权限,返回类型为void。

5、sleep和wait有什么区别?

  • sleep:sleep是Thread的静态方法。sleep不会释放锁,它也不需要占用锁。可以被interrupted方法中断。
  • wait:wait是Object的方法,任何对象实例都能调用。wait会释放锁,但调用它的前提是当前线程占有锁(即代码要在synchronized中)。可以被interrupted方法中断。

6、为什么要是用线程池?

      线程池为线程生命周期开销和资源不足为提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到了多个任务上。其好处是消除了线程创建锁都带来的延迟。通过适当的调整线程池中线程数的数目,可以防止资源不足。如果任务创建过于频繁,而且任务的平均处理时间过短,那么为每个任务创建线程将会导致性能问题。

7、Runnable接口和Callable接口的区别?

  1. Callable有返回值,需要调用FutureTask.get()方法实现,此方法会阻塞线程直到获取结果,当不调用此方法时,主线程 不会阻塞。Runnable没有返回值。
  2. Runnable没有容错机制;Callable有容错机制,实现类中run()方法允许将异常向上抛出,也可以直接在内部处理
  3. Runnable可以通过Thread来启动,也可以通过线程池的execute、submit来处理;Callable线程只能通过线程池的submit来处理

8、CyclicBarrier和CountDownLatch的区别?

CyclicBarrierCountDownLatch
加计数方式减计数方式
计数达到指定值时释放所有等待线程计算为0时释放所有等待的线程
计数达到指定值时,计数置为0重新开始计数为0时,无法重置
调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响
可重复利用不可重复利用

9、volatile关键字的作用?

volatile让变量每次在使用的时候,都从主存中取。而不是从各个线程的“工作内存”。

volatile具有synchronized关键字的“可见性”,但是没有synchronized关键字的“并发正确性”,也就是说不保证线程执行的有序性。

也就是说,volatile变量对于每次使用,线程都能得到当前volatile变量的最新值。但是volatile变量并不保证并发的正确性。

10、synchronized和ReentrantLock的区别?

ReentrantLock是Lock的实现类,是一个互斥的同步器,在多线程高竞争条件下,ReentrantLock比synchronized有更加优异的性能表现。

1 用法比较

Lock使用起来比较灵活,但是必须有释放锁的配合动作; Lock必须手动获取与释放锁,而synchronized不需要手动释放和开启锁; Lock只适用于代码块锁,而synchronized可用于修饰方法、代码块等。 此外,reentrantlock有trylock 和lockinterruptly ,所以对锁的操作更灵活。从功能 的角度看,reentrantlock支持公平锁和非公平锁 而synchronized 仅支持非公平锁。

2 特性比较

ReentrantLock的优势体现在:

具备尝试非阻塞地获取锁的特性:当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁; 能被中断地获取锁的特性:与synchronized不同,获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放; 超时获取锁的特性:在指定的时间范围内获取锁;如果截止时间到了仍然无法获取锁,则返回。

3 注意事项

在使用ReentrantLock类的时,一定要注意三点: 在finally中释放锁,目的是保证在获取锁之后,最终能够被释放。 不要将获取锁的过程写在try块内,因为如果在获取锁时发生了异常,异常抛出的同时,也会导致锁无故被释放。 ReentrantLock提供了一个newCondition的方法,以便用户在同一锁的情况下可以根据不同 的情况执行等待或唤醒的动作。

  

三、集合       

1. String类为什么是final的。

  设计成final就是不希望被改变的意思。可以从安全和效率上来理解这个问题。

  安全:final修饰的类是不能被继承和修改的。String被设计成可以共享的,所以String一旦被创建就不能被修改。

  效率 : 设计成final,JVM可以直接定位到String类的相关方法上,提高了执行效率。

总言之,String类设计成final,就是要确保java.lang.String引用的对象一定是 java.lang.String的对象,而不是引用它的子孙类,这样才能保证它的效率和安全。

2. HashMap的源码,实现原理,底层结构。

  底层结构:数组+链表,JAVA 8 以后添加了红黑树

  参考HashMap源码,理解put()、resize()等方法的实现。

3. 说说你知道的几个Java集合类:list、set、queue、map及其实现类

Java集合类主要由两个接口派生而出:Collection和Map,Collection和Map是Java集合框架的根接口。

父类为Collection:Set、List、Queue 

      Set:Set集合与Colection基本相同,Set集合不允许包含相同的元素。

      List:List集合代表一个元素有序、内容可重复的集合,集合中每个元素都有其对应的顺序索引。

     Queue:模拟了队列这种数据结构,即先进先出。接口中定义的方法有访问元素poll()、新插入元素offer()等。

父类为Map:HashMap、HashTable

    HashMap:允许null key存在,不支持线程同步、如果需要同步,可使用synchronizedMap方法或使用CurrentHashMap。                               HashMap是无序的。

  HashTable:在处理元素时使用Synchronize,所以它是线程安全的。在单线程环境下比HashMap慢。

4. 描述一下ArrayList、LinkedList各自实现和区别

   ArrayList实现了基于动态数组的数据结构,LinkedList实现了基于链表的数据结构;

  对于随机访问set和get,ArrayList效率高于LinkedList,因为LinkedList需要移动指针。

  对于删除和修改,LinkedList效率高于ArrayList,因为ArrayList需要移动数据。

5. Java中的队列都有哪些,有什么区别。

  阻塞队列、普通队列、非阻塞队列

  阻塞队列和普通队列的区别在于,当队列为空时,从队列中获取元素会造成阻塞;或当队列已满,向队列中插入元素时,会造成阻塞。

6. Java数组和链表两种结构的操作效率,在哪些情况下(从开头开始,从结尾开始,从中间开始),哪些操作(插入,查找,删除)的效率高

数组静态分配内存,链表动态分配内存; 
数组在内存中连续,链表不连续; 
数组元素在栈区,链表元素在堆区; 
数组利用下标定位,时间复杂度为O(1),链表定位元素时间复杂度O(n); 
数组插入或删除元素的时间复杂度O(n),链表的时间复杂度O(1)。

7. string、stringbuilder、stringbuffer区别

StringStringBufferStringBuilder
String的值是不可变的,这就导致每次对String的操作都会生成新的String对象,不仅效率低下,而且浪费大量优先的内存空间StringBuffer是可变类,和线程安全的字符串操作类,任何对它指向的字符串的操作都不会产生新的对象。每个StringBuffer对象都有一定的缓冲区容量,当字符串大小没有超过容量时,不会分配新的容量,当字符串大小超过容量时,会自动增加容量可变类,速度更快
不可变可变可变
 线程安全线程不安全
 多线程操作字符串单线程操作字符串

8. HashtableHashMap的区别

  1. HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。
  2. HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
  3. 另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。
  4. 由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。
  5. HashMap不能保证随着时间的推移Map中的元素次序是不变的。

9. String a= “abc” String b = “abc” String c = new String(“abc”) String d = “ab” + “c” .他们之间用 == 比较的结果

a、b、d三个用==比较结果都为true,这个涉及到字符串常量池的问题了。c是new出来的,在堆内存新开辟了空间,地址和a b c不会相同。

10. String 类的常用方法

1、length:获取字符串长度

2、charAt(int index):获取字符串某一位置的字符

3、substring(int beginIndex, intendIndex):截取字符串

4、compareTo(String str):字符串比较

5、indexOf(String str):查找子串在字符串中的位置

6、toLowerCase()、toUpperCase():字符串大小写转换

7、trim()去除字符串首尾空格

8、split(String str):将字符串分割成字符串数组

9、valueOf(xxx xx):基本类型转为字符串
19. Hashtable,HashMap,ConcurrentHashMap 底层实现原理与线程安全问题(建议熟悉 jdk 源码,才能从容应答)

11. Hash冲突怎么办?哪些解决散列冲突的方法?

  1. 开放定址法:开放定址法就是一旦发生了冲突,就去寻找下一个空的散列地址,只要散列表足够大,空的散列地址总能找到,并将记录存入 。当冲突发生时,使用某种探测技术在散列表中形成一个探测序列。沿此序列逐个单元地查找,直到找到给定的关键字,或者 碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。查找时探测到开放的地址则表明表 中无待查的关键字,即查找失败。 
  2. 再哈希法:再哈希法又叫双哈希法,有多个不同的Hash函数,当发生冲突时,使用第二个,第三个,….,等哈希函数
    计算地址,直到无冲突。虽然不易发生聚集,但是增加了计算时间。
  3. 链接地址法:每个哈希表节点都有一个next指针,多个哈希表节点可以用next指针构成一个单向链表,被分配到同一个索引上的多个节点可以用这个单向链表连接起来,如: 
    键值对k2, v2与键值对k1, v1通过计算后的索引值都为2,这时及产生冲突,但是可以通道next指针将k2, k1所在的节点连接起来,这样就解决了哈希的冲突问题 
  4. 建立公共溢出区:将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出

12. 抽象类和接口的区别

      抽象类和接口的区别

参数抽象类接口
默认的方法实现有默认的方法实现接口是完全抽象的。不存在方法的实现
实现子类使用extends关键字来继承抽象类。如果子类不是抽象类,它需要提供所有抽象类声明的方法的实现子类使用implements来实现接口。它需要提供接口中所有声明方法的实现。
构造器抽象类可以有构造器接口不能有构造器
与正常Java类的区别除了不能实例化抽象类外,抽象类和普通Java类没有区别接口是完全不同的类型
访问修饰符抽象类可以有public、protected、default这些修饰符接口默认方法是public.不能使用其他修饰符
main方法抽象方法有main方法并且我们可以运行它接口没有main方法,因此无法运行它。(java8后接口有default和Static方法,所以可以运行main方法)
多继承抽象方法可以继承一个类和实现多个接口接口只可以继承一个或多个其他接口
速度抽象类比接口速度快接口稍微慢一点,它需要时间去寻找在类中的实现方法
添加新方法往抽象类添加新方法,可以给它提供默认的实现方法,无需改变现在的代码接口中添加新方法,必须在实现类添加他的实现

      什么会后使用抽象类和接口:

      a) 如果你拥有一些方法并且想让他们有一些默认的实现,则使用抽象类

      b) 如果想实现多继承,则必须使用接口。由于Java不支持多继承,子类不能继承多个类,但可以实现多个接口。

      c) 如果基本功能不断在变,则使用抽象类。如果基本功能不断改变且使用接口,则必须改变所有实现了接口的类

四、JVM   

1、JVM jAVA运行时数据区?

  • 堆:线程共享,存放对象实例和数组
  • 虚拟机栈:线程私有,存放Java方法、局部变量表、操作数栈、方法出口
  • 方法区:线程共享、存放静态变量、类信息,常量,即时编译器编译后的代码
  • 本地方法栈:线程私有、
  • 程序计数器:线程私有,存放虚拟机字节指令码地址和undefined


运行时常量池,存放编译器生成的各种字面量和符号引用,编译期和运行期都可以将常量放入常量池中,内存有限,无法申请时抛出OutOfMemoryError.

2、访问对象的两种方式?

  • 通过对象的reference来访问
  • 1、通过句柄访问:reference存储句柄地址,reference中存储的是稳定的句柄地址,在GC时,只是改变对象的存储地址
  • 2、通过指针访问:refereence存储对象地址
  • 频繁GC下适合通过句柄方式访问对象。频繁访问对象适合通过指针访问对象

3、如何判断对象是否死亡?

  • 引用计数法:给对象添加一个引用计数器,难以解决循环引用问题
  • 可达性分析法:通过一系列的'GC Roots'的对象作为起始点,从这些节点出发走过的路径被称为引用链。当一个GC Roots没有任何引用链到达的时候说明对象不可用。

4、简单的介绍一下强引用、软引用、弱引用、虚引用(虚引用与软引用和弱引用的区别、使用软引用能带来的好处)。

  • 强引用:new创建的。只要有强引用在,就不会被回收。
  • 软引用:SoftReference类实现软引用。在系统发生内存溢出异常之前,将会把这些对象列进回收范围之中进行二次回收。
  • 弱引用:WeakReference类实现弱引用。对象只能生存到下一次垃圾回收之前。在垃圾收集器工作时,无论内存是否足够,都会回收掉只呗=被弱引用关联的对象。
  • 虚引用:PhanTomReference类实现虚引用。无法通过虚引用获取一个对象实例,为一个对象设置虚引用关联的唯一目的,就是在这个对象被回收的时候,收到一个系统通知。

5、垃圾收集有哪些算法,各自的特点?

  • 标记-清除算法:最基础的收集算法,算法分为标记、清除两个阶段。先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象。该算法有两点不足:一是效率问题,标记和清除两个过程的效率都不高。二是标记清除后会产生大量不连续的内存碎片。
  • 复制算法:该算法将内存按容量分为大小相等的两块,每次只使用其中的一块。当这一块内存用完了,就把还存活着的对象复制到另一块,然后再把使用过的内存一次清理掉。复制算法实现简单,运行高效,但是这种算法将内存大小缩减到了原来的一半。在对象存活率较高的时候,该算法效率会降低。
  • 标记-整理算法:该算法标记过程与标记-清除算法一样,但是后续步骤不同,而是让所有存活的对象都向一端移动,然后直接清理掉端边界以外的内存。
  • 分代收集算法:当前商业虚拟机的垃圾收集都采用分代收集算法,该算法知识根据对象生存周期的不同将内存划分为几个不同的块,一般是把Java对划分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的算法。在新生代中,每次垃圾搜集都会有大量的对象死去,只有少量存活,那就使用复制算法。而老年代中对象存货率较高,没有额外的空间对她进行分配担保,就必须使用标记-清除或标记-整理算法。

6、GC参数?

堆设置  
-Xms :初始堆大小  
-Xmx :最大堆大小  
-XX:NewSize=n :设置年轻代大小  
-XX:NewRatio=n: 设置年轻代和年老代的比值。如:为3,表示年轻代与年老代比值为1:3,年轻代占整个年轻代年老代和的1/4  
-XX:SurvivorRatio=n :年轻代中Eden区与两个Survivor区的比值。注意Survivor区有两个。如:3,表示Eden:Survivor=3:2,一个Survivor区占整个年轻代的1/5 
-XX:MaxPermSize=n :设置持久代大小  
收集器设置  
-XX:+UseSerialGC :设置串行收集器  
-XX:+UseParallelGC :设置并行收集器  
-XX:+UseParalledlOldGC :设置并行年老代收集器  
-XX:+UseConcMarkSweepGC :设置并发收集器  
垃圾回收统计信息  
-XX:+PrintHeapAtGC GC的heap详情 
-XX:+PrintGCDetails  GC详情 
-XX:+PrintGCTimeStamps  打印GC时间信息 
-XX:+PrintTenuringDistribution    打印年龄信息等

-XX:+HandlePromotionFailure   老年代分配担保(true  or false)

并行收集器设置  
-XX:ParallelGCThreads=n :设置并行收集器收集时使用的CPU数。并行收集线程数。  
-XX:MaxGCPauseMillis=n :设置并行收集最大暂停时间  
-XX:GCTimeRatio=n :设置垃圾回收时间占程序运行时间的百分比。公式为1/(1+n)  
并发收集器设置  
-XX:+CMSIncrementalMode :设置为增量模式。适用于单CPU情况。  
-XX:ParallelGCThreads=n :设置并发收集器年轻代收集方式为并行收集时,使用的CPU数。并行收集线程数。

7、常见的垃圾回收器有那些?

  • Serial收集器:单线程收集器,是虚拟机运行在Client模式下默认的新生代收集器,在进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。
  • ParNew收集器:ParNew收集器就是Serial的多线程版。ParNew收集器除了多线程收集外,其他与Serial收集器相比,并没有太多创新之处。但它却是许多运行在Server模式下的虚拟机中首选的新生代收集器,其中有一个与性能无关但很重要的原因是,除了Serial收集器外,目前只有它能与CMS收集器配合工作。
  • Parallel Scavenge收集器:Parallel Scavenge收集器是一个新生代收集器。它也是使用复制算法的收集器,又是并行的多线程收集器。但是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能的缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。所谓吞吐量就是CPU用于运行用户代码的时间与CPU总消耗的时间的比值。
  • Serial Old收集器:Serial Old是Serial收集器的老年代版本,它同样是一个单线程的收集器,使用标记-整理算法。这个收集器的主要意义也是在于给运行在Client模式下的虚拟机使用。如果在Server模式下,它还有主要两大用途:一是在JDK1.5以及之前的版本中与Parallel Scavenge收集器搭配使用;二是作为CMS收集器的后备预案,在并发收集发生Concurrent Mode Failure时使用。
  • Parallel Old收集器:是Parallel Scavenge收集器的老年代版本,使用多线程和标记-整理算法。该收集器在JDK 1.6才开始提供。在注重吞吐量以及CPU资源敏感的场合,都可以优先考虑Parallel Scavenge 加Parallel Old 收集器。
  • CMS收集器:该收集器是一种以获取最短回收停顿时间为目标的收集器。CMS(Concurrent Mark Sweep)收集器是基于标记-清除算法实现的。整个过程分为以下4个步骤:
    • 初始标记:该过程只是标记一下GC Roots能直接关联到的对象,速度很快。
    • 并发标记:该过程就是执行GC Roots Tracing的过程。
    • 重新标记:该过程是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分对象的标记记录,这个阶段的停顿时间应该比初始标记稍微长一点,但远比并发标记的时间要短。
    • 并发清除:该过程就是执行标记-清除算法。

        整个过程中,耗时最长的并发标记和并发清除过程收集器线程都可以和用户线程一起工作,所以,从整体上来说,CMS收集器的内存回收过程是与用户线程一起并发执行的。

       CMS收集器有3个明显的缺点:

  1.  CMS收集器对CPU资源非常敏感
  2.  CMS收集器无法处理浮动垃圾
  3.  CMS收集器采用的是标记-清除算法,在垃圾回收结束后会产生大量内存碎片
  • G1收集器:G1是一款面向服务端的垃圾收集器,HotSpot 团队希望G1未来能替换掉 CMS。在G1之前,其他垃圾收集器进行的收集范围都是整个新生代或整个老年代,而G1不再是这样。使用G1收集器时,Java堆的内存布局就与其他垃圾收集器有很大的区别,G1将整个Java堆划分为多个大小独立相等的区域(Rgion),虽然保留了新生代和老年代的概念,但新生代和老年代不再是物理隔离的了,他们都是一部分Region集合。G1收集器与其他收集器相比,G收集器具有如下特点:
  1. 并行与并发:G1能充分利用多CPU、多核环境下的硬件优势,使用多个CPU来缩短Stop-The-World停顿时间,部分其他收集器原本需要停顿Java线程执行GC的动作,G1收集器仍然可以通过并发的方式让Java程序继续执行。
  2. 分代收集:与其他收集器一样,分代收集概念在G1中仍然保溜。虽然G1不需要其他收集器配合就能独立管理整个GC堆,但它仍能够采用不同的方式去处理新创建的对象和已经存活过一段时间、熬过多次GC的旧对象以获取更好的收集效果。
  3. 空间整合:与CMS的标记-清除算法不同,G1从整体上是基于标记-整理算法实现的收集器,从局部(两个Region之间)上看是基于复制算法实现的,但无论如何,这两种算法都意味着G1在垃圾回收过程中,不会产生内存空间碎片。
  4. 可预测的停顿:降低停顿是G1和CMS共同的关注点,但G1除了降低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,这几乎已经是实时Java(RTSJ)的垃圾收集器的特征了。G1收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java堆中进行全区域的垃圾收集。G1跟踪各个Region里的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。

    在G1收集器中,Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,虚拟机都是使用Remembered Set来避免全堆扫描的。G1中每个Region都有一个与之对应的Remembered Set,虚拟机发现程序在对Reference类型的数据进行写操作时,会产生一个Write Barrier 暂时中断写操作,检查Reference引用的对象是否处于不同的Region之中,如果是,便通过CardTable把相关的引用信息记录到被引用对象所属的Remembered Set中,当进行内存回收时,在GC根节点的枚举范围中加入Remembered Set即可保证不对全堆进行扫描也不会有遗漏。

    不计算维护Remembered Set的操作,G1收集器运作步骤大致如下

  1.  初始标记:标记GC Roots能直接关联到的对象,修改TAMS(Next Top at Mark Start)的值,让下一阶段用户程序并发运行时,能在正确可用的Region中创建对象。
  2.  并发标记:从GC Root开始对堆中的对象进行可达性分析。
  3.  最终标记:修正在并发标记期间因用户线程继续运作而导致标记产生变动的一部分标记记录,虚拟机将这这段时间对象变化记录在线程Remembered Set Logs里面,最终标记阶段需要把Remembered Set Logs的数据整合到Remembered Set中,这个阶段需要暂停线程,但是可以并发执行。
  4.  筛选回收:对各个Region的回收价值进行排序,根据用户期望的停顿时间制定回收计划。

8、Minor Gc和Full GC 有什么不同呢?

  • Minor GC:新生代GC,即发生在新生代的垃圾收集动作,因为Java对象大多具备朝生夕死的特征,所以Minor GC非常频繁,一般回收速度也比较快。
  • Full GC:老年代GC(Major GC / Full GC),出现Full GC,通常会至少伴随一次Minor GC(Paralell Scavenge除外),Full GC的速度一般会比Minor GC 慢10倍以上。

9、简单说说类加载过程,里面执行了哪些操作?

类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)7个阶段。其中,验证、准备、解析3个部分统称为连接(Linking)

  1. 加载:程序运行之前jvm会把编译完成的.class二进制文件加载到内存,供程序使用,用到的就是类加载器classLoader ,这里也可以看出java程序的运行并不是直接依   靠底层的操作系统,而是基于jvm虚拟机。如果没有类加载器,java文件就只是磁盘中的一个普通文件
  2. 连接:1)验证:确保类加载的正确性。一般情况由javac编译的class文件是不会有问题的,但是可能有人的class文件是自己通过其他方式编译出来的,这就很有可能不符合jvm的编 译规则,这一步就是要过滤掉这部分不合法文件。2)准备:为类的静态变量分配内存,将其初始化为默认值 。我们都知道静态变量是可以不用我们手动赋值的,它自然会有一个初始值 比如int 类型的初始值就是0 ;boolean类型初始值为false,引用类型的初始值为null 。 这里注意,只是为静态变量分配内存,此时是没有对象实例的 3)解析:把类中的符号引用转化为直接引用。解释一下符号引用和直接引用。比如在方法A中使用方法B,A(){B();},这里的B()就是符号引用,初学java时我们都是知道这是java的引用,以为B指向B方法的内存地址,但是这是不完整的,这里的B只是一个符号引用,它对于方法的调用没有太多的实际意义,可以这么认为,他就是给程序员看的一个标志,让程序员知道,这个方法可以这么调用,但是B方法实际调用时是通过一个指针指向B方法的内存地址,这个指针才是真正负责方法调用,他就是直接引用
  3. 初始化:为类的静态变量赋予正确的初始值,上述的准备阶段为静态变量赋予的是虚拟机默认的初始值,此处赋予的才是程序编写者为变量分配的真正的初始值

10、什么是双亲委派模型?

JVM在加载类时默认采用的是双亲委派模型。当某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,直至启动类加载器。如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,自己才尝试去加载。

这种层次关系称为类加载器的双亲委派模型。我们把每一层上面的类加载器叫做当前层类加载器的父加载器,它们之间的父子关系不是通过继承关系实现,而是使用组合关系复用父加载器中的代码。

双亲委派模型并不是一个强制性的约束模型,而是 Java 设计者们推荐给开发者的一种类的加载器实现方式。

使用双亲委派模型来组织类加载器之间的关系,很明显的好处就是 Java 类随着它的类加载器一起具备了一种带有优先级的层次关系,这对于保证Java程序的稳定运作很重要。

11、JVM调优的常见命令行工具有哪些?

  1. jps命令用于查询正在运行的JVM进程,
  2. jstat可以实时显示本地或远程JVM进程中类装载、内存、垃圾收集、JIT编译等数据
  3. jinfo用于查询当前运行这的JVM属性和参数的值。
  4. jmap用于显示当前Java堆和永久代的详细信息
  5. jhat用于分析使用jmap生成的dump文件,是JDK自带的工具
  6. jstack用于生成当前JVM的所有线程快照,线程快照是虚拟机每一条线程正在执行的方法,目的是定位线程出现长时间停顿的原因。

12、简单介绍一下Class类文件结构(常量池主要存放的是那两大常量?Class文件的继承关系是如何确定的?字段表、方法表、属性表主要包含那些信息?)

  1. 魔数与class文件版本号:class文件头4个字节是魔数,魔数的唯一作用在于确定这个Class文件是否是Java虚拟机接受的Class文件。
  2. 常量池:常量池可以简单理解为class文件的资源从库,这种数据类型是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的项目之一。在常量池中主要存放字面量和符号引用。字面量比较接近Java语言层面的常量概念,比如文本字符串、声明为final的常量值等(百度百科的解释是字面量是用双引用号引住的一系列字符)。
  3. 访问标志:常量池之后的数据结构是访问标志(access_flags),这个标志主要用于识别一些类或者接口层次的访问信息,主要包括:这个Class是类还是接口、是否定义public、是否定义abstract类型;如果是类的话是否被声明为final等。
  4. 类索引、父类索引与接口索引:这个数据项主要用于确定这个类的继承关系。
  5. 字段表集合:字段表用于描述接口或者类中声明的变量。
  6. 方发表集合:JVM中堆方法表的描述与字段表是一致的,包括了:访问标志、名称索引、描述符索引、属性表集合。方法表的结构与字段表是一致的,区别在于访问标志的不同。在方法中不能用volatile和transient关键字修饰,所以这两个标志不能用在方法表中。在方法中添加了字段不能使用的访问标志,比如方法可以使用synchronized、native、strictfp、abstract关键字修饰,所以在方法表中就增加了相应的访问标志。
  7. 属性表集合:前面的Class文件、字段表和方法表都可以携带自己的属性信息,这个信息用属性表进行描述,用于描述某些场景专有的信息。在属性表中没有类似Class文件的数据项目类型和顺序的严格要求,只要新的属性不与现有的属性名重复,任何人都可以向属性表中写入自己定义的属性信息。

13. 反射中,Class.forName和classloader的区别

  两者都可对类进行加载。

  Class.forName()除了将类的.class文件加载到JVM外,还会对类进行解释,执行类中的static块。

  classloader只是将类的.class文件加载到JVM中,不会执行static块,只会在newInstance才会去执行static块。

  Class.forName(name,initialize,loader)带参函数也可以控制是否执行static块。并且只有调用了newInstance()采用调用构造函数,创建类的对象。

14 .异常的结构,运行时异常和非运行时异常,各举个例子

运行时异常:

都是RuntimeException类及其子类异常,如NullPointerException(空指针异常)、IndexOutOfBoundsException(下标越界异常)等,这些异常是非检查异常,程序中可以选择捕获处理,也可以不处理。这些异常一般是由程序逻辑错误引起的,程序应该从逻辑角度尽可能避免这类异常的发生。

运行时异常的特点是Java编译器不会检查它,也就是说,当程序中可能出现这类异常,即使没有用try-catch语句捕获它,也没有用throws子句声明抛出它,也会编译通过。

非运行时异常:

是RuntimeException以外的异常,类型上都属于Exception类及其子类。从程序语法角度讲是必须进行处理的异常,如果不处理,程序就不能编译通过。如IOException、SQLException等以及用户自定义的Exception异常,一般情况下不要自定义检查异常。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值