(1)每一个栈帧内部都包括一个指向运行时常量池中该栈帧所属方法的引用。包含这个引用的目的就是为了支持当前方法的代码能够实现动态链接(Dynamic Linking)。
(2)在Java源文件被编译到字节码文件中时,所有的变量和方法引用都作为符号引用保存在class文件的常量池中。比如:描述了一个方法调用了另外的其他方法时,就是通过常量池中指向 方法的符号引用来表示的,那么动态链接的作用就是为了将这些符号引用转换为调用方法的直接引用。
在JVM中,将符号引用转换为调用方法的直接引用与方法的绑定机制相关。
(1)静态链接:当一个字节码文件被装载进JVM时,如果被调用的目标方法在编译期可知,且运行期保持不变。这种情况下将调用方法的符号引用转换为直接引用的过程称之为静态链接。
(2)动态链接:如果被调用的方法在编译期无法被确定下来,也就是说,只能在程序运行期间将调用方法的符号引用转换为直接引用,由于这种引用转换过程具有动态性,因此也就被称之为动态链接。
非虚方法:
(1)如果方法在编译期就确定了具体的调用版本,这个版本在运行时是不可变的。这样的方法称之为非虚方法。
(2)静态方法、私有方法、final方法、实例构造器、父类方法都是非虚方法。
(3)其他方法称为虚方法。
(4)子类多态性的使用前提:类的继承关系,方法的重写
虚拟机中提供了以下几条方法调用指令:
普通指令调用:
(1)invokestatic:调用静态方法,解析阶段确定唯一方法版本
(2)invokespecial:调用
(3)invokevirtual:调用所有虚方法
(4)invokeinterface:调用接口方法
动态调用指令:
(5)invokedynamic:动态解析出需要调用的方法,然后执行
前四条指令固化在虚拟机内部,方法的调用执行不可人为干预,而invokedynamic指令则支持用户确定方法版本。其中invokestatic指令和invokespecial指令调用的方法称为非虚方法,其余的(final修饰的除外)称为虚方法。
(1)动态类型语言和静态类型语言两者的区别就在于对类型的检查是在编译期还是在运行期,满足前者就是静态类型语言,反之就是动态类型语言。
(2)说的再直白一点就是,静态类型语言是判断变量自身的类型信息;动态类型语言是判断变量自身的类型信息,动态类型语言是判断变量值的类型信息,变量没有类型信息,变量值才有类型信息,这是动态语言的一个重要特征。
(1)存放调用该方法的PC寄存器的值
(2)一个方法的结束,有两种方式:
正常执行完成
出现未处理的异常,非正常退出
(3)无论出现哪种方式退出,在方法退出后都应该返回到该方法被调用的位置。方法正常退出时,调用者的pc计数器的值作为返回地址,即调用该方法的指令的下一条指令的地址。而通过异常退出的,返回地址是要通过异常表来确定,栈帧中一般不会保存这部分信息。
栈帧中还允许携带与Java虚拟机实现相关的一些附加信息。例如,对程序调试提供支持的信息。
(1)Java虚拟机栈用于管理Java方法的调用,而本地方法栈用于管理本地方法的调用。
(2)本地方法栈,也是线程私有的。
(3)允许被实现成固定或者可动态扩展的内存大小。(在内存溢出方面是相同的)
如果线程请求分配的栈容量超过本地方法栈允许的最大容量,Java虚拟机将会抛出一个StackOverflowError异常。
如果本地方法栈可以动态扩容,并且在尝试扩展的时候无法申请到足够的内存,或者在创建新的线程时没有足够的内存去创建对应的本地方法栈,那么Java虚拟机将会抛出一个OutOfMemoryError异常。
(1)一个JVM实例只存在一个堆内存,堆也是Java内存的核心管理区域。
(2)Java堆区在JVM启动的时候即被创建,其空间大小也就确定了。是JVM管理的最大一块内存空间。
堆内存的大小是可以调节的。
(3)《Java虚拟机规范》规定,堆可以出于物理上不连续的内存空间中,但在逻辑上它应该被视为连续的。
(4)所有的线程共享Java堆,在这里还可以划分线程私有的缓冲区(Thread Local Allocation Buffer,TLAB)。
(1)存储在JVM中的Java对象可以被划分为两类:
一类是声明周期较短的瞬时对象,这类对象的创建和消亡都非常迅速
另外一类对象的声明周期却非常长,在某些极端的情况下还能够与JVM的声明周期保持一致。
(2)Java堆区进一步细分的话,可以划分为年轻代(YoungGen)和老年代(OldGen)
(3)其中年轻代又可以被划分为Eden空间、Servivor0空间和Servivor1空间(有时也叫from区、to区)。
(4)配置新生代与老年代在堆结构中的占比:默认为-XX:NewRatio=2,表示新生代1,老年代2,新生代占整个堆的三分之一。
JVM在进行GC时,并非每次都对上面三个内存(新生代、老年代;方法区:永久代或者元空间)区域一起回收的,大部分时候回收的都是指新生代。
针对HotSpot VM的实现,它里面的GC按照回收区域又分为两大种类型:一种是部分收集(Partial GC),一种是整堆收集(Full GC)
(1)部分收集:不是完整收集整个Java堆的垃圾收集。其中又分为:
新生代收集(Minor GC / Young GC):只是新生代(Eden\S0,S1)的垃圾收集;
老年代收集(Major GC/ Old GC):只是老年代的垃圾收集。目前,只有CMS GC会有单独收集老年代的行为。注意,很多时候Magor GC会有Full GC混淆使用,需要具体分辨是老年代回收还是整体回收;
混合收集(Mixed GC):收集整个新生代以及部分老年代的垃圾收集。目前,只有G1 GC会有这种行为
(2)整堆收集(Full GC):收集真个JAVA堆和方法区的垃圾收集
(1)当年轻代空间不足时,就会触发Minor GC,这里的年轻代满了指的是Eden代满,Survivor满不会触发GC。(每次Minor GC会清理年轻代的内存)
(2)因为Java对象大多都具备朝生夕灭的特性,所以Minor GC非常频繁,一般回收速度也比较快。这一定义既清晰有易于理解。
(3)Minor GC会引发STW,暂停其它用户的线程,等垃圾回收结束,用户线程才恢复运行。
(1)指的是发生在老年代的GC,对象从老年代消失时,我们说“Major GC"或者”Full GC"发生了。
(2)出现了Major GC,经常会伴随至少一次的Minor GC(但非绝对的,在Parallel Scavenge收集器的收集策略中就有直接进行Major GC的策略选择过程)。
也就是说在老年代空间不足时,会先尝试触发Minor GC。如果之后空间还不足,则触发Major GC。
(3)Major GC的速度一般会比Minor GC慢十倍以上,STW的时间更长。(Stop-The-World 简称 STW ,是在垃圾回收算法执行过程中,将jvm内存冻结,停顿的一种状态在STW状态下,所有的线程都是停止运行的 - >垃圾回收线程除外)
触发Full GC执行的情况有如下五种:
(1)调用System.gc()时,系统建议执行Full GC,但是不必然执行
(2)老年代空间不足
(3)方法区空间不足
(4)通过MinorGC进入老年代的平均大小大于老年代的可用内存
(5)由Eden区、survivor space0(From Space)区向survivor space1(To
Space)区复制时,对象大小大于To Space 可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小
说明:full gc是开发或者调优中需要尽量避免的
(1)如果对象在Eden出生并且经过第一次MinorGC后仍然存活,并且能够被Survivor容纳的话,将会被移动到Survivor空间之中,并且将对象年龄设置为1。对象在Survivor区中每熬过一次MinorGC,年龄就增加1一岁,当它的年龄增加到一定程度(默认15岁,其实每个JVM、每个GC都有所不同)时,就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过选项 -XX:MaxTenuringThreshold来设置。
(2)针对不同年两段的对象分配原则如下所示:
优先分配到Eden
大对象直接分配到老年代:尽量避免程序中出现过多的大对象
长期存活的对象分配到老年代
动态对象年龄判断:如果Survivor区中相同年龄的所有对象大小的总和大于Survivor空间的一半,年龄大于或者等于该年龄的对象可以直接进入老年代,无需等到MaxTenuringThreshold中要求的年龄。
空间分配担保: -XX:HandlePromotionFaulure
(1)堆区是线程共享区域,任何线程都可以访问到堆区中的共享数据
(2)由于对象实例的创建在JVM中非常频繁,因此在并发环境下从堆区中划分内存空间时线程不安全的。
(3)为了避免多个线程操作同一地址,需要使用加锁等机制,进而影响了分配速度。
(4)从内存模型而不是垃圾回收的角度,对Eden区域继续进行划分,JVM为每个线程分配了一个私有缓存区域,它包含在Dden空间内部。
(5)多线程同时分配内存时,使用TLAB可以避免一系列非线程安全问题,同时还能够提升分配的吞吐量,因此我们可以将这种内存分配方式称之为快速分配策略。
(6)一旦对象在TLAB空间分配内存失败时,JVM就会尝试着通过使用加锁机制确保数据操作的原子性,从而直接在Eden空间中分配内存。