Java程序员必备前言最近看了Java虚拟机第三版深入理解,整理了一些基本结构图,比较完整。做笔记,让大家一起学习。一、Java虚拟机运行时数据区图JVM内存结构是Java程序员必须掌握的基础。程序计数器程序计数器,可以看作是当前线程执行的字节码的行号指示器,是线程私有的。Java虚拟机栈是线程私有的,与线程具有相同的生命周期。每个方法执行时都会创建一个“栈帧”来存储局部变量表(包括参数)、操作数栈、动态链接、方法出口等信息。局部变量表存储了boolean、byte、char、short等各种基本数据类型。局部方法栈与虚拟机栈基本类似,不同的是虚拟机栈服务于虚拟机执行的java方法machine,而本地方法栈是为Native方法Serve准备的。Java堆Java堆是java虚拟机管理的内存中最大的一块内存区域。它也是各个线程共享的内存区域,在JVM启动时创建。它的大小由-Xms和-Xmx参数设置。-Xms是JVM启动时申请的最小内存,-Xmx是JVM可以申请的最大内存。方法区用来存放虚拟机加载的类信息、常量、静态变量,是各个线程共享的内存区域。-方法区的大小可以通过-XX:PermSize和-XX:MaxPermSize参数来限制。2.堆默认分配图Java堆=老年代+新生代=Eden+S0+S1默认新生代与老年代的比例为1:2,可以通过参数-XX配置:新比率。默认情况下,Eden:from:to=8:1:1,可以通过参数-XX:SurvivorRatio设置即时编译器编译的类型信息、常量、静态变量、代码缓存等数据。4、对象的内存布局一个Java对象在堆内存中包括三部分:对象头、实例数据和padding:对象头包括MarkWord(存储哈希码、GC分代年龄等)和类型指针(对象指向其类型元数据的指针),如果是数组对象,还有一个空间保存数组的长度Instancedata是对象实际存储的有效信息,包括对象的所有成员变量,其大小为由每个成员变量的大小决定。Alignmentpadding不一定存在,它只是一个占位符。5.对象头的MarkWord图MarkWord用于存储对象本身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程ID、有偏见的时间戳等待。在32位的HotSpot虚拟机中,如果对象处于解锁状态,那么MarkWord的32位空间中的25位用于存储对象的哈希码,4位用于存储世代年龄对象的,2位用来存放Lock标志位,1位固定为0,表示是非偏向锁。6.对象与监视器关联结构图对象与监视器是怎样关联的?一个Java对象在堆内存中包含一个对象头,对象头有一个Mark字。Mark字存储锁状态,锁指针指向监视器地址。Synchronized底层与此有关~7.JavaMonitor工作机制图:Java线程同步底层是监控锁Monitor~,下面是JavaMonitor的工作机制图:如果想获取监听线程,首先会进入_EntryList队列。当一个线程获得该对象的监视器后,进入Owner区并将其设置为当前线程,同时counter计数加1。如果线程调用了wait()方法,就会进入WaitSet队列。它会释放monitor锁,即给owner赋null,count减1,进入WaitSet队列阻塞等待。如果其他线程调用notify()/notifyAll(),WaitSet中的一个线程会被唤醒,该线程会再次尝试获取monitor锁,如果成功则进入Owner区。同步方法执行完成后,线程退出临界区,设置monitor的owner为null,释放monitor锁。.8、创建对象内存分配流程图对象一般都是在Eden区产生的。如果Eden区满了,就会触发YoungGC。当YoungGC被触发时,Eden区被清空,没有被引用的对象直接清空。存活下来的对象会被送到Survivor区,Survivor=S0+S1。每次YoungGC发生时,将存活的对象复制到未使用的Survivor区,同时将另一个当前正在使用的Survivor区完全清除,然后交换两个Survivor区的使用情况。如果YoungGC要转移的对象大于Survivor区上限,则对象直接进入老年代。一个对象不可能一直留在新生代。如果多次GC后还活着,且次数超过-XX:MaxTenuringThreshold的阈值,则直接进入老年代。总之,对象经历了多次江河红尘的风波,终于成为了长者(进入老年)9.可达性分析算法,判断一个对象是否还活着可达性分析算法的使用判断一个对象是否存活~算法核心思想:以一系列称为“GCRoots”的对象为起点,按照引用关系从这些节点开始搜索,搜索路径称为“引用链”。当一个对象到达GCRoots而没有任何引用链连接时(从GCRoots到这个对象不可达),证明这个对象不能再使用了。10.标记清除算法示意图标记清除算法是最基本的垃圾收集算法。该算法分为标记和清除两个阶段。首先标记需要回收的对象。标记完成后,标记对象统一回收。当然也可以反过来,先标记存活的对象,将未标记的对象统一回收。mark-clear的两个缺点是执行效率不稳定和内存空间碎片化~11。mark-copy算法示意图1969年Fenichel提出“半区复制”,将内存容量分成两等份,只使用每个时间块。当这块内存用完后,将存活的对象复制到另一块,然后一次性清理掉使用过的内存空间~1989年AndrewAppel提出“Appel回收”,将新生代分为更大的Eden和两个较小的幸存者空间。每次内存分配只使用Eden和一个Survivor空间。当发生垃圾回收时,Eden和Survivor中存活的对象会一次性复制到另一个Survivor空间中。Eden和Survivor的比例为8:1~“半区复制”的缺点是浪费了可用空间,如果对象的存活率高,复制的次数会增加,效率会降低.12.mark-sort算法示意图1974年,Edward提出了“mark-sort”算法。标记过程与“标记-清除”算法相同,然后将所有存活的对象移动到内存空间的一端,然后直接清理边界外的对象。memory~mark-clear算法和mark-clear算法的本质区别是:前者是非移动恢复算法,后者是移动恢复算法。是否移动幸存的物体有利也有弊。移动时内存回收虽然复杂,但从程序吞吐量上来说更划算;不动的时候内存分配比较复杂,但是垃圾回收的停顿时间会比较短,所以要看收集器。~ParallelScavenge收集器基于标记排序算法,因为它关注吞吐量。CMS收集器基于标记-清除算法,因为它与延迟有关。13、垃圾收集器组合图新生代收集器:Serial、ParNew、ParallelScavenge老年代收集器:CMS、SerialOld、ParallelOldHybrid收集器:G114。类生命周期图一个类从加载到虚拟机内存开始,直到内存被卸载,这个生命周期经历了加载、验证、准备、解析、初始化、使用、卸载七个阶段。加载阶段:获取通过其完全限定名称定义此类的二进制字节流。将这个字节流表示的静态存储结构转换成方法区的运行时数据结构。在内存中生成一个代表该类的java.lang.Class对象,作为该类在方法区的各种数据的访问入口校验:校验的目的是保证Class文件的字节流所包含的信息满足约束要求,保证这些代码在运行时不会对虚拟机本身造成伤害。安全验证阶段包括:文件格式验证、元数据验证、字节码验证和符号引用验证。准备准备阶段是为类中定义的变量(静态变量)正式分配内存,并设置类变量初值的阶段。解析解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程。初始化在初始化阶段,真正执行类中定义的Java字节码。15.类加载器双亲委派模型示意图双亲委派模型构成启动类加载器、扩展类加载器、应用类加载器、自定义类加载器双亲委派模型。双亲委托模型的工作过程是,如果一个类加载器收到一个类加载请求,它不会首先尝试自己加载这个类,而是将这个请求委托给父类加载器来完成。每个类加载器都是如此,只有当父加载器在自己的搜索范围内找不到指定的类时(即ClassNotFoundException),子加载器才会尝试自己加载。为什么需要双亲委派模型?如果没有双亲委托,那么用户是否可以自己定义一个java.lang.Object和java.lang.String同名的类,放到ClassPath中,然后类之间的比较结果和唯一性班级将无法保证。因此,双亲委托模型可以防止内存中出现相同字节码的多份拷贝。16.栈帧概念结构图栈帧是虚拟机支持的方法调用和方法执行背后的数据结构。栈帧中存放了方法的局部变量表、操作数栈、动态链接和方法返回地址信息。局部变量表是一组变量值的存储空间,用于存储方法参数和方法内部定义的局部变量。局部变量表的容量以变量槽(VariableSlot)为最小单位。操作数栈操作数栈,又称操作栈,是一个后进先出的栈。当一个方法刚开始执行时,该方法的操作数栈也是空的。方法执行过程中,会有各种字节码指令对操作数栈进行写入和提取内容,即出栈和Push操作。DynamicLinking每个栈帧在运行时常量池中都包含一个对该栈帧所属方法的引用,持有该引用是为了在方法调用时支持动态链接(DynamicLinking)。方法返回地址当一个方法开始执行时,只有两种方法可以退出该方法。一个是执行引擎遇到方法返回的任何字节码指令。另一种退出方式是在方法执行过程中遇到异常。17.Java内存模型图Java内存模型规定所有的变量都存储在主存中。每个线程也有自己的工作内存。线程的工作内存存储线程中使用的变量的主内存副本。复制线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存。不同的线程不能直接访问对方工作内存中的变量,线程间变量的传递需要自己的工作内存和主存之间进行数据同步。18、线程状态转换图Java语言定义了6种线程池状态:新建(New):创建后还未启动的线程处于该状态运行(Running):线程启动start()方法,将进入该状态状态。无限等待(Waiting):处于这种状态的线程不会被分配处理器执行时间。一般LockSupport::park()和没有Timeout的Object::wait()方法都会使线程陷入无限等待状态。TimedWaiting:处于该状态的线程不会被分配处理器执行时间,一定时间后会被系统自动唤醒。sleep()方法会进入这个状态~阻塞(Blocked):当程序等待进入同步区时,线程会进入这个状态~Terminated:终止线程的线程状态,线程已经执行完19.类文件格式图u1、u2、u4、u8分别表示1字节、2字节、4字节、8字节的无符号数表由多个无符号数或其他表作为数据项组成复合数据类型前四个字节每个Class文件的名字叫一个magicnumber(记得之前校招面试,面试官问我magicnumber是什么。。。)minor和majorversion代表minor版本号,major版本号紧跟在major之后和次要版本号,还有常量池条目。常量池可以类比Class文件中的资源仓库~20。JVM参数思维导图JVM调优是进阶开发的必经之路,所以认真积累JVM参数配置哈~
