JVM

      1.什么是jvm?

     JVM全称即是java虚拟机,它区别于一些其它 虚拟机 ,例如VMware 等 ,VMware这些虚拟机是通过软件层面来模拟真正的物理机所具备的功能,而java的jvm是用来加载运行.class文件,内部有它自己的指令集,是针对.class文件而言的独有指令集,而VMware中模拟的是真正物理机中的指令集,两个指令集是不一样的..class文件相当于c语言编译后的文件,C语言编译链接后,可以运行在物理机的操作系统平台,.class文件是java语言编译后生成的文件,它通过链接后也可运行在jvm虚拟机上.

       JVM是用c编写的,它就相当于一个强大的第三方,或者说类似于一个强大的编译器,在不同的操作系统平台上,它有各自的适应版本,它们的适应版本虽然不同,但是它们是可以解析运行同一段java代码,这就是它可移植性的根本依赖,即实现了一次编写,处处运行.

       需要注意的是,JVM与java语言相对独立,java语言编写的程序的运行需要依赖jvm,但是jvm是可以独立出来的,它的上面并不仅仅可以运行java语言,只要符合它的规则的语言都可以运行,如Groovy Clojure Scala这几种语言都可以运行在jvm上.

       2.JVM的发展史

       1996年 SUN JDK 1.0 Classic VM 纯解释运行,使用外挂进行JIT 1997年 JDK1.1 发布 AWT、内部类、JDBC、RMI、反射          

       1998年 JDK1.2 Solaris Exact VM JIT 解释器混合 Accurate Memory Management 精确内存管理,数据类型敏感 提升的GC性能. java1.2开始,称为 java2 ,   java2SE , java2ME ,java2EE .

       2000年 JDK 1.3 Hotspot 作为默认虚拟机发布

          2002年 JDK 1.4 Classic VM退出历史舞台

       2004年发布 JDK1.5 即 JDK5 、J2SE 5 、Java 5  支持泛型 ,注解, 装箱 ,枚举 ,可变长的参数 ,Foreach循环 ,后又出JDK1.6 ,即JDK6  支持 脚本语言,JDBC 4.0 ,Java编译器 API

       2011年 JDK7发布 ,延误项目推出到JDK8 ,有了G1 ,动态语言增强, 64位系统中的压缩指针 ,NIO 2.0

       2014年 JDK8发布,有了 Lambda表达式 语法增强 Java类型注解

          2016年JDK9 发布,有了模块化

                       ♥ 使用最为广泛的JVM为HotSpot ,HotSpot 为Longview Technologies开发,后被SUN收购 .

       2006年 Java开源 并建立OpenJDK HotSpot 成为Sun JDK和OpenJDK中所带的虚拟机

       2008 年 Oracle收购BEA 得到JRockit VM

       2010年Oracle 收购 Sun 得到Hotspot , Oracle宣布在JDK8时整合JRockit和Hotspot,优势互补, 在Hotspot基础上移植JRockit优秀特性

                          KVM SUN发布 IOS Android前,广泛用于手机系统

       CDC/CLDC HotSpot 手机、电子书、PDA等设备上建立统一的Java编程接口 J2ME的重要组成部分

       JRockit BEA

       IBM J9 VM ,  应用于IBM内部

       Apache Harmony 兼容于JDK 1.5和JDK 1.6的Java程序运行平台 与Oracle关系恶劣 退出JCP ,Java社区的分裂 OpenJDK出现后,受到挑战 2011年 退役 没有大规模商用经历 对Android的发展有积极作用

     3.java语言规范

     4.JVM规范  

     5.JVM启动流程:

             

                                 JVM启动流程图

      

      

     6.JVM基本结构

 

        

               一 .  PC寄存器 :

        每个线程都有一个pc寄存器,在线程创建时创建,指向下一条指令的地址,执行本地方法时,pc的值为undefined

     二 .  方法区 :

        保存装载的类的信息,包含有类型的常量池,字段方法信息,方法字节码,通常和永久区perm关联在一起

     三 . JAVA堆

        是和程序开发密切相关的,应用系统对象都保存在java堆中,所有线程共享java堆

        对分代GC来说,堆也是分代的

        GC的主要工作区间

                                 

      四.   JAVA栈 

         java栈是线程私有的,栈由一系列帧组成,因此也叫帧栈,帧保存一个方法的局部变量,常量池指针,操作数栈,每一次方法调用都创建一个帧,并压栈.在多线程访问中,每一个线程过来,都有它自己的栈.函数调用组成帧栈

         操作数栈,java没有寄存器,所有参数传递使用操作数栈, 用来操作数据,做一些加减,出入栈等.

         在java中,栈上分配,函数调用完自动清理

        一般在栈上分配的是没有逃逸(本来这个对象只能本线程操作访问,但是如果其它线程也能操作的话,就叫做逃逸)的小对象,这样直接分配在栈上,可以减轻GC回收压力

        大对象和逃逸对象是无法栈上分配的. 

      五. 内存模型

         每个线程都有一个工作内存和主存独立,工作内存存放主存中变量值的拷贝

              

          为了更好的表现出来,再看下面这张图,手抖,图画的有点...,咳咳,见谅

                              

        可见性: 一个线程修改了变量,其他线程可以立即知道.保证可见性的方法有volatile,final(一旦初始化完成,其他线程就可见),synchronized

        有序性 : 在本线程,操作都是有序的,在本线程外观察,操作都是无序的

        指令重排  : 

            在jcpu指令的执行中,是呈流水线工作状态,从而提高指令的运行数量,提高效率.这种流水线工作状态也有一个弊端,那就是在一个流程的前一个指令操作的数据,需要依赖后面一个流程未执行的指令执行后的结果的时候,这个前面流程的指令不能执行,需要等待后面流程指令执行后才可以,这时候,在这个位置会插入一个气泡,也就是没有指令执行,cpu为了优化执行效率,将这个气泡用其他不相关,不影响的指令填充了,这样整个效率就提升了,这个指令重排是硬件层面的重排.当然,在这个过程中,有些指令是可以重排,有些是不能重排的.这些不用我们来考虑.     指令重排破坏了线程间的有序性,可以通过同步来保证有序性

            指令重排的基本原则,依赖于程序顺序原则,volatile规则,锁规则,传递性等.......

                  解释运行 :  解释执行的意思是读一句,执行一句

        JIT: 即是编译运行,将字节码编译成机器码,直接执行机器码,编译后,性能有数量级的提升

      7.JVM的配置参数

         trace跟踪参数  : 

            -verbose:gc     -xx:printGC    可以打印GC的简要信息

                  -xx:printGCDetails    可以打印GC的详细信息

            -xloggc:log/gc.log      指定GC log的位置,以文件输出

            -xx:+TraceClassLoading    监控类的加载

        堆的分配参数 :

            -xmx  - xms   指定最大堆和最小堆

            -xmn  设置新生代大小

            -xx:NewRatio   新生代(eden+2*s)和老年代的比值

            -xx:Survivor区和eden的比

            -xx:+HeapDumpOnOutOfMemoryError       OOM时导出堆到文件

            -xx:+HeapDumpPath    导出OOM的路径

            -XX:OnOutOfMemoryError=D:/tools/jdk1.7_40/bin/printstack.bat %p    在oom时执行一个脚本,oom就是内存溢出

             根据实际情况调整新生代和幸存代的大小

            官方推荐新生代占堆的3/8 

             幸存代占新生代的1/10

            在OOM时,要Dump出堆,确保可以排查问题

            -xx:PermSIze   -xx:MaxPermSize    设置永久区的初始空间和最大空间,表示一个系统可以容纳多少个类型

            使用CGLIB等库的时候,可能会产生大量的类,这些类有可能撑爆永久区导致OOM

            有时候OOM,打开堆的Dump,可以看到堆空间实际占用非常少,但是永久区溢出一样抛出OOM

         栈大小分配:

            -xss     通常只有几百K,决定了函数调用的深度,每个线程都有独立的栈空间,局部变量,参数分配在栈上

      

        8.GC算法与种类:

           GC即是jvm的垃圾回收机制

           GC算法有:

              引用计数法  

              标记清除法

              标记压缩法

              复制算法

           在java中,GC的对象是堆空间和永久区

           老牌的垃圾回收算法,通过引用计数来回收垃圾,COM,ActionScript#,Python中都有使用

           一  .    引用计数法

                 引用计数器的实现很简单,对于一个对象A,只要有任何一个对象引用了A,则A的引用计数器就加1,

                 当引用失效时,引用计数器就减1.只要对象A的引用计数器的值为0,则对象A就不可能再被使用.

      

                                        

 

                  引用计数法问题: 引用和去引用伴随加法和减法,影响性能,很难处理循环引用

                  二.  标记-清除 

                  标记-清除 算法使现代垃圾回收算法的思想基础.标记-清除算法将垃圾回收分为两个阶段:

                  标记阶段和清除阶段.一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的                  可达对象.因此,未被标记的对象就是未被引用的垃圾对象.然后,在清除阶段,清除所有未被标记的对象.

              三.  标记-压缩

                     标记-压缩算法适合用于存活对象较多的场合,如老年代.它在标记-清除算法的基础上做了一些优

                化.和标记-清除算法一样,标记-压缩算法也首先需要从根节点开始,对所有可达对象做一次标记.但之后

                ,它并不简单的清理未标记的对象,而是将所有的存活对象压缩到内存的一端.之后,清理边界外所有的

                空间.

              四. 复制算法

                与标记-清除算法相比,复制算法使一种相对搞笑的回收方法,但是它不适合用于存活对象较多的场合,

               如老年代.它的原理是将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内

               存中的存活对象复制到未使用的内存中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色

               ,完成垃圾回收

                 复制算法的最大问题是 : 空间浪费  

              引用计数算法没有被java采用

        9.分代思想

            依据对象的存货周期进行分类,短命对象归为新生代,长命对象归为老年代.

            根据不同代的特点,选取合适的收集算法    

               少量对象存活,适合复制算法

               大量对象存活,适合标记清理或者标记压缩

        10.可触及性

           可触及的 : 从根节点可以触及到这个对象

           可复活的    :  一旦所有引用被释放,就是可复活状态,在finalize()中可能复活该对象

           不可触及的  :  在finalize后,可能会进入不可触及状态,不可触及的对象不可能复活,可以回收

          尽量避免使用finalize(),操作不慎会导致错误,可以使用try-catch-finally 来替代它

           根  :  指的是栈中引用的对象 , 方法区中静态成员或者常量引用的对象(全局对象),JNI方法栈中引用的对象

        11.Stop-The-World

            Stop-The-World 是java中一种全局暂停的现象,意思是,所有java代码停止,native代码可以执行,但是不能喝JVM

           交互,多半是由于GC引起,  Dump线程,死锁检查,堆Dump等

            GC有全局停顿,是因为在清理垃圾对象的时候,如果不停止活动,则有新的垃圾产生,在jvm层面来说,它就无法知道

            何时能清理干净了.

           全局停顿的危害, 长时间服务器停止,没有响应,遇到HA系统的话,可能引起主备切换,严重危害生产环境

        GC参数 

          串行收集器:  

              最古老,最稳定,效率高,可能会产生较长的停顿

              -xx:+UseSerialGC

                特点是新生代,老年代使用串行回收,新生代使用复制算法,老年代使用标记-压缩算法

                  

           并行收集器

              ParNew :

              -xx:+UseParNewGC

                新生代并行,老年代串行

              算是Serial收集器新生代的并行版本,

              使用了复制算法.多线程,需要多核支持

              -xx:ParallelGCThreads  限制线程数量  

              

           Parallel收集器

              类似ParNew,新生代使用复制算法,老年代使用标记-压缩

              更加关注吞吐量

                 -xx:+UseParallelGC

                    使用Parallel收集器+老年代串行

                  -xx:+UseParallelOldGC

                    使用Parallel收集器+并行老年代

               -xx:MaxGCPauseMills

                  最大停顿时间,单位毫秒

                  GC尽力保证回收时间不超过设定值

              -xx:GCTimeRatio

                  0-100的取值范围

                  设置的是垃圾收集时间占总时间的比

                  默认是99,即最大允许1%时间做GC

                这两个参数是矛盾的,因为停顿时间和吞吐量不可能同时调优.

                

            CMS收集器

              COncurrent  Mark  Sweep  并发标记清除,就是与用户线程一起执行

              使用了标记-清除算法,与标记-压缩相比,并发阶段会降低吞吐量,老年代收集器,新生代使用了ParNew

              -xx:+UseConcMarkSweepGC

              CMS运行过程比较复杂,着重实现饿了标记的过程,可分为

                 初始标记:

                   根可以直接关联到的对象,速度快

                 并发标记:

                    就是和用户线程一起,主要标记过程,标记全部对象

                 重新标记:

                    由于并发标记时,用户线程依然运行,因此在正式清理前,再一次修正

                 并发清除:

                    和用户线程一起执行

                    基于标记结果,直接清理对象

         

                    

              CMS的特点:

                  尽可能降低停顿,但是会影响系统整体吞吐量和性能.

                   例如,在用户线程运行过程中,分一半CPU去做GC,系统性能在GC阶段,反应速度就下降一半

                  还会导致清理不彻底

                      因为在清理阶段,用户线程还在运行,会产生新的垃圾,无法清理

                  因为和用户线程一起运行,不能在空间快满时再清理

                    -xx:CMSInitiatingOccupancyFraciton设置出发GC的阈值,如果内存预留空间不够,就会引起

                   concurrent mode  failure

                    可以使用串行收集器作为后备

                  关于碎片

                    标记-清除会有碎片产生,标记压缩没有

                  -xx:+UseCMSCompactAtFullCollection Full GC 后 ,进行一次整理,整理过程是独占的,会引起停

                   顿时间变长

                  -xx:+CMSFullGCsBeforCompaction  , 设置进行几次Full gc后,进行一次碎片整理

                  -xx:ParallelCMSThreads    设定CMS的线程数量

                      

                通过前面这些,可以看出,GC的处理也与系统的性能和稳定有很大关系,所以在软件架构设计和代码编写

             以及堆空间分配中,我们就要注意到,从而减轻GC压力....

       GC整个参数的设置整理:         

          -XX:+UseSerialGC:在新生代和老年代使用串行收集器

          -XX:SurvivorRatio:设置eden区大小和survivior区大小的比例

           -XX:NewRatio:新生代和老年代的比

          -XX:+UseParNewGC:在新生代使用并行收集器

          -XX:+UseParallelGC :新生代使用并行回收收集器

          -XX:+UseParallelOldGC:老年代使用并行回收收集器

          -XX:ParallelGCThreads:设置用于垃圾回收的线程数

          -XX:+UseConcMarkSweepGC:新生代使用并行收集器,老年代使用CMS+串行收集器

          -XX:ParallelCMSThreads:设定CMS的线程数量

          -XX:CMSInitiatingOccupancyFraction:设置CMS收集器在老年代空间被使用多少后触发       

          -XX:+UseCMSCompactAtFullCollection:设置CMS收集器在完成垃圾收集后是否要进行一次内存碎片的整理

          -XX:CMSFullGCsBeforeCompaction:设定进行多少次CMS垃圾回收后,进行一次内存压缩

          -XX:+CMSClassUnloadingEnabled:允许对类元数据进行回收

          -XX:CMSInitiatingPermOccupancyFraction:当永久区占用率达到这一百分比时,启动CMS回收           -XX:UseCMSInitiatingOccupancyOnly:表示只在到达阀值的时候,才进行CMS回收

        减小堆的大小,会增加GC压力,升级jdk也会带来额外的性能提升,性能的根本在应用,GC参数属于微调,但是它的设置不

                 合理,会影响性能,产生大的延迟

        12.类装载器    

           BootStrap  ClassLoader  启动类加载器

           Extension   ClassLoader   扩展类加载器

           App   ClassLoader    应用或者系统类加载器

           Custom  ClassLoader  自定义类加载器

          每个类加载器都有一个parent作为父亲

            全盘委托的类加载机制,也就是双亲模式,顶层ClassLoader无法加载底层ClassLoader的类

           Thread.setContextClassLoader()   上下文加载器,用来解决这个问题,双亲模式是默认模式,但是并不是说必须这

          这么做,例如Tomcat就打破这一模式

         13.性能监控工具

          性能监控工具,java自带的就有很多

          死锁 :死锁的结果会导致程序卡死 ,可以用jstack查找死锁

          Jmeter这个工具可以用来测试服务器性能,

         14.锁 

          JVM内部的锁有偏向锁,轻量级锁,自旋锁

          Mark  Word ,对象头的标记,32位,描述对象的hash,锁信息,垃圾回收标记,年龄,指向锁记录的指针,包括指向monitor

         的指针,GC标记,偏向锁线程ID等

            无锁的实现方式:CAS,非阻塞的同步

        15.class文件结构

            魔数

            版本 .....

posted on 2018-09-11 20:41  PandOne  阅读(234)  评论(0编辑  收藏  举报

导航