Java 最佳实践(一)

一、JVM

  1. Jvm的体系结构?

     包括: 类装载器(class loader subsystem)子系统、运行时数据、执行引擎(execution engine)。

         说明:

                jvm实例是一个进程,对应了一个独立运行的java程序;

     执行引擎实例对应一个用户线程。       

      2. Jvm内存组成?(运行时数据)

    

         a. 堆内存

    运行时动态分配实例对象和数组,栈存放对象引用;
    线程共享;
    GC回收;
   b. 栈内存
    存储方法状态 如局部变量、方法参数、返回值等;
    线程隔离;
    执行方法先添加栈帧,执行完就出栈;

    StackOverflowError,OutOfMemoryError.

    c. Native栈
    功能与栈相同;
    每次调用本地方法,另起一个本地栈;  

  

    例如:  JavaMethod -> JavaMethod -> CMethod -> CMethod -> JavaMethod -> JavaMethod.

   d. 方法区

    类加载.class文件后,存放 类型信息、属性、方法、静态或final常量;

    线程共享;

         e. 寄存器

              pc程序计数器(记录下一个程序执行位置);

              optop操作数栈顶指针(记录Java栈区的指针);

          说明:

              直接内存说明: 

               直接内存不是虚拟机运行时数据区的一部分。通过Native函数库直接分配的堆外内存,然后通过存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。          

  3. 类装载器

    a. 类加载过程

               加载(Loading) 查找class字节码文件,直接的父类和接口都会加载到字节数组中;

     链接(Linking)(a)检查:检查载入的class文件数据的正确性 (b)准备:给类的静态变量分配存储空间 (c)解析:将符号引用转成直接引用
               初始化(init) 对静态变量、静态代码执行初始化操作。
            b. 类加载实现原理

    JVM的类加载是通过ClassLoader及其子类来完成的,类的层次关系和加载顺序可以由下图来描述(继承关系):

  ①Bootstrap ClassLoader

    负责加载$JAVA_HOME中jre/lib/rt.jar里所有的class,由C++实现,不是ClassLoader子类

  ②Extension ClassLoader

    负责加载java平台中扩展功能的一些jar包,包括$JAVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包

  ③App ClassLoader

    负责记载classpath中指定的jar包及目录中class

  ④Custom ClassLoader

    属于应用程序根据自身需要自定义的ClassLoader,如tomcat、jboss都会根据j2ee规范自行实现ClassLoader

    加载过程中会先检查类是否被已加载,检查顺序是自底向上,从Custom ClassLoader到BootStrap ClassLoader逐层检查,只要某个classloader已加载就视为已加载此类,保证此类只所有ClassLoader加载一次。而加载的顺序是自顶向下,也就是由上层来逐层尝试加载此类。  

双亲委派模型  

    某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。
    优势:
  保证类加载器与类的唯一性,安全性
  双亲委派模型的系统实现:
  在java.lang.ClassLoader的loadClass()方法中,先检查是否已经被加载过,若没有加载则调用父类加载器的loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。如果父加载失败,则抛出ClassNotFoundException异常后,再调用自己的findClass()方法进行加载。

      4. 执行引擎

            将字节码文件转换成jvm可以识别的指令;

            解释器:一条一条的读取,解释并执行字节码指令;

            即时(Just-In-Time)编译器:为了弥补解释器的缺点,在合适的时候,将常用的方法编译成本地代码。

            。因此,内置了JIT编译器的JVM都会检查方法的执行频率,如果一个方法的执行频率超过一个特定的值的话,那么这个方法就会被编译成本地代码。                 

二、GC

       1.  判断对象是否存活的算法?

          a. 引用计数算法

    原理: 为每个对象增加一个引用计数器,当计数器为0,代表对象已经死亡;

            问题:很难解决循环引用问题。

          b. 可达性分析算法

            原理: 它的基本思想是通过一系列被称为“GC Root”的对象为起点,从这个起点向下搜索,搜索走过的路径称为引用链,当某个对象不在任何引用链上时,则说明这个对象不可能再被使用。

            GC Root包括以下几种对象:

            1. 虚拟机栈中引用的对象;

            2. 本地方法栈中JNI引用的对象;

            3. 方法区中类静态成员变量引用的对象;

            4. 方法区中常量引用的对象;

       2. 内存分配和回收策略  

            目前为止,jvm已经发展处三种比较成熟的垃圾收集算法:1.标记-清除算法;2.复制算法;3.标记-整理算法;4.分代收集算法

            1.        标记-清除算法

             这种垃圾回收一次回收分为两个阶段:标记、清除。首先标记所有需要回收的对象,在标记完成后回收所有被标记的对象。这种回收算法会产生大量不连续的内存碎片,当要频繁分配一个大对象时,jvm在新生代中找不到足够大的连续的内存块,会导致jvm频繁进行内存回收(目前有机制,对大对象,直接分配到老年代中)

            2.        复制算法

            这种算法会将内存划分为两个相等的块,每次只使用其中一块。当这块内存不够使用时,就将还存活的对象复制到另一块内存中,然后把这块内存一次清理掉。这样做的效率比较高,也避免了内存碎片。但是这样内存的可使用空间减半,是个不小的损失。

            3.        标记-整理算法

            这是标记-清除算法的升级版。在完成标记阶段后,不是直接对可回收对象进行清理,而是让存活对象向着一端移动,然后清理掉边界以外的内存

           4.        分代收集算法

           当前商业虚拟机都采用这种算法。首先根据对象存活周期的不同将内存分为几块即新生代、老年代、永久代,然后根据不同年代的特点,采用不同的收集算法。在新生代中,每次垃圾收集时都有大量对象死去,只有少量存活,所以选择了复制算法。而老年代中因为对象存活率比较高,所以采用标记-整理算法(或者标记-清除算法)

         GC的执行机制

          由于对象进行了分代处理,因此垃圾回收区域、时间也不一样。GC有两种类型:Scavenge GC和Full GC。

   Minor GC

  一般情况下,当新对象生成,并且在Eden申请空间失败时,就会触发Minor GC,对Eden区域进行GC,清除非存活对象,并且把尚且存活的对象移动到Survivor区。然后整理Survivor的两个区。这种方式的GC是对年轻代的Eden区进行,不会影响到年老代。因为大部分对象都是从Eden区开始的,同时Eden区不会分配的很大,所以Eden区的GC会频繁进行。因而,一般在这里需要使用速度快、效率高的算法,使Eden去能尽快空闲出来。

  Full GC

  对整个堆进行整理,包括Young、Tenured和Perm。Full GC因为需要对整个堆进行回收,所以比Minor GC要慢,因此应该尽可能减少Full GC的次数。在对JVM调优的过程中,很大一部分工作就是对于FullGC的调节。有如下原因可能导致Full GC:

  1.年老代(Tenured)被写满

  2.持久代(Perm)被写满

  3.System.gc()被显示调用

     4.上一次GC之后Heap的各域分配策略动态变化

     

        永生代(Permanent Space)为方法区

三、synchronized

      synchronized

四、内存泄漏和内存溢出区别

  内存泄漏: memory leak,是指程序在申请内存后,无法释放已申请的内存空间,多次泄漏导致内存被耗光。

      例子: 

             1. 数据库连接,网络连接,IO连接等没用显式调用Close关闭,会导致内存泄漏;

             2. 监听器的使用,在是否对象的同时没用相应删除监听器的时候也可能导致内存泄漏;

      内存溢出: out of memory,是指程序申请内存时,没有足够的内存空间供其使用。

 

posted on 2017-10-26 10:48  齊帥  阅读(195)  评论(0编辑  收藏  举报

导航