Java基础知识

一、JAVA面向对象的三大特征

封装、继承、多态

1.封装:

隐藏实现细节,提高代码的复用性,提高了安全性。

2.继承:

多个事物直接有共同的属性和行为放到父类中,特有的放在子类中,子类可以继承父类的属性和行为。通过extends关键字。

注:JAVA只支持单继承,不支持多继承。一个类只能有一个父类,不可以有多个父类。但可以多层继承。子类访问父类的成员变量通过super关键字。

  • 优点:可以继承父类的特征
  • 缺点:如果多层父类有相同名字的实例变量或者相同的方法,调用可能会产生歧义,不知道用的哪个父类的。

3.多态:

子类的对象放在父类的引用中,如 Animal a=new Cat(),子类的对象当父类对象来用。

优点:提高了程序的扩展性和复用性

缺点:通过父类引用操作子类对象时,只能用父类中有的方法,不能操作子类特有的方法。

注:多态的前提:1、必须有关系:继承、实现  2、通常都有重写的操作

3.1 向上转型:

当父类的引用指向子类的对象时,就发生了向上转型,即把子类类型转成了父类类型。

优点:隐藏了子类类型,提高了代码的扩展性

弊端:只能使用父类的内容,无法使用子类特有的功能

3.2 向下转型:

当要使用子类特有的功能时,就需要使用向下转型

优点:可以使用子类的 特有功能

弊端:需要面对具体的子类对象时,向下转型时容易发生ClassCastException类型转换异常。所以转型前必须要做判断。

3.3 对象的强制转换:

格式: 引用 instanceof 类型 判断当前对象是否是引用类

用法: Animal a1=new Dog();

if( !Cat instanceof a1){ //判断当前对象是不是Cat类型

}

Cat d=(Cat)a1;

二、java异常

1.异常

Java主要分为Error错误Exception异常两类。Error(错误)是程序无法处理的错误,表示运行应用程序中较严重问题。Exception(异常)是程序本身可以处理的异常。Exception(异常)又包含运行时异常非运行时异常(编译异常)

  • Error:OOM等
  • 运行时异常:ArrayIndexOutOfBoundException数组越界、NullPointerException空指针等。
  • 非运行时异常:如IOException IO异常。

2.可查的异常和不可查异常

Java的异常(Throwable)分为可查的异常(checked exceptions)不可查的异常(unchecked exceptions)

 

  • 可查异常必须要去处理,否则编译不通过,如IOException和ClassNotFoundException等。

处理异常方法:要么用try-catch语句捕获它,要么用throws子句声明抛出它。

  •  不可查异常(编译器不要求强制处置的异常)包括运行时异常和错误,不用捕获对应异常,应该找出错误程序进行修改。

三、基础知识点汇总

1.java中extends和implements

extends是继承类,implements是实现接口。 类只能继承一个,接口可以实现多个。
extends继承父类的时候可以重写父类的方法,implements实现接口,必须实现接口的所有方法。

abstract class A {
    abstract m(): void;
}


class B extends A{
}

class C implements A {
    m(): void { } //必须要实现定义在A中的所有方法
}

 

2.基本类型和包装类型

2.1 区别

  • 包装类型对应变量default值是 null ,而基本类型有默认值且不是 null
  • 包装类型可用于泛型,而基本类型不可以。
  • 基本数据类型的局部变量存放在 Java 虚拟机栈中的局部变量表中,基本数据类型的成员变量(未被 static 修饰 )存放在 Java 虚拟机的堆中。包装类型属于对象类型,几乎所有对象实例都存在于堆中。
  • 相比于对象类型, 基本数据类型占用的空间非常小。

2.2 装箱与拆箱

  • 装箱:将基本类型用它们对应的引用类型包装起来;
  • 拆箱:将包装类型转换为基本数据类型;

举例:

Integer i = 10;  //装箱
int n = i;   //拆箱

3.接口和抽象类

3.1 共同点 :

  • 都不能被实例化。
  • 都可以包含抽象方法。
  • 都可以有默认实现的方法(Java 8 可以用 default 关键字在接口中定义默认方法)。

3.2 区别 :

  • 接口主要用于对类的行为进行约束,你实现了某个接口就具有了对应的行为。抽象类主要用于代码复用,强调的是所属关系。
  • 一个类只能继承一个类,但是可以实现多个接口。
  • 接口中的成员变量只能是 public static final 类型的,不能被修改且必须有初始值,而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。

4.深拷贝、浅拷贝、引用拷贝

浅拷贝、深拷贝、引用拷贝示意图

  • 引用拷贝:两个不同的引用指向同一个对象
  • 浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),对象内部的属性是引用类型的话,指向的是同一地址。
  • 深拷贝 :深拷贝会完全复制整个对象。

5.== 和 equals()

  • 对于基本数据类型来说,== 比较的是值。
  • 对于引用数据类型来说,== 比较的是对象的内存地址。

 ==:比较的是值是否相等(基本类型比较值,引用类型(对象)比较的是内存地址)

equals():只能用来判断对象,不能用于基本数据类型。不重写等同于==,重写可以自定义。

6.String、StringBuffer、StringBuilder 的区别

  • String :里面的对象是不可变的,线程安全。适用于操作少量数据
  • StringBuffer :线程安全。适用于多线程操作大量数据。
  • StringBuilder:非线程安全的。适用于单线程操作大量数据。

四、反射

赋予了我们在运行时分析类以及执行类中方法的能力。通过反射可以获取任意一个类的所有属性和方法。

优点:代码更加灵活、为各种框架提供开箱即用的功能提供了便利。

缺点:不安全(比如无视泛型参数的安全检查),性能会变差。

应用场景:业务场景使用较少,框架使用较多。像 Spring/Spring Boot、MyBatis这些框架, Spring 里面的注解,都用了反射机制。里面也用了动态代理,动态代理的实现也依赖反射。

五、集合

1.java集合框架

两大接口派生而来:一个是 Collection接口,主要用于存放单一元素;另一个是 Map 接口,主要用于存放键值对。

2. List, Set, Queue, Map 四者的区别

List: 有序、可重复。
Set: 无序、不可重复。
Queue: 排队,有序、可重复。一般用于排队功能的叫号机。
Map: 使用键值对(key-value)存储。

List

  • ArrayList: Object[] 数组
  • VectorObject[] 数组
  • LinkedList: 双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)

Set

  • HashSet(无序,唯一): 基于 HashMap 实现,底层采用 HashMap 来保存元素
  • LinkedHashSet: 通过 LinkedHashMap 实现。
  • TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树)

Queue

  • PriorityQueueObject[] 数组来实现二叉堆
  • ArrayQueueObject[] 数组 + 双指针

Map

  • HashMap: 数组+链表组成,链表长度大于阈值(默认为 8)会出现红黑树转换。
  • LinkedHashMap: 继承自 HashMap,增加了一条双向链表,可以实现顺序访问。
  • Hashtable: 数组+链表组成的,数组是 Hashtable 的主体,链表则是主要为了解决哈希冲突而存在的
  • TreeMap: 红黑树(自平衡的排序二叉树)

注:上图是LinkedHashMap的存储结构,在hashMap的基础上多了一个双向链表,可以实现顺序访问。

3.区别

3.1 ArrayList 和 Vector :

  • ArrayList 是 List 的主要实现类,底层使用 Object[ ]存储,线程不安全 ;
  • Vector 是 List 的古老实现类,底层使用Object[ ] 存储,线程安全的。

3.2 ArrayList 与 LinkedList:

  • 数据结构:一个数组一个链表数组。LinkedList 是 双向链表 (JDK1.6 之前为循环链表,JDK1.7 取消了循环)
  • 访问速度:数组可以快速随机访问,链表不行
  • 更新:链表对中间数据插入较友好好。
  • 线程安全:都不是线程安全的;
注:很少使用LinkedList

3.3 HashSet、LinkedHashSet 和 TreeSet

  • 都是 Set 接口的实现类,元素唯一,并且都不是线程安全的。
  • HashSet 用于不需要保证元素插入和取出顺序的场景,LinkedHashSet 用于 FIFO 的场景,TreeSet 用于元素需要自定义排序场景。

3.4 Queue 与 Deque 

  • Queue :单端队列,一端入,另一端出,一般遵循 先进先出(FIFO) 规则
  • Deque :双端队列,在队列的两端均可以插入或删除元素。

3.5HashMap 和 Hashtable

  • HashMap 非线程安全,Hashtable 是线程安全的(内部方法基本都经过synchronized 修饰)。
  • HashMap 要比 Hashtable 效率高一点。

注:基本不会用Hashtable,要线程安全就用ConcurrentHashMap

3.6ConcurrentHashMap在jdk1.7和1.8的区别

  • 数据结构:1.7是Segment 数组 + hashMap(HashEntry 数组 + 链表),1.8是 Node 数组 + 链表 / 红黑树。相当于原来是Segment和hashMap的数组是映射关系,现在合并了。
  • 锁1.7是Segment 分段锁,针对Segment(段/槽)加锁,1.8后锁的力度更细了,锁粒度更细,synchronized 只锁定当前链表或红黑二叉树的首节点。
  • 1.7的分段锁继承自 ReentrantLock。1.8 放弃了分段锁设计,采用 Node + CAS + synchronized 保证线程安全。
  • 并发度 :JDK 1.7 最大并发度是 Segment 的个数,默认是 16。JDK 1.8 最大并发度是 Node 数组的大小,并发度更大

3.7 HashMap实现

默认大小16,负载因子0.75,超过对应阈值会扩容。

put过程:先计算key的hash值,然后低16位和高16位做异或(充分利用每一位),然后与数组长度-1做与操作确定数组的位置,然后在链表最后插入对应的节点(如果已经有相同的key会替换)。如果超过8则会有红黑树的转换。

六、并发编程

1.进程和线程

进程:是系统运行程序的基本单位。一个进程在其执行的过程中可以产生多个线程。

线程:是一个比进程更小的执行单位。同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈。多线程之间切换负担要比进程小得多。

2.并发与并行的区别

:两个及两个以上的作业在同一 时间段 内执行。
:两个及两个以上的作业在同一 时刻 执行。

3.同步和异步的区别

同步 : 发出一个调用之后,在没有得到结果之前, 该调用就不可以返回,一直等待。
异步 :调用在发出之后,不用等待返回结果,该调用直接返回。

4.线程的生命周期和状态

5.sleep() 方法和 wait() 方法

  • 都可以暂停线程的执行。
  • sleep() 方法没有释放锁,而 wait() 方法释放了锁 。
  • sleep() 是 Thread 类的静态本地方法,wait() 则是 Object 类的本地方法(本质是每个对象都有对象锁,要释放当前线程占有的对象锁,所以操作的是对象而不是线程)。
  • wait() 需要别的线程调用同一个对象上的 notify()或者 notifyAll() 方法唤醒。sleep()方法执行完成后,线程会自动苏醒。

6.可以直接调用 Thread 类的 run 方法吗

可以但不会以多线程的方式执行,用当前线程执行的。

new 一个 Thread后,需要调用 start()方法,start()方法作用是启动一个线程处于就绪状态,当分配到时间片后就可以开始运行,这时候该线程会自动执行 run() 方法进行执行,这样就是多线程了。省略了start,那么就没有额外的线程,会用当前线程执行。

7.volatile

作用:保证变量的可见性,禁止指令重排。

7.1 如何保证变量可见性

volatile修饰的,不会有在工作内存有变量副本,线程直接从主内存取。

7.2 如何禁止指令重排

单例模式(线程安全) :

public class Singleton {

    private volatile static Singleton uniqueInstance;

    private Singleton() {
    }

    public  static Singleton getUniqueInstance() {
       //先判断对象是否已经实例过,没有实例化过才进入加锁代码
        if (uniqueInstance == null) {
            //类对象加锁
            synchronized (Singleton.class) {
                if (uniqueInstance == null) {
                    uniqueInstance = new Singleton();
                }
            }
        }
        return uniqueInstance;
    }
}

 uniqueInstance = new Singleton(); 这段代码其实是分为三步执行:

  1. 为 uniqueInstance 分配内存空间
  2. 初始化 uniqueInstance
  3. 将 uniqueInstance 指向分配的内存地址

JVM 的指令重排可能导致执行顺序不是1->2->3,而是 1->3->2。这样再多线程情况下,1->3的时候,另一个线程读到的就不是null了。

8.synchronized

8.1 作用域

修饰方法

修饰对象:根据修饰的对象不同可以分为全局锁(xxx.class)或者代码块.

8.2 底层原理

通过 JDK 自带的 javap 命令,可以查看java字节码.class信息,发现synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令;synchronized 修饰的方法并没有 monitorenter 指令和 monitorexit 指令,取得代之的确实是 ACC_SYNCHRONIZED 标识,该标识指明了该方法是一个同步方法。不过两者的本质都是对对象监视器 monitor 的获取。

8.3 锁升级

偏向锁、轻量级锁、重量级锁

用synchronized修饰的会有锁升级过程,如果只有一个线程获取锁,那么这个锁会偏向这个线程,叫做偏向锁;如果这时候有其他线程也来获取这个锁,那么这个锁会变成轻量级锁,获取不到锁的线程会自旋,然后重新获取;如果还是获取不到,则会变为重量级锁,获取不到的线程会阻塞(锁池状态)。

8.4 synchronized 和 ReentrantLock 的区别

  • 两者都是可重入锁
  • synchronized非公平锁,ReentrantLock默认也是非公平锁,不过可以有参数可以配置变成公平锁
  • synchronized 依赖于 JVM , ReentrantLock 依赖于 API(JDK 层面实现),需要lock() 和 unlock() 方法配合 try/finally 语句块来完成
  • ReentrantLock功能更多,如可以变成公平锁

9.ThreadLocal

为了实现每个线程都有自己的专属本地变量。通过创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本,从而避免了线程安全问题。

9.1 ThreadLocal 原理

// 源码:
public class Thread implements Runnable {
    //......
    //与此线程有关的ThreadLocal值。由ThreadLocal类维护
    ThreadLocal.ThreadLocalMap threadLocals = null;

    //与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    //......
}

ThreadLocal内部有一个ThreadLocalMap,每个线程在往ThreadLocal里放值的时候,都会往自己的ThreadLocalMap里存,读也是以ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。

10.线程池

10.1为什么使用线程池

  • 降低资源消耗:可以重复利用已创建的线程,降低线程创建和销毁造成的消耗。
  • 提高响应速度:当任务到达时,用已有线程可以直接执行。
  • 方便管理:统一创建,分配,调优和监控。

10.2 实现 Runnable 接口和 Callable 接口的区别

  • Runnable自 Java 1.0 以来一直存在,Callable在 Java 1.5 中引入
  • Runnable 接口不会返回结果或抛出检查异常,但是 Callable 接口 可以。所以如果任务不需要返回结果或抛出异常就使用 Runnable 接口 ,看起来更简洁。

10.3  execute()方法和 submit()方法的区别

  • execute()方法:没有返回值,无法判断任务是否被线程池执行成功;
  • submit()方法:线程池会返回一个 Future 类型的对象,可以判断是否执行成功。此外可以通过 Future 的 get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成。

10.4 四类线程池

CachedThreadPool: 可根据实际情况动态调整线程数量的线程池。

FixedThreadPool : 固定线程数量的线程池。

SingleThreadExecutor: 只有一个线程的线程池。

ScheduledThreadPool:调度类的线程池。

 

10.4.1 几个核心参数:

  • corePoolSize : 核心线程数。
  • maximumPoolSize : 最大线程数。
  • workQueue: 工作队列。
  • keepAliveTime:非核心线程(临时线程)存活时间;
  • handler :饱和策略。主要有报错、丢弃当前任务、丢弃最早未处理的任务、用提交任务的线程执行这四种。

10.4.2 大致流程

进来任务时,用核心线程做任务;如果任务超过核心线程数,则放入队列;如果任务还在增加,则创建临时线程执行,直到最大线程数;如果还在增加,则按照对应的饱和策略处理。

11.JUC 包中的4大Atomic原子类

基本类型:使用原子的方式更新基本类型

  • AtomicInteger:整型原子类
  • AtomicLong:长整型原子类
  • AtomicBoolean:布尔型原子类

数组类型:使用原子的方式更新数组里的某个元素

  • AtomicIntegerArray:整型数组原子类
  • AtomicLongArray:长整型数组原子类
  • AtomicReferenceArray:引用类型数组原子类

引用类型:

  • AtomicReference:引用类型原子类
  • AtomicStampedReference:原子更新带有版本号的引用类型。
  • AtomicMarkableReference :原子更新带有标记位的引用类型

对象的属性修改类型

  • AtomicIntegerFieldUpdater:原子更新整型字段的更新器
  • AtomicLongFieldUpdater:原子更新长整型字段的更新器
  • AtomicReferenceFieldUpdater:原子更新引用类型字段的更新器

12.AQS

AQS 的全称为(AbstractQueuedSynchronizer),是一个用来构建锁和同步器的框架。如: ReentrantLock,Semaphore信号量等都是用的AQS框架。

12.1 AQS 原理

通过ReentrantLock加锁说明:简单的说就是,AQS对象内部有一个volatile的int变量,初始state的值是0,一个线程要来加锁时,通过CAS把0改为1,代表加锁成功,释放锁就是再减回去,减到0就代表锁释放了。

另外AQS内部还维护着一个先进先出的等待队列,如果其他线程也来加锁,但是因为锁已经被其他线程占用而加锁失败,就会存放这些加锁失败的线程。

 

注:CLH(Craig,Landin and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS 是将每条请求共享资源的线程封装成一个 CLH 锁队列的一个结点(Node)来实现锁的分配。

12.2 AQS 组件总结

  • ReentrantLock:一次只允许一个线程访问某个资源,可重入。
  • Semaphore(信号量)-允许多个线程同时访问问: synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。
  • CountDownLatch (倒计时器): 可以让某一个线程等待直到倒计时结束,再开始执行。
  • CyclicBarrier(循环栅栏): 让一组线程到达栅栏时被阻塞,直到最后一个线程到达时,栅栏被推倒,所有线程开始执行。

七、反射

1.概念

可以在运行时分析类以及执行类中方法,通过反射可以获取和调用对应类的属性和方法。

 像Spring/Spring Boot、MyBatis 等框架中都大量使用了反射机制,动态代理也是基于反射。

2.优缺点

  • 优点 : 更加灵活、为各种框架实现对应功能提供了便利。
  • 缺点 :无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。性能也会稍差些。

3.获取 Class 对象的四种方式

3.1. 知道具体类的情况下可以使用:

Class alunbarClass = TargetObject.class;

3.2. 通过 Class.forName()传入类的全路径获取:

Class alunbarClass1 = Class.forName("cn.test.TargetObject");

3.3. 通过对象实例instance.getClass()获取:

TargetObject o = new TargetObject();
Class alunbarClass2 = o.getClass();

3.4. 通过类加载器xxxClassLoader.loadClass()传入类路径获取:

ClassLoader.getSystemClassLoader().loadClass("cn.test.TargetObject");

八、语法糖

1.概念:

计算机语言中的某种特殊语法,方便程序员使用。可以让程序更加简洁,有更高的可读性。

注:反编译,可以用java自带的javap,对.class文件进行反编译,可读性稍差。有一些网站可以把字节码反编译成对应的代码。所以查看语法糖依赖反编译。

2.常见语法糖

  • switch 支持 String 与枚举:java7开始支持switch string,正常switch对比的是基本数据类型的值或者ascii码。switch string,进行switch的实际是哈希值,然后通过使用equals方法比较进行安全检查。
  • 泛型:编译阶段通过类型擦除的方式进行解语法糖。
  • 自动装箱与拆箱:装箱通过调用包装器的 valueOf 方法实现,而拆箱通过调用包装器的 xxxValue 方法实现的。
  • 可变长参数:把可变长参数变成了一个数组,方法调用传的是数组。

注:在编译阶段,如果有一些代码根本不会走到(废代码),那么编译器会不进行编译。

 

九、jvm内存模型

十、垃圾回收

1.垃圾回收算法

1.1 标记-清除算法

该算法分为“标记”和“清除”阶段:首先标记出所有不需要回收的对象,在标记完成后统一回收掉所有没有被标记的对象。

  • 效率问题
  • 空间问题(标记清除后会产生大量不连续的碎片)

1.2 标记-复制算法

将内存分为大小相同的两块,每次使用其中的一块。当这一块的内存使用完后,就将还存活的对象复制到另一块去,然后再把使用的空间一次清理掉。

注:新生代采用标记-复制算法

1.3 标记-整理算法

先标记,然后让所有存活的对象向一端移动,然后直接清理掉端边界以外的内存。

注:老年代有使用标记-整理算法

1.4 分代收集算法

根据对象存活周期的不同将内存分为几块。分为新生代和老年代。新生代朝生夕死的多,所以用标记-复制算法。老年代存活几率比较高,没有额外的空间来对它进行担保,所以用标记-清除或者标记-整理算法。

2.分代回收细节

分为新生代、老年代和永久代(1.8后改成meta space),新生代里面有一个Eden区,两个Survivor区,每次回收时,复制Eden区和Survivor区存活对象到另外一个Survivor区,如此反复。如果超过15次还存活的对象,则进入老年代。

注:大的对象直接进入老年代,如大的数组、字符串等。

注2:对象一般在Eden区分配,当 Eden 区没有足够空间进行分配时,将会进行 Minor GC

  • 新生代收集(Minor GC / Young GC):只对新生代进行垃圾收集;
  • 老年代收集(Major GC / Old GC):对老年代进行垃圾收集。
  • 混合收集(Mixed GC):对整个新生代和部分老年代进行垃圾收集。
  • 整堆收集 (Full GC):收集整个 Java 堆和方法区。

3.死亡对象判断

3.1 引用计数法

对象有一个引用计数器,每次被引用,计数器加 1,引用失效,计数器就减 1;为 0 的对象可以被回收。

优点:效率高

缺点:无法解决循环引用的问题

3.2 可达性分析算法

通过根节点 “GC Roots”向下搜索,当一个对象到 GC Roots 没有任何引用链相连的话,就可以被回收。

可以作为 GC Roots 的对象:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈(Native 方法)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 所有被同步锁持有的对象

4. 引用类型

4.1.强引用(StrongReference):

我们平常使用的Object ob = new Object(),内存不足抛出 OOM,只有ob=null,才会回收。

4.2.软引用(SoftReference)

内存空间足够,就不会回收它,如果内存空间不足了,就会回收这些对象的内存。

4.3.弱引用(WeakReference)

垃圾收集器只要扫到,都会回收。

4.4.虚引用(PhantomReference)

被回收的时候会有一个通知,主要用来跟踪对象被垃圾回收的活动。

十一、高cpu占用优化

1.背景:

saas服务启动时,cpu占用太高。

2.过程:

  1. 首先确定CPU占比高的进程,线程ID。通过windows提供的工具Process Explorer可查到saas服务的进程ID(PID), 以及内部线程占用cpu情况。实时显示选中的进程内部线程ID,占用CPU情况。 操作saas,记录下占比CPU比较高的线程ID。
  2. 通过Jstack工具查看进程内部的线程执行情况: jstack -l PID > XXX.stack .
  3. 通过线程ID在jstack文件中找到消耗cpu比较大的线程为:C2 CompilerThread0 .在server模式下启动jar包后默认是开启tiered compiler对javac产生的字节码进行优化,这个线程消耗资源比较多。
  4.  用 -client 模式启动jar包后, 初步看内存占用从400+MB降到200+MB,CPU占比高的持续时间明显降低。

3.分析:

因为我们都知道JIT( just in time ), 也就是即时编译编译器。使用即时编译器技术,能够加速 Java 程序的执行速度。主要有Server 模式和 client 模式两种启动模式,主要的差别在于:-server 模式启动时,速度较慢,但是一旦运行起来后,性能将会有很大的提升。原因是:当虚拟机运行在-client 模式的时候,使用的是一个代号为 C1 的轻量级编译器,而-server 模式启动的虚拟机采用相对重量级代号为 C2 的编译器。C2 比 C1 编译器编译的相对彻底,服务起来之后,性能更高。

 

发表评论

邮箱地址不会被公开。 必填项已用*标注