JVM的结构及基本原理

 

目录

程序计数器(Program Counter Register)

虚拟机栈(VM Stack)

本地方法栈(Native Method Stack)

堆(Heap)

方法区(Method Area)

执行引擎


 

  • 程序计数器(Program Counter Register)

    • 线程私有。可以看作是当前线程所执行字节码的行号指示器。执行引擎中的字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等功能都需要依赖这个计数器来完成。 此内存区域是唯一一个在Java虚拟机规范中没有规定任何OutOfMemoryError情况的区域。
    • 使用程序计数器存储字节码指令地址有什么用?
      • 因为CPU运行时需要在各个线程间切换,切换回来后就得知道需要从哪开始接着执行。JVM的字节码解释器就需要通过改变程序计数器的值来明确下一条应该执行什么样的字节码指令。JVM中的PC寄存器是对物理PC寄存器的一种抽象模拟。
  • 虚拟机栈(VM Stack)

    • 线程私有,生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法在执行的同时都会创建一个栈帧用于存储局部变量表、操作数栈、动态连接、方法出口等信息。每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。
    • 在一条活动线程中,一个时间点上只会有一个活动的栈帧,也就是说只有当前栈顶的栈帧是有效的,与栈帧对应的方法就是当前方法,定义这个方法的类就是当前类。
      • 执行引擎运行的所有字节码指令只针对当前栈帧进行操作。
      • 如果当前方法中调用了其他方法,对应的新栈帧会被创建出来成为新的当前帧。
    • Java虚拟机规范允许Java栈大小是动态的或是固定不变的。可以通过-Xss设置栈大小。
      • 固定大小情况下,如果线程请求分配的栈容量超过Java虚拟机栈允许的最大容量则会抛出StackOverFlowError异常
      • 采用动态扩展的情况下,如果无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建的虚拟机栈则会抛出OOM的异常。
      • 栈的动态扩展有两种方法
        • Segmented stack:可以理解为一个双向链表把多个栈连接起来,它一开始只分配一个栈,当这个栈的空间不够时,就再分配一个,用链表一个一个连起来。
        • Stack copying:在栈不够时,分配一个更大的栈,再把原来的栈复制过去。
    • hotSpot为什么不支持栈的动态扩展?
      • 首先栈是一个固定的数据结构,而且这个结构是运行所需的资源,核心不是为了保存变量,保存的是计算过程中所需的资源。除非你的栈的调用深度比较大这种是一个非常特例的情况。
    • 栈顶缓存技术
      • 将栈顶元素全部缓存在物理CPU的寄存器中,以降低对内存的读写次数
    • 局部变量表(start 有效的起始作用范围,length,有效的作用范围,这俩加一起就是字节码长度)
      • 一个数字数组,主要用于存储方法参数和定义在方法体内的局部变量,这些数据类型包括各种基本数据类型(8中基本类型都可以用数字表示)、对象引用、以及returnAddress类型。局部变量表的大小是在编译器就确定下来的,在运行期不会被更改。
      • 最基本的存储单位是slot(变量槽),32位以内的占一个槽,64位的占两个,byte、short、boolean转换前被转为int。
        • JVM会为局部变量表中的每一个slot都分配一个访问索引,通过这个索引可以成功访问到局部变量表中指定的局部变量值。如果当前栈帧是由构造方法或者实例方法创建的那么this将会放在index为0的slot处,所以静态方法无法使用this。
        • 可以使用 javap -v 表名 查看反编译后的局部变量表信息
      • 局部变量表中的遍历是重要的垃圾回收根节点,只要局部变量表中直接或间接引用的对象都不会被回收。
  • 操作数栈
    • 在方法执行的过程中往栈中写入数据或提取数据,即入栈/出栈(只能有这两个操作),主要用于保存计算过程的中间结果,同时作为计算过程中变量的临时存储空间。虽然使用数组实现,但是不能通过索引访问,只能使用push、pop方式。
    • 每个操作数栈都会有一个明确的栈深度用于存储数值,所需的最大深度在编译期就定义好了。(stack=2, locals=2,)32位的占一个栈单位深度,64位的占两个
    • java虚拟机的解释引擎是基于栈的执行引擎,其中栈指的就是操作数栈,(执行引擎会将操作数栈中的字节码指令翻译为机器指令)
      • 动态链接
        • 每一个栈帧内部都包含一个执行运行时常量池中该栈帧所属方法的引用,包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(Dynamic Linking)。比如:invokedynamic指令。
        • 在java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在class文件的常量池里,动态链接就是为了将这些符号引用转换为调用方法的直接引用。
          • 也就是说,当前方法中如果需要调用其他方法的时候,能够从运行时常量池中找到对应的符号引用,然后将符号引用转换为直接引用,就能直接调用对应方法, 这就是动态链接。
      • 方法的调用:将符号引用转换为直接引用与方法的绑定机制相关
        • 静态链接:当一个字节码文件被装载进JVM内部时,如果被调用的目标方法在编译期可知且运行期保持不变时。这种情况下调用方法的符号引用转换为直接引用的过程称为静态链接。
        • 动态链接:如果被调用的方法在编译期无法确定,也就是说只能够在程序运行期将调用方法的符号引用转换为直接引用,由于这种引用具备动态性,因此也被称为动态链接。
        • 方法的绑定机制分为早期绑定(Early Binding)和晚期绑定(Late Bingind)。绑定是一个字段、方法或类在符号引用被替换为直接引用的过程。
          • 早期绑定:被调用的目标方法在编译期可知,且运行保持不变。
          • 晚期绑定:被调用方法在编译期无法被确定下来,只能够在程序运行期根据实际类型绑定相关的方法。
        • 虚方法与非虚方法:如果方法在编译期就确定具体调用版本,这个版本在运行期间是不可变的。静态方法、私有方法、final方法、实例构造器、父类方法都是非虚方法,其它方法都为虚方法。
        • 如果对于类型的检查是在编译器检查的就叫做静态类型语言(判断变量自身的类型),如果是在运行期检查的就叫动态类型语言(判断变量值的类型)。java是静态类型语言。
    • 方法返回地址
      • 存储调用该方法的程序计数器的值,当一个方法开始执行后,只有两种方式可以退出这个方法,正常执行退出和发生异常退出。正常退出时调用者的程序计数器的值作为返回地址,即调用该方法的指令的下一条指令地址,异常退出的不会给上层调用者返回任何信息。                                                                                                            

  • 本地方法栈(Native Method Stack)

    • 线程私有。本地方法栈与虚拟机栈的作用是非常相似的,他们之间的区别不过是虚拟机栈为虚拟机执行Java方法(字节码)服务,而本地方法栈则为虚拟机使用到的Native方法服务。在虚拟机规范中对本地方法栈中方法使用的语言、使用的方式与数据结构并没有强制的规定,因此虚拟机是可以自由实现的。在HotSpot JVM中,直接将本地方法栈和虚拟机栈合二为一。
  • 堆(Heap)

    • 线程共享。《java虚拟机规范》中对java堆的描述是:所有的对象实例以及数组都应当在运行时分配在堆上。一个JVM实例只存在一个堆内存,堆也是java内存管理的核心区域。堆在JVM启动时就被创建,是JVM管理的最大一块内存空间,堆可以处于物理上不连续的内存空间中,但在逻辑上它应该被视为是连续的。堆是GC执行垃圾回收的重要区域。
    • 可以通过选项-Xms和-Xmx来进行设置堆的启动时大小
      • -Xms用于表示堆的起始内存,Xmx用于表示堆的最大内存,一旦堆区中的内存大小超过-Xmx所指定的内存就会抛出OutofMemoryerror异常
      • JVM每次GC后会根据这两个参数的大小进行扩缩容所以为了避免每次GC后JVM重新分配内存,JVM可将-Xms和-Xmx设为相同值。
    • 字符串常量池和静态变量
      • JDK7中将字符串常量池从方法区转移到了堆空间中,因为永久代的回收效率很低,在FullGc时才会触发,而FullGc是老年代、永久代空间不足才会触发,这就导致字符串常量池回收效率不高,通常开发中会有大量的字符串被创建,回收效率低会导致永久代的内存不足,放到堆中能及时回收内存。
      • 内存分配在日常开发过程中,字符串的创建是比较频繁的,而字符串的分配和其他对象的分配是类似的,需要耗费大量的时间和空间,从而影响程序的运行性能,所以作为最基础最常用的引用数据类型,Java设计者在JVM层面提供了字符串常量池。
      • 在JVM层面为字符串提供字符串常量池,可以理解为是一个缓存区。创建字符串常量时,JVM会检查字符串常量池中是否存在这个字符串,若字符串常量池中存在该字符串,则直接返回引用实例。若不存在,先实例化该字符串,并且,将该字符串放入字符串常量池中,以便于下次使用时,直接取用,达到缓存快速使用的效果。
      • 静态变量(static):跟类关联在一起,随着类的加载而加载,它们称为类数据在逻辑上的一部分,类变量被所有实例共享,即使没有类实例也可以访问它Java7之后保存在堆中。之所以放到堆中和字符串常量池一样,为了方便GC回收。
      • 为了统一设计,把一些偏向于堆的内容放到堆中就包含了字符串常量池和静态变量。
    • 堆上为对象分配内存:指针碰撞和空闲列表
      • 指针碰撞的前提条件是堆中的内存是规整的,也就是说没有内存碎片的产生。所以当内存规整的时候,通过指针碰撞的方式就可以更加充分的利用内存。堆中内存是绝对规整的,中间通过一个指针来进行划分。当有新new的对象要在堆中划分内存时,这个指针会向空闲内存空间偏移一段可以存放下新对象的内存地址,然后再将新的对象存放到刚刚划分出来的新的内存空间当中。
      • 空闲列表的方式是在内存不规整的情况下的一种内存的分配方式。堆中可用空间跟已经使用的空间都相互交错,就没有办法通过指针碰撞这种方式来进行内存分配。这个时候虚拟机会维护一个列表去记录堆当中大大小小的可用内存空间,当新的对象需要进来分配内存空间的时候,会从空闲列表中找到一块能够存放进新对象的内存区域去存放对象,并且更新空闲列表的记录。
      • 创建对象肯定是会发生并发情况的,当某个线程调用的方法在创建对象的时候,他并不知道这个时候会不会有其他线程在这个时候恰巧也在创建对象。这就会产生并发争抢内存的现象。JVM针对这种现象也给出了相应的解决措施,一种是CAS,另外一种则是TLAB。
        • 通过CAS + 失败重试,保证以原子性的方式来对分配内容的动作进行同步处理。
      • TLAB
        • 在每个线程初始化的时候,就在堆空间中为线程分配一块专属的内存。自己线程的对象就往自己专属的那块内存存放就可以了。
        • 从内存模型角度,对Eden区继续进行划分,JVM为每个线程分配了一个私有缓存区域,包含在Eden空间内。多线程同时分配时,使用TLAB可以避免一系列非线程安全的问题,同时还能提升内存分配的吞吐量,因此也将这种方式称为快速分配策略,据我所知所有openJDK衍生出来的JVM都提供了TLAB的设计。
        • JVM将TLAB作为内存分配的首选。默认情况下TLAB空间非常小,仅占Eden空间的1%,可以通过参数设置百分比大小,一 旦对象在TLAB空间分配内存失败时,JVM就会尝试使用CAS的方式进行分配,从而直接在Eden空间中分配内存。
      • 逃逸分析:从JDK7开始,HotSpot中就已经默认开启了逃逸分析
        • 一种可以有效减少Java程序中同步负载和内存堆分配压力的跨函数全局数据流算法,逃逸分析的基本行为就是分析对象动态作用域。
        • 栈上分配
          • 当一个对象在方法中被定义后,对象只在方法内部使用,则认为没有发生逃逸可以分配到栈上,随着方法的执行结束,栈空间被移除。
          • 当一个对象在方法中被定义后,它被外部方法所引用,则认为发生逃逸,尝试使用TLAB进行内存分配。
        • 同步消除(锁消除)
          • 线程同步本身比较耗,如果确定一个对象不会逃逸出线程,无法被其它线程访问到,那该对象的读写就不会存在竞争,对这个变量的同步措施就可以消除掉。单线程中是没有锁竞争。(锁和锁块内的对象不会逃逸出线程就可以把这个同步块取消)
        • 标量替换
          • Java虚拟机中的原始数据类型(int,long等数值类型以及reference类型等)都不能再进一步分解,它们就可以称为标量。相对的如果一个数据可以继续分解,那它称为聚合量,Java中最典型的聚合量是对象。如果逃逸分析证明一个对象不会被外部访问,并且这个对象是可分解的,那程序真正执行的时候将可能不创建这个对象,而改为直接创建它的若干个被这个方法使用到的成员变量来代替。拆散后的变量便可以被单独分析与优化,可以各自分别在栈帧或寄存器上分配空间,原本的对象就无需整体分配空间了。
        • 结论:开发中能使用局部变量的,就不要在方法外定义。

  • 方法区(Method Area)

    • 线程共享。在JVM启动时被创建并且它的实际物理内存空间和Java堆一样是可以不连续的,方法区是它用于存储已被虚拟机加载的类型信息、常量、即时编译器编译后的代码缓存、常量池、运行时常量池等数据。Java虚拟机对于方法区的限制非常宽松,因此也就导致了不同的虚拟机上方法区有不同的表现。
    • 以hotSpot为例:方法区在JDK1.7之前是一块连续的堆空间,也被称为永久代,但本质上两者并不等价,应该说永久代只是方法区的一种实现。这样HotSpot的垃圾收集器就可以向管理Java堆一样管理这部分内存,但是这样也会更容易出现内存溢出问题,因为永久代的GC和老年代是捆绑在一起的,无论谁满了都会触发Full Gc。因此在JDK1.7中将方法区中的字符串常量池移到了堆中,并在JDK1.8中完全废除了永久代,使用元空间替代了永久代。
    • 元空间不再与堆连续,而是使用本地内存。可以通过-XX:MetaSpaceSize和-XX:MaxMetaSpaceSize
      • -XX:MetaSpaceSize:设置初始大小,对于一个64位系统来说,默认值为21MB,这就是初始的高水位线,一旦触及就会触发FullGC并卸载掉没用的类,然后高水位线会被重置,新的高水位线取决于GC后释放了多少元空间,如果不足那么在不超过最大空间的情况下会适当提高该值,如果释放过多则会适当降低。如果初始高水位线设置过低,会触发多次FullGC,所以建议将-XX:MetaSpaceSize设置为一个相对较高的值。
      • -XX:MaxMetaSpaceSize:最大空间,默认为-1即没有限制。
    • 如果定义了太多的类、加载大量第三方jar包、Tomcat部署工程过多、大量动态的生成反射类都会导致方法区溢出,虚拟机会同样抛出OOM异常。关闭JVM就会释放这个区域的内存。                                                                                                
  • 类型信息
    • 对每个加载的类型(类、接口、枚举、注解)JVM必须在方法区中存储这个类的全路径名、这个类的直接父类完整有效名、这个类型的修饰符、这个类型直接接口的一个有序列表等信息。
    • 域信息(Field):也就是平时所说的成员变量,JVM必须在方法区中保存类型的所有域的相关信息以及域的顺序声明。包括:域名称、类型、修饰符
    • 方法信息(Method):方法名称、返回类型、参数的数量和类型(按顺序)、修饰符、方法的字节码、操作数栈、局部变量表大小、异常表。
    • 常量(static final):被声明为fianl的类变量的处理方法不同,每个全局常量在编译的时候就被分配了。
    • 运行时常量池
      • 当类的字节码被加载到内存中后,他的常量池信息就会集中放入到一块内存,这块内存就称为运行时常量池,并且把里面的符号引用变为直接引用。
      • 运行时常量池相对于class文件常量池另一项重要的特征是具备动态性。(String.intern())如果常量池中已经该字符串,则返回池中的字符串;否则将此字符串添加到常量池中,并返回字符串的引用。
    • 常量池
      • 一个有效的字节码文件中除了包含类的版本信息、字段、方法以及接口等描述信息外,还包含一项信息就是常量池表,包括各种字面量和符号引用。这部分内容将在类加载后存放到方法区的运行时常量池中。
        • 字面量比较接近java语言层次的常量概念,如文本字符串、声明为final的常量值等。
        • 符号引用属于编译原理方面的概念,包括三类常量:类和接口的全限定名、字段和名称的描述符、方法和名称的描述符。
      • 为什么需要常量池?常量池有什么用?
        • 一个java源文件中的类、接口编译后产生一个字节码文件,而java中的字节码需要数据支持,通常这种数据会很大以至于不能直接存到字节码里,换另一种方式,可以存到常量池,这个字节码包含了指向常量池的引用,在动态链接的时候会用到运行时常量池。如果类中引用到的结构多(object、string、printStream、system),体积会变得更大。
        • 在解释器解释执行每条JVM指令码的时候,根据这些指令码的符号地址去常量池中找到对应的描述。然后解释器就知道该执行哪个类的那个方法、方法的参数是什么等。
      • 常量池中存储的数据类型包括:数量值、字符串值、类引用、字段引用、方法引用。
      • 常量池可以看做一张表,虚拟机指令根据这张表找到要执行的类名、方法名、参数类型、字面量等类型。
    • 垃圾回收:Java虚拟机规范中对方法区的约束是非常宽松的,可以不要求虚拟机在方法区中实现垃圾收集。
      • 方法区中的垃圾回收主要回收两部分内容:常量池中废弃的常量和不再使用的类型。
        • HotSpot虚拟机对常量池的回收策略是很明确的。只要常量池中的常量没有被任何地方引用,就可以被回收。回收废弃常量与回收Java堆中的对象非常类似。
        • 判断一个类是否属于不再使用的类条件就比较苛刻了,需要同时满足三个条件:
          • 该类所有的实例都已经被回收,也就是说Java堆中不存在该类及其任何派生子类的实例。
          • 加载该类的类加载器已经被回收,这个条件除非是经过设计的可替换类加载器的场景,否则是很难达成的。
          • 该类对应的java.lang.class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法。
        • 在大量使用反射、动态代理、CGLib等字节码框架,动态生成JSP以及OSGI这类频繁自定义类加载器的场景中,通常都需要Java虚拟机具备类型卸载的能力,以保证不会对方法区造成过大的内存压力。                                                                                  
  • 执行引擎

    • 虚拟机是一个相对于物理机的概念,这两种机器都有代码执行能力,其区别是物理机的执行引擎是直接建立在处理器、缓存、指令集和操作系统层面上的,而虚拟机的执行引擎是由软件自行实现的,因此可以不受物理条件制约的定制指令集与执行引擎的结构体系,能够执行哪些不被硬件直接支持的指令集格式。
    • JVM的主要任务是负责装载字节码到其内部,但字节码并不能直接运行在操作系统上 ,因为字节码指令并非等价于本地机器指令,它内部包含仅仅是一些能够被JVM所识别的字节码指令、符号表、以及其他辅助信息。
    • 如果想让一个Java程序运行起来,执行引擎的任务就是将字节码指令解释/编译为对应平台上的本地机器指令才可以,简单来说JVM的执行引擎充当了将高级语言翻译为机器语言的翻译者。
    • 执行引擎在执行过程中究竟需要执行什么样的字节码指令完全依赖于程序计数器。每当执行完一项指令操作后,程序计数器就会更新下一条需要被执行的指令地址。
    • 当然方法在执行的过程中,执行引擎有可能会通过存储在局部变量表中的对象引用准确定位到存储在Java堆区中的对象实例信息,以及通过对象头中的元数据指针定位到目标对象的类型信息。
    • 为什么说Java是半编译半解释性语言?
      • HotSpot采用的是解释执行与即时编译器并存的架构。在Java虚拟机运行时,解释器和即时编译器能互相协作,尽量选择最适合的方式来权衡编译本地代码的时间和解释执行代码的时间。
      • 也可以使用参数设置只使用解释器或者编译器启动。
    • 解释器
      • 当Java虚拟机启动时会根据预定义的规范对字节码采用逐行解释的方式执行,将每条字节码文件中的内容“翻译”为对应平台的本地机器指令执行。
      • 解释器真正意义上所承担的角色就是一个运行时“翻译者”,将字节码文件中的内容“翻译”为对应平台的本地机器指令执行。当一条字节码指令被解释执行完成后,接着再根据程序计数器中记录的下一条需要被执行的字节码指令执行解释操作。
    • 前端编译器和后端编译器
      • 前端编译器:把.java文件编译成本地机器代码的过程。
        • 词法分析
        • 语法分析
        • 语义分析
      • JIT(Just In Time Compiler)即时编译器(后端编译器)
        • 就是虚拟机将源代码直接编译成和本地机器平台相关的机器语言。就是将class文件转变为机器码的过程。
        • 热点代码及探测方式
          • 是否需要启动JIT编译器需要根据代码被调用的执行频率而定。关于那些需要被编译为本地代码的字节码也被称为热点代码,JIT会针对那些频繁被调用的热点代码做出深度优化,将其直接编译为对应平台的机器指令以提升java程序的执行性能。
          • 一个被调用多次的方法或者是一个方法体内部循环次数较多的循环体都可以被称为热点代码,目前HotSpot采用的热点探测方式是基于计数器的热点探测。分为方法调用计数器和回边计数器。
            • 方法调用计数器:用于统计方法被调用的次数默认阈值在Client模式下是1500次,在Server模式下是10000次。超过这阈值就会出发JIT编译,这个阈值可以通过参数调整。
            • 回边计数器:统计一个方法中循环体执行的次数,被循环执行一次+1,与方法调用计数器相加超过阈值就会使用JIT编译。
            • 热度衰减:当超过一定的时间限度,如果方法的调用次数仍不足以让它提交给即时编译器,那这个方法的调用计数器就会减少一半,这个过程称为方法调用计数器热度的衰减。可以使用参数关闭热度衰减。
            • 当一个方法被调用时,会先检查该方法是否存在被JIT编译过的版本如果存在,则优先使用编译后的本地代码来执行。如果不存在已被编译过的版本,则将此方法的调用计数器值加1,然后判断方法调用计数器与回边计数器值之和是否超过方法调用计数器的阈值。如果已超过阈值,那么将会向即时编译器提交一个该方法的代码编译请求。
    • AOT(静态提前编译器)
      • Java9引入了实验性AOT编译工具,它借助了Graal编译器将所输入的Java类文件转换为机器码,并存放至生成的动态共享库之中。AOT编译指的是在程序运行之前,将字节码转换为机器码的过程。
    • 为什么即时编译器效率更高,解释器还没被淘汰?
      • 当程序启动后解释器可以马上发挥作用,省去编译的时间,立即执行。而编译器想要发挥作用,把代码编译成机器语言需要一定的时间,但编译为机器语言后执行效率更高。
      • 当虚拟机启动的时候,解释器可以首先发挥作用,而不必等待即时编译器全部编译完成再执行,这样可以省去许多不必要的编译时间。并且随着程序运行时间的推移,即时编译器逐渐发挥作用,根据热点探测功能,将有价值的字节码编译为本地机器指令,以换取更高的程序执行效率。
      • 同时,解释执行在编译器进行激进优化不成立的时候,作为编译器的“逃生门”。                                                  

  • 29
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
2019最新深入理解JVM内存结构及运行原理JVM调优)高级核心课程视频教程下载。JVM是Java知识体系中的重要部分,对JVM底层的了解是每一位Java程序员深入Java技术领域的重要因素。本课程试图通过简单易懂的方式,系统的深入讲解JVM相关知识。包括JVM执行过程、虚拟机类加载机制、运行时数据区、GC、类加载器、内存分配与回收策略等,全套视频加资料高清无密码  第1讲 说在前面的话 免费 00:05:07  第2讲 整个部分要讲的内容说明 免费 00:06:58  第3讲 环境搭建以及jdk,jre,jvm的关系 免费 00:20:48  第4讲 jvm初体验-内存溢出问题的分析与解决 免费 00:17:59  第5讲 jvm再体验-jvm可视化监控工具 免费 00:21:17  第6讲 杂谈 免费 00:12:37  第7讲 Java的发展历史 00:27:24  第8讲 Java的发展历史续 00:02:27  第9讲 Java技术体系 00:08:46  第10讲 jdk8的新特性 00:07:31  第11讲 lanmbda表达式简介 00:07:02  第12讲 Java虚拟机-classic vm 00:06:06  第13讲 Java虚拟机-ExactVM 00:03:35  第14讲 Java虚拟机-HotSpotVM 00:04:23  第15讲 Java虚拟机-kvm 00:03:04  第16讲 Java虚拟机-JRockit 00:04:12  第17讲 Java虚拟机-j9 00:04:23  第18讲 Java虚拟机-dalvik 00:02:20  第19讲 Java虚拟机-MicrosoftJVM 00:03:57  第20讲 Java虚拟机-高性能Java虚拟机 00:02:58  第21讲 Java虚拟机-TaobaoVM 00:03:06  第22讲 Java内存区域-简介 00:07:56  第23讲 Java内存区域-Java虚拟机栈 00:12:04  第24讲 Java内存区域-程序计数器 00:12:54  第25讲 Java内存区域-本地方法栈 00:02:39  第26讲 Java内存区域-堆内存 00:05:08  第27讲 Java内存区域-方法区 00:06:32  第28讲 Java内存区域-直接内存和运行时常量池 00:15:53  第29讲 对象在内存中的布局-对象的创建 00:21:19  第30讲 探究对象的结构 00:13:47  第31讲 深入理解对象的访问定位 00:08:01  第32讲 垃圾回收-概述 00:06:20  第33讲 垃圾回收-判断对象是否存活算法-引用计数法详解 00:14:08  第34讲 垃圾回收-判断对象是否存活算法-可达性分析法详解 00:07:09  第35讲 垃圾回收算法-标记清除算法 00:04:36  第36讲 垃圾回收算法-复制算法 00:14:35  第37讲 垃圾回收算法-标记整理算法和分代收集算法 00:05:24  第38讲 垃圾收集器-serial收集器详解 00:09:45  第39讲 垃圾收集器-parnew收集器详解 00:04:53  第40讲 垃圾收集器-parallel收集器详解 00:11:02  第41讲 垃圾收集器-cms收集器详解 00:14:58  第42讲 最牛的垃圾收集器-g1收集器详解 00:18:04  第43讲 内存分配-概述 00:04:23  第44讲 内存分配-Eden区域 00:22:51  第45讲 内存分配-大对象直接进老年代 00:06:42  第46讲 内存分配-长期存活的对象进入老年代 00:03:40  第47讲 内存分配-空间分配担保 00:04:54  第48讲 内存分配-逃逸分析与栈上分配 00:10:32  第49讲 虚拟机工具介绍 00:10:27  第50讲 虚拟机工具-jps详解 00:11:20  第51讲 虚拟机工具-jstat详解 00:09:20  第52讲 虚拟机工具-jinfo详解 00:05:03  第53讲 虚拟机工具-jmap详解 00:08:48  第54讲 虚拟机工具-jhat详解 00:08:10  第55讲 虚拟机工具-jstack详解 00:10:19  第56讲 可视化虚拟机工具-Jconsole内存监控 00:07:09  第57讲 可视化虚拟机工具-Jconsole线程监控 00:12:18  第58讲 死锁原理以及可视化虚拟机工具-Jconsole线程死锁监控 00:10:38  第59讲 VisualVM使用详解 00:08:03  第60讲 性能调优概述 00:11:22  第61讲 性能调优-案例1 00:23:28  第62讲 性能调优-案例2 00:10:05  第63讲 性能调优-案例3 00:12:41  第64讲 前半部分内容整体回顾 00:15:41  第65讲 Class文件简介和发展历史 免费 00:11:26  第66讲 Class文件结构概述 免费 00:16:50  第67讲 Class文件设计理念以及意义 免费 00:13:41  第68讲 文件结构-魔数 免费 00:09:49  第69讲 文件结构-常量池 免费 00:23:44  第70讲 文件结构-访问标志 免费 00:11:36  第71讲 文件结构-类索引 00:11:26  第72讲 文件结构-字段表集合 00:13:21  第73讲 文件结构-方法表集合 00:10:06  第74讲 文件结构-属性表集合 00:18:23  第75讲 字节码指令简介 00:09:18  第76讲 字节码与数据类型 00:09:34  第77讲 加载指令 00:09:33  第78讲 运算指令 00:10:24  第79讲 类型转换指令 00:13:42  第80讲 对象创建与访问指令 00:09:38  第81讲 操作树栈指令 00:03:27  第82讲 控制转移指令 00:11:58  第83讲 方法调用和返回指令 00:06:37  第84讲 异常处理指令 00:09:44  第85讲 同步指令 00:07:34  第86讲 类加载机制概述 00:07:26  第87讲 类加载时机 00:13:15  第88讲 类加载的过程-加载 00:15:15  第89讲 类加载的过程-验证 00:10:24  第90讲 类加载的过程-准备 00:05:40  第91讲 类加载的过程-解析 00:14:04  第92讲 类加载的过程-初始化 00:19:41  第93讲 类加载器 00:22:41  第94讲 双亲委派模型 00:17:03  第95讲 运行时栈帧结构 00:08:46  第96讲 局部变量表 00:20:48  第97讲 操作数栈 00:08:36  第98讲 动态连接 00:02:56  第99讲 方法返回地址和附加信息 00:03:24  第100讲 方法调用-解析调用 00:09:49  第101讲 方法调用-静态分派调用 00:16:21  第102讲 方法调用-动态分派调用 00:09:02  第103讲 动态类型语言支持 00:09:27  第104讲 字节码执行引擎小结 00:03:38  第105讲 总结与回顾 00:10:55  第106讲 happens-before简单概述 00:15:17  第107讲 重排序问题 00:23:19  第108讲 锁的内存语义 00:13:54  第109讲 volatile的内存语义 00:12:04  第110讲 final域内存语义

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值