编译机制
编译主要是把 .Java文件转换为 .class 文件。其中转换后的 .class 文件就包含了元数据,方法信息等一些信息。好比说元数据就包含了 Java 文件中声明的常量,也就是咱们所说的常量池。java
泛型实现原理
Java泛型实现原理:类型擦除
Java的泛型是伪泛型。在编译期间,全部的泛型信息都会被擦除掉。正确理解泛型概念的首要前提是理解类型擦出(type erasure)。
Java中的泛型基本上都是在编译器这个层次来实现的。
在生成的Java字节码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会在编译器在编译的时候去掉。这个过程就称为类型擦除。
如在代码中定义的List<object>和List<String>等类型,在编译后都会变成List。JVM看到的只是List,而由泛型附加的类型信息对JVM来讲是不可见的。Java编译器会在编译时尽量的发现可能出错的地方,可是仍然没法避免在运行时刻出现类型转换异常的状况。算法
编译时期和运行时期类型检查
Java中的许多对象(通常都是具备父子类关系的父类对象)在运行时都会出现两种类型:编译时类型和运行时类型,例如:Person person = new Student();
这行代码将会生成一个person变量,该变量的编译时类型是Person,运行时类型是Student。
Java的引用变量有两个类型,一个是编译时类型,一个是运行时类型,编译时类型由声明该变量时使用的类型决定,运行时类型由实际赋给该变量的对象决定。数据库
多态实现原理
基于继承实现的多态能够总结以下:对于引用子类的父类类型,在处理该引用时,它适用于继承该父类的全部子类,子类对象的不一样,对方法的实现也就不一样,执行相同动做产生的行为也就不一样。
继承是经过重写父类的同一方法的几个不一样子类来体现的,那么就能够是经过实现接口并覆盖接口中同一方法的几不一样的类体现的。
在接口的多态中,指向接口的引用必须是指定这实现了该接口的一个类的实例程序,在运行时,根据对象引用的实际类型来执行对应的方法。
当超类对象引用变量引用子类对象时,被引用对象的类型而不是引用变量的类型决定了调用谁的成员方法,可是这个被调用的方法必须是在超类中定义过的,也就是说被子类覆盖的方法,可是它仍然要根据继承链中方法调用的优先级来确认方法,该优先级为:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。bootstrap
A继承B,加载顺序api
若要加载类A,则先加载执行其父类B(Object)的静态变量以及静态语句块(执行前后顺序按排列的前后顺序)。数组
而后再加载执行类A的静态变量以及静态语句块。(而且一、2步骤只会执行1次)缓存
若需实例化类A,则先调用其父类B的构造函数,而且在调用其父类B的构造函数前,依次先调用父类B中的非静态变量及非静态语句块.最后再调用父类B中的构造函数初始化。多线程
而后再依次调用类A中的非静态变量及非静态语句块.最后调用A中的构造函数初始化。( 而且三、4步骤能够重复执行)分布式
而对于静态方法和非静态方法都是被动调用,即系统不会自动调用执行,因此用户没有调用时都不执行,主要区别在于静态方法能够直接用类名直接调用(实例化对象也能够),而非静态方法只能先实例化对象后才能调用。函数
serizeble有什么用
序列化就是把一个对象保存到一个文件或数据库字段中去,反序列化就是在适当的时候把这个文件再转化成原来的对象使用。我想最主要的做用有:
在进程下次启动时读取上次保存的对象的信息
在不一样的AppDomain或进程之间传递数据
在分布式应用系统中传递数据
序列化
当一个父类实现序列化,子类自动实现序列化,不须要显式实现Serializable接口;
当一个对象的实例变量引用其余对象,序列化该对象时也把引用对象进行序列化;
static,transient后的变量不能被序列化;
抽象类和接口区别:
接口是抽象类的变体,接口中全部的方法都是抽象的。而抽象类是声明方法的存在而不去实现它的类。
接口能够多继承,抽象类不行
接口定义方法,不能实现,而抽象类能够实现部分方法。
接口中基本数据类型为static 而抽类象不是的。
JVM虚拟机结构
JVM主要包括四个部分:
类加载器(ClassLoader):在JVM启动时或者在类运行时将须要的class加载到JVM中。
执行引擎:负责执行class文件中包含的字节码指令(执行引擎的工做机制,这里也不细说了,这里主要介绍JVM结构);
内存区(也叫运行时数据区):是在JVM运行的时候操做所分配的内存区。运行时内存区主要能够划分为5个区域
方法区(Method Area):用于存储类结构信息的地方,包括常量池、静态变量、构造函数等(JDK7 永久代,JDK metaspace)。虽然JVM规范把方法区描述为堆的一个逻辑部分,但它却有个别名non-heap(非堆),因此你们不要搞混淆了。方法区还包含一个运行时常量池。这部分区域不是线程所私有,而是各个线程所共享的。
java堆(Heap):存储java实例或者对象(对象和数组等实例)的地方。这块是GC的主要区域(后面解释)。从存储的内容咱们能够很容易知道,方法区和堆是被全部java线程共享的。
java栈(Stack):java栈老是和线程关联在一块儿,每当建立一个线程时,JVM就会为这个线程建立一个对应的java栈。在这个java栈中又会包含多个栈帧,每运行一个方法就建立一个栈帧,用于存储局部变量表、操做栈、方法返回值等。每个方法从调用直至执行完成的过程,就对应一个栈帧在java栈中入栈到出栈的过程。因此java栈是线程私有的。
程序计数器(PC Register):用于保存当前线程执行的内存地址。因为JVM程序是多线程执行的(线程轮流切换),因此为了保证线程切换回来后,还能恢复到原先状态,就须要一个独立的计数器,记录以前中断的地方,可见程序计数器也是线程私有的。
本地方法栈(Native Method Stack):和java栈的做用差很少,只不过是为JVM使用到的native方法服务的。
4. 本地方法接口:主要是调用C或C++实现的本地方法及返回结果。
双亲委派模型
工做过程是:若是一个类加载器收到了类加载的请求,它首先不会本身去尝试加载这个类,而是把这个请求委派给父类加载器去完成, 每个层次的类加载都是如此 ,所以全部的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈本身没法加载这个加载请求的时候,子加载器才会尝试本身去加载。
使用这种机制,能够避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次(试想若是有人编写了一个恶意的基础类,好比String类,并装载到JVM中将会引发多么可怕的后果呢。可是,因为有了全盘负责委托机制,String类 永远是有根装载器装载,这样就避免了事件的发生)。
ClassLoader主要对类的请求提供服务,当JVM须要某类时,它根据名称向ClassLoader要求这个类,而后由ClassLoader返回这个类的class对象。
每当 JVM 启动的时候,就会产生 三个 ClassLoader,它们分别是Bootstrap Loader, ExtClassLoader 和 AppClassLoader,ClassLoader就是用来动态加载class文件到内存当中用的。
Bootstrap Classloader启动类加载器,主要负责java_home/lib下的核心api或者-Xbootstrap选项指定的jar包装入工做。
Extension ClassLoader扩展类加载器,主要负责java_home/lib/ext下jar包。
App CLassLoader 系统类加载器,主要负责Java -classpath/所指的目录下的类与jar包的装入工做。
UserCustom ClassLoader用户自定义类加载器,在程序运行期间,经过Java.lang.Classloader的子类动态加载class。
ExtClassLoader的父类加载器是null,只不过在默认的ClassLoader 的 loadClass 方法中,当parent为null时,是交给BootStrapClassLoader来处理的,并且ExtClassLoader 没有重写默认的loadClass方法,因此,ExtClassLoader也会调用BootStrapLoader类加载器来加载,这就致使“BootStrapClassLoader具有了ExtClassLoader父类加载器的功能”。
查看classloader的源码能够发现三个重要的方法:
loadClass。classloader加载类的入口,此方法负责加载指定名字的类,ClassLoader的实现方法为先从已经加载的类中寻找,如没有则继续从父ClassLoader中寻找,如仍然没找到,则从BootstrapClassLoader中寻找,最后再调用findClass方法来寻找,如要改变类的加载顺序,则可覆盖此方法,如加载顺序相同,则可经过覆盖findClass来作特殊的处理,例如解密、固定路径寻找等,当经过整个寻找类的过程仍然未获取到Class对象时,则抛出ClassNotFoundException。如类须要resolve,则调用resolveClass进行连接。
findClass。它接受要加载的类做为它的参数,在该方法中会找到class文件而且读取文件中的内容到一个 byte 数组。此方法直接抛出ClassNotFoundException,所以须要经过覆盖loadClass或此方法来以自定义的方式加载相应的类。
defineClass。此方法负责将二进制的字节码转换为Class对象,这个方法对于自定义加载类而言很是重要,如二进制的字节码的格式不符合JVM Class文件的格式,抛出ClassFormatError;如须要生成的类名和二进制字节码中的不一样,则抛出NoClassDefFoundError;如须要加载的class是受保护的、采用不一样签名的或类名是以java.开头的,则抛出SecurityException;如需加载的class在此ClassLoader中已加载,则抛出LinkageError。
致使Gc的状况:
tenured被写满
perm被写满
System.gc()的显式调用。
上一次GC以后heap的各域分配策略动态变化。
JVM分别对新生代和旧生代采用不一样的垃圾回收机制
将对象按其生命周期的不一样划分红:年轻代(Young Generation)、年老代(Old Generation)、持久代(Permanent Generation)
常见检测出垃圾算法:
引用计数法
可达性分析算法
新生代的GC(Minor GC): 指发生在新生代的垃圾收集动做,由于 Java 对象大多都具有朝生夕灭的特性,因此 Minor GC 很是频繁,通常回收速度也比较快。新生代一般存活时间较短,所以基于Copying算法来进行回收,所谓Copying算法就是扫描出存活的对象,并复制到一块新的彻底未使用的空间中,对应于新生代,就是在Eden和FromSpace或ToSpace之间copy。
新生代采用空闲指针的方式来控制GC触发,指针保持最后一个分配的对象在新生代区间的位置,当有新的对象要分配内存时,用于检查空间是否足够,不够就触发GC。当连续分配对象时,对象会逐渐从eden到survivor,最后到旧生代。
旧生代的GC(Major GC / Full GC):指发生在老年代的 GC。旧生代与新生代不一样,对象存活的时间比较长,比较稳定,所以采用标记(Mark)算法来进行回收,所谓标记就是扫描出存活的对象,而后再进行回收未被标记的对象,回收后对用空出的空间要么进行合并,要么标记出来便于下次进行分配,总之就是要减小内存碎片带来的效率损耗。 MajorGC 的速度通常会比 Minor GC 慢 10倍以上。Thinking in java给Java gc取了一个罗嗦的称呼:“自适应、分代的、中止-复制、标记-扫描”式的垃圾回收器。
JVM调优
从如下几个方面进行:
线程池:解决用户响应时间长的问题
链接池
JVM启动参数:调整各代的内存比例和垃圾回收算法,提升吞吐量
程序算法:改进程序逻辑算法提升性能
内存泄露:
归纳地说,这就是内存托管语言中的内存泄漏产生的主要缘由:保留下来却永远再也不使用的对象引用。
全局集合
缓存
典型的算法是:
检查结果是否在缓存中,若是在,就返回结果。
若是结果不在缓存中,就进行计算。
将计算出来的结果添加到缓存中,以便之后对该操做的调用可使用。
该算法的问题(或者说是潜在的内存泄漏)出在最后一步。若是调用该操做时有至关多的不一样输入,就将有至关多的结果存储在缓存中。很明显这不是正确的方法。为了预防这种具备潜在破坏性的设计,程序必须确保对于缓存所使用的内存容量有一个上限。
所以,更好的算法是:
检查结果是否在缓存中,若是在,就返回结果。
若是结果不在缓存中,就进行计算。
若是缓存所占的空间过大,就移除缓存最久的结果。
将计算出来的结果添加到缓存中,以便之后对该操做的调用可使用
ClassLoader
ClassLoader的特别之处在于它不只涉及“常规”的对象引用,还涉及元对象引用,好比:字段、方法和类。这意味着只要有对字段、方法、类或ClassLoader的对象的引用,ClassLoader就会驻留在JVM中。由于ClassLoader自己能够关联许多类及其静态字段,因此就有许多内存被泄漏了。
volatile关键字怎么实现
可见性的意思是当一个线程修改一个共享变量时,另一个线程能读到这个修改的值。
若是你们有兴趣查看代码JIT生成后的汇编指令,会发现针对volatile的变量的写操做,会有一个Lock指令,这是用来实现内存屏障的,保证若是一个处理器修改了变量值,会直接将值写回到内存,其余的处理器对应的缓存也会失效,须要从新从内存中读取,这样就保证全部的处理器读到的值,都是最近的变量值。将当前处理器缓存行的数据会写回到系统内存。这个写回内存的操做会引发在其余CPU里缓存了该内存地址的数据无效。