• 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,是指程序申请内存时,没有足够的内存空间供其使用。

    齊帥
  • 相关阅读:
    Centos7 安装Postgres11(更改数据目录)
    将trj保存成.gpx文件方便进行地图匹配(来自徐博士的支援)
    将北京路网OSM文件导入到PostgreSQL + PostGIS 中,并利用osm2pgrouting工具+osmosis工具构建路网Graph拓扑结构
    SQL-时间-UTC-时间戳-日期-年查询在PG+PostGIS
    地理坐标系4326--投影坐标系3857/2436
    基于postgis时空查询-记录而已
    Java 接口
    单例模式
    weblogic启动一闪而过
    oracle存储过程中is和as区别
  • 原文地址:https://www.cnblogs.com/qishuai/p/7735670.html
Copyright © 2020-2023  润新知