java 后端开发——JVM面试考点
2023-02-25 09:42:56 1 举报
AI智能生成
jvm 面试知识点总结
作者其他创作
大纲/内容
JVM内存模型
堆内存
java 大多数对象分配区域
虚拟机栈
线程栈,每个线程分配一定栈空间大小。线程栈中保存着栈帧
每开启一个方法,从线程栈中获取一块栈帧空间
每开启一个方法,从线程栈中获取一块栈帧空间
栈帧
局部变量表
操作数栈
动态链接
方法出口
本地方法栈
JVM 中本地native方法区域
元空间/方法区
类元数据信息,运行时常量池,类型信息,字段信息,方法信息,类加载器引用,对Class的引用
程序计数器
记录程序执行位置
java类加载
类加载过程
ClassLoader.loadClass(Class)
ClassLoader.loadClass(Class)
加载
磁盘上读取字节码文件,并通过io读取到内存中。
只有使用到的类才会被加载,如new 。在加载过程中会生成一个
Java.Lang.Class对象到堆内存中。作为方法区调用该类的入口。
只有使用到的类才会被加载,如new 。在加载过程中会生成一个
Java.Lang.Class对象到堆内存中。作为方法区调用该类的入口。
验证
校验字节码文件的正确性
准备
给内的静态变量分配内存并赋初值
解析
将符号引用转换为直接引用,将类中的静态方法替换为指向内存数据的指针或句柄,这就是静态链接的过程
初始化
给类中的静态变量赋予初始值(程序员指定),执行静态代码块
类加载器
引导类加载器
负责加载JVM的核心类库,位于jre/lib下
扩展类加载器
负责加载jvm扩展类库,位于jre/lib/ext下
应用程序类加载器
负责加载jvm类路径下,程序员写的Class
自定义加载器
程序员自定义类加载器,加载自定义路径下的类库
类加载的双亲委派机制
加载某个类时会先委托父加载器寻找目标类,
找不到再委托上层父加载器加载,
如果所有父加载器在自己的加载类路径下都找不到目标类,
则在自己的类加载路径中查找并载入目标类
找不到再委托上层父加载器加载,
如果所有父加载器在自己的加载类路径下都找不到目标类,
则在自己的类加载路径中查找并载入目标类
首先,检查一下指定名称的类是否已经加载过,如果加载过了,就不需要再加载,直接返回。
如果此类没有加载过,那么,再判断一下是否有父加载器;如果有父加载器,则由父加载器加载(即调用parent.loadClass(name, false);).或者是调用bootstrap类加载器来加载。
如果父加载器及bootstrap类加载器都没有找到指定的类,那么调用当前类加载器的findClass方法来完成类加载。
为什么要设计双亲委派机制?
沙箱安全机制:自己写的java.lang.String.class类不会被加载,这样便可以防止核心API库被随意篡改
避免类的重复加载:当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次,保证被加载类的唯一性
全盘负责委托机制
当一个ClassLoder装载一个类时,除非显示的使用另外一个ClassLoder,该类 所依赖及引用的类也由这个ClassLoder载入。
java对象
对象创建流程
类加载检查
创建一个对象时,先判断该类是否被加载过,如果未被加载,先进行类加载。
分配内存
类加载完成后类的大小确定,jvm分配足够大小的内存空间给该类存放对象数据
对象内存分配方式
指针碰撞
JVM可使用的堆内存是连续的,使用一个指针将连续的内存空间分成已使用和未使用两部分。
当需要为对象分配内存时,将指针向未使用内存方向移动足够大小内存空间
当需要为对象分配内存时,将指针向未使用内存方向移动足够大小内存空间
空闲列表
堆内存空间不是连续时使用该方式。JVM维护一张空闲内存列表,当需要为对象分配内存时,从列表中拿出足够大小内存。
并更新空闲列表。
并更新空闲列表。
内存分配并发冲突问题?
CAS失败重试机制:JVM采用该方式保证分配内存原子性,同步分配。
TLAB本地线程分配缓冲机制:将分配内存放在按不同线程分的内存中进行。
每个线程在开始就从JVM中分配一块内存空间。通过+XX:TLABSize (TLABSize分配大小,默认开启)
每个线程在开始就从JVM中分配一块内存空间。通过+XX:TLABSize (TLABSize分配大小,默认开启)
初始化赋零值
内存分配完成后,需要对内存中的字段赋予零值,保证字段没有设置值也能访问。
如果开启了TLAB,该阶段可以提前到内存分配中进行
如果开启了TLAB,该阶段可以提前到内存分配中进行
设置对象头
一个对象包括对象头、实例数据、内存对齐三个部分。
对象头包括Mark Word 和Kclass Pointer 指针。
如果对象是数组还要加上数组长度。
对象头包括Mark Word 和Kclass Pointer 指针。
如果对象是数组还要加上数组长度。
Mark Word:32位操作系统站4个字节;64为操作系统站8个字节
这里记录了对象运行时的数据:
这里记录了对象运行时的数据:
锁状态
Hash Code
GC分代年龄
锁标志
是否偏向锁
Kclass pointer 类型指针 : 指向对象的类元数据,通过这个指针知道是那个类的对象。
java指针压缩:jdk 1.6 update14 开始支持64位机器指针压缩。
开启指针压缩64位占4字节,关闭指针压缩占8字节
使用jvm参数 +CompressdOops(默认开启)开启指针压缩
开启指针压缩64位占4字节,关闭指针压缩占8字节
使用jvm参数 +CompressdOops(默认开启)开启指针压缩
64 位操作系统中使用指针压缩可以减少大于1.5 倍的内存使用量
指针压缩可以减少由于使用大指针在主存和缓存中移动数据带来的较大带宽占用和GC压力。
在jvm中32 位地址最大支持4g内存配置,使用指针压缩可以让32地址操作大于4g的内存(小于等于32)。堆内存小于4G时,不需要开启指针压缩,jvm 会去掉最高32位地址。堆内存大于32 G时,指针压缩会失败。使用64位指针寻址。
执行init
程序员执行赋值操作,对象字段赋初始值,执行构造方法
JVM垃圾分代收集机制
对象在栈上分配:JVM开启逃逸分析,确保一个不被外部访问的对象,
且栈上有足够大的内存空间。
且栈上有足够大的内存空间。
对象在堆上分配时,需要使用到GC进行垃圾回收,当对象非常多时,会给GC带来压力,导致性能降低。jvm为了减少这堆分配内存数量。jvm通过逃逸分析确保一个对象不会被外部访问,这样的对象jvm就会在栈上进行分配,当然这个对象必须在栈上找到足够的空间。否则还是会在堆上进行分配。在这种情况下为了尽量是对象在栈上分配,jvm采用标量替换的方式,将一个对象拆分成对象的单个成员属性,而不是直接创建对象。
Eden区分配:大多数对象都会在Eden区创建
当Eden 区空间分配满时,会进行一次minor gc ,这时99%的对象都会配置回收,未被回收的对象将会被移动到s0 区 同时分代年龄加1。再次进行minor gc,未被回收的对象将再次被移动到s1区。当未被回收的对象GC分代年龄大于15(默认可设置)时,这些对象将被移动到老年代。如果在minor gc 的过程中s0或者s1区放不下时,对象将会被直接放到老年代中。当到老年代满时,jvm会执行一次full GC 回收 新生代、老年代、元空间的垃圾对象。
minor gc :执行非常频繁,回收速度快。
full gc :回收速度慢,一般执行时间时minor gc 的10 倍左右。所以要尽量减少full gc 的次数。
大对象直接放入老年代
大对象需要直接放入老年代,因为其需要大量连续的内存空间,如:数组、字符串。在对大对象进行复制移动操作时会消耗性能,降低效率。
长期存活的对象进入老年代
jvm 采用分代年龄的方式判断对象是否是长期存在的对象,jvm 会为每个对象赋予一个年龄计数器,对象没逃逸一次gc分代年龄加一,当分代年龄达到15(默认,可设置)时,对象会被移动到老年代
对象动态年龄判断机制
对象动态年龄判断机制一般在minor gc 之后触发,当前survivor 区域中(其中放对象的那块s区),所有对象大小大于等于该 s 区域的50% 时(可设置),会将这个区域大于等于这批年龄最大值的对象放入老年代。 例如:s 区的一批对象, 年龄1 + 年龄2 + ...+年龄n 内存大于等于s 区的50%,那么 年龄大于等于n 的对象将被直接放入老年代。
老年代空间分配担保机制
年轻代每次在minor gc 之前 都会进行判断整个年轻代对象大小(包括垃圾对象)是否大于老年代剩余空间大小。如果小于直接进行minor gc ,如果大于,再判断是否设置-XX:-HandlePromotionFailure(jdk1.8 默认设置)。未设置这个参数将直接进行一次full gc ,如果设置了这个参数,判断 之前minor gc 后进入老年代对象大小的平均值是否大于老年代剩余空间。如果大于直接进行full gc ,full gc 后,老年代还是没有空间放新对象,直接oom。如果小于老年代剩余空间,就执行minor gc ,如果还是放不下,在进行full gc 。full gc 后还是放不下。直接 oom
对象内存回收
引用计数法
给每个对象加一个引用计数器,当一个对象被其他地方引用时,引用计数器加1。当一个引用被释放时计数器减一。当计数器为0时表示这个对象未被任何应用。可以被回收。这个方法实现简单、效率高。但很难解决循环引用问题。所以主流的虚拟机很少使用这种方式进行垃圾回收。
可达性分析算法
使用GC ROOT 作为对象应用的根节点,向下搜索,搜索到的对象标记为非垃圾对象,其余的自然就是垃圾对象。进行回收
GC ROOT
线程栈的本地变量、静态变量、本地方法栈等变量
判断一个类为无用类
该类所有对象被回收
该类的ClassLoader 类加载器被回收
该类的Java.Lang.Class 没有被引用
jvm垃圾收集器
分代收集理论
JVM 回收垃圾对象采用分代收集理论收集,jvm 将堆内存分为年轻代和老年代,为不同年龄代采用不同的收集算法进垃圾对象收集。年轻代的对象大多数是朝生夕死的对象,采用标记复制算法进行垃圾回收;老年代的对象是长期存在于堆内存的对象,多采用标记清楚或标记整理算法。注:标记复制算法的效率在标记 清楚和标签整理算法的10倍已上。
垃圾收集算法
标记复制算法
需要两块同样大小的内存空间A和B,将对象在A内存空间进行分配,当需要进行垃圾回收时,将A内存空间中标记不是垃圾的对象复制到B内存空间,然后将A内存空间全部进行清楚。
标记清除算法
使用一块内存空间A,将对象分配在A 内存空间中,当需要进行垃圾回收时,将A 内存中不是垃圾的对象打上标记。清楚其他未标记的对象。其会带来回收效率问题,就是在有大量对象需要标记时会耗费更多的时间。还有一个就是在垃圾回收后会产生大量不连续的内存空间。
标记整理算法
根据老年代特点提出的一种垃圾对象回收算法。其标记对象与标记清除方式相同,只是后面回收垃圾对象时采用的是非垃圾对象向一端移动。然后从端点边界外开始回收未被标记的对象
垃圾收集器
jvm垃圾收集器
Serial 收集器
Serial垃圾收集器是单线程垃圾收集器,在执行GC的时候会暂停用户线程(STW),专心执行垃圾回收操作,其年轻代回收采用的标记复制算法,老年的回收采用的标记整理算法。Serial Old收集器是Serial收集器的老年代版本,它同样是一个单线程收集器。它主要有两大用途:一种用途是在JDK1.5以及以前的版本中与Parallel Scavenge收集器搭配使用,另一种用途是作为CMS收集器的后备方
Parallel Scavenge收集器
Parallel 是Serial收集器的多线程版本,除回收垃圾采用多线程外,其他方式与Serial 收集器类似。其默认采用的回收线程数与cpu 核心数相等。该值一般不需要修改。其关注点在于吞吐量(高效使用CPU),吞吐量时指用户线程使用CPU的时间占整个CPU使用时间的比值。新生代采用标记复制算法,老年代采用标记整理算法。Parallel Old收集器是Parallel Scavenge收集器的老年代版本。使用多线程和“标记-整理”算法。在注重吞吐量以及CPU资源的场合,都可以优先考虑 Parallel Scavenge收集器和Parallel Old收集器(JDK8默认的新生代和老年代收集器)。
ParNew收集器
ParNew收集器和parallel收集器类似,其区别在于ParNew收集器可以和CMS收集器配置使用。新生代采用标记复制算法,老年代采用标记整理算法。它是许多运行在Server模式下的虚拟机的首要选择,除了Serial收集器外,只有它能与CMS收集器(真正意义上的并发收 集器,后面会介绍到)配合工作。
CMS收集器
CMS(Concurrent Mark Sweep)收集器是一种获取最短用户线程停顿时间为目标的收集器。
它是hotStop 虚拟机第一个真正意义上的并发收集器。它第一次实现了用户线程和垃圾回收线程同步工作(基本)。
CMS 是一个基于标记清楚算法的垃圾收集器,设置jvm 参数开启XX:+UseCMSCompactAtFullCollection,
实现标记整理以减少标记清除带来的大量内存碎片问题。
它是hotStop 虚拟机第一个真正意义上的并发收集器。它第一次实现了用户线程和垃圾回收线程同步工作(基本)。
CMS 是一个基于标记清楚算法的垃圾收集器,设置jvm 参数开启XX:+UseCMSCompactAtFullCollection,
实现标记整理以减少标记清除带来的大量内存碎片问题。
CMS垃圾收集器回收过程
初始标记
该过程在执行GcRoot时,会先标记GcRoot的直接引用。该过程也会暂停用户线程,但它的标记速度相当快。
并发标记
该过程不会停止用户线程,而是标记线程和用户线程同时执行。并发标记阶段就是从GCROOT直接关联的对象开始遍历整个对象图。这个过程耗时长,但是由于其是与用户线程并发执行,所以用户很难感知到该过程。在整个并发标记的过程中也正因为用户线程还在执行会导致被标记的对象发生改变的问题。
重新标记
重新标记是解决并发标记过程中产生的对象状态发生改变问题。该过程需要暂停用户线程,时间比初始标记长,但远比并发标记时间短。其采用三色标记法和增量更新算法
并发清理
开启用户线程,同时GC线程对未标记的对象进行统一清理,这个阶段由用户线程产生的对象直接标记为黑色对象,不做任何处理。
并发重置
重置GC 过程中标记的对象。
CMS垃圾收集器缺点
CMS会跟用户线程抢占CPU资源导致吞吐量下降。
CMS在做并发标记和并发清理的过程中不会暂停用户线程,这就导致会出现浮动垃圾(标记了的垃圾对象),只能等到下次GC才能清理。
CMS采用标记清除算法实现的垃圾对象清理,就会出现大量不连续的内存碎片。可以采用设置jvm 参数让其在做完GC后进行标记整理
执行过程中的不确定性,会存在上一次垃圾回收还没执行完,然后垃圾回收又被触发的情况。例如在执行CMS回收过程中,触发了年轻代GC 而 s0 s1区域存不下,直接放到老年代区域,恰好老年代也放不下就再次触发GC。此时CMS 回收就会触发concurrentmode failure,导致直接暂停用户线程使用serial old 垃圾收集器来回收。
CMS并发标记问题
三色标记法 :GCROOT 可达性分析过程中按照是否访问过对象,
给予对象标记三种不同的颜色。
给予对象标记三种不同的颜色。
黑色 : GCROOT 完全访问过该对象和其内部引用的全部对象
灰色 : GCROOT 访问过该对象,但其引用的对象未被完全访问
白色:GCROOT 未访问过的对象
什么是漏标?
并发标记对象过程中,由于用户线程未停止,可能出现黑色对象引用白色对象,而黑色对象已经被GC ROOT 访问过,白色对象不会在被标记,导致CMS会将其作为垃圾对象回收
多标—浮动垃圾
在并发标记过程中,如果由于方法运行结束导致部分局部变量(gcroot)被销毁,这个gcroot引用的对象之前又被扫描过 (被标记为非垃圾对象),那么本轮GC不会回收这部分内存。这部分本应该回收但是没有回收到的内存,被称之为“浮动 垃圾”。浮动垃圾并不会影响垃圾回收的正确性,只是需要等到下一轮垃圾回收中才被清除。另外,针对并发标记(还有并发清理)开始后产生的新对象,通常的做法是直接全部当成黑色,本轮不会进行清除。这部分 对象期间可能也会变为垃圾,这也算是浮动垃圾的一部分。
CMS解决漏标问题
三色标记法,见上文
增量更新
当黑色对象加入新的白色对象引用时,将这个新的引用记录下来,在进行重新标记时,再已记录的黑色对象为根进行重新标记。也就是黑色对象加入新的引用时将黑色对象变为灰色对象
跨代引用问题—记忆集&卡表
垃圾回收器在回收垃圾的过程中,有可能会出现老年代引用年轻代的情况。而在做minorGC 时 就会出现找不到GCRoot的情况 ,导致垃圾回收出错。为了解决这种情况jvm 使用了 记忆集与卡表来解决该问题。
记忆集是一种数据结构卡表示记忆集的具体实现,老年代会分成512个字节大小的卡页,年轻代会维护一张卡表数组,其中用0和1 记录老年代卡页中是否有引用年轻代对象,一个卡页中可包含多个对象,只要有一个对象的字段存在跨代指针,其对应的卡表的元素标识就变成1,表示该元素变 脏,否则为0。年轻代在做GCroot时会将这些脏引用加入到年轻代GCRoot 进行扫描。
G1垃圾收集器
G1(Garbage-First)是一款面向服务器的垃圾回收器,主要用在多CPU和大内存服务的垃圾回收,以极高的效率满足垃圾回收暂停用户线程时间,同时满足高吞吐量
G1内存模型
G1 将JAVA 堆内存划分为很多个region区域,该区域最多有2048个。一般单个region区域大小为整个JAVA堆大小除以2048。例如 现在JAVA 堆大小为4G,那么 "region大小 = 4096M % 2048 = 2 M"。当然这个region大小也可以通过G1 jvm 参数-XX:G1HeapRegionSize进行制定,但是一般采用默认值。
G1在逻辑上保留了年轻代老年代的概念。但在实际堆内存中它们并不是连续的而是分成很多个region区域。
最开始年轻代默认是占整个堆内存的%5,例如整个堆的大小是4096M,那么年轻代大概占有200M空间。这个比例也可以通过jvm参数-XX:G1NewSizePercent进行修改,一般选择默认。G1收集器在会根据实际服务的运行情况动态的增大年轻代的空间大小。这个增加也是有限度的,默认是整个堆空间大小的60%,这个值也是可以通过jvm参数-XX:G1MaxNewSizePercent进行调整。
同样年轻代里Eden区域和s0、s1 区的大小默认也是8:1:1 也就是160m、20m、20m。还有一点,一个年轻代的region区域被回收后,可能之后这个区域被分配给了老年代。所有region区域的分代不是固定的,它在之后的服务运行期间有可能是G1 得任何一个分代阶段
G1 收集器和之前的垃圾收集器将对象转移到老年代的机制一样,有一点不同的是G1收集器将大对象转移到humongous区,而不是直接放到老年代的region区域。G1 对大对象的判断规则是一个对象超过region区域的50%,就会放入humongous区域。如果该对象太大可能横跨多个region区域存放。将大对象直接放入humongous区域可以节约老年代的区域,减少因为老年代空间不足带来的GC 性能开销。
G1垃圾回收过程
初始标记
暂停用户线程,GCRoot 只是扫描直接引用,不会进行深度扫描,该过程非常快。
并发标记
同CMS,用户线程与垃圾回收线程并行,GCroot对所有引用对象进行深度扫描。
最终标记
同CMS的重新标记,但这里使用的是原始快照的方式进行漏标问题解决。
筛选回收
该过程不同与CMS的并发清理,这个过程会暂停用户线程。根据设置的jvm参数-XX:MaxGCPauseMillis GC停顿时间(默认200ms),对需要回收的region区域进行筛选。优先回收那些回收价值较大的区域
什么是回收价值
例如region区域A存活对象有50个,区域B 存活对象只有1个,那么复制一个所用的时间当然比复制50个对象所用的时间要少。那么我们就说区域B的回收价值大于区域A的回收价值
筛选回收说明
G1的回收算法是复制算法,就说将一个存活对象复制到另一个区域,然后将之前的区域进行回收。这个过程不同与CMS的标记清楚算法,其产生的内存碎片就会很少。G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region(这也就是它的名字Garbage-First的由来),比如一个Region花200ms能回收10M垃圾,另外一个Region花50ms能回收20M垃圾,在回收时间有限情况下,G1当然会优先选择后面这个Region回收。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限时间内可以尽可能高的收集效率
G1 收集器的优点
并发与并行:在多个cpu环境下,G1充分利用多核条件,来减少用户线程的停顿时间。相比于其他一些需要暂停用户线程来执行垃圾的回收器,G1可以实现垃圾回收过程和用户线程并发执行。
分代收集:G1不需要其他收集器的配合,但它还是保留了分代收集概念。
G1 最底层采用的是复制算法,很少会产生内存碎片。
可预测的gc停顿:这是G1 相对于CMS的一个最大优点,G1 在做GC的过程中可以通过设置jvm参数-XX:MaxGCPauseMillis 来指定gc停顿时间。可以让用户根据时间服务运行情况设置该值,让GC停顿时间和吞吐量达到平衡。
G1垃圾收集分类
young GC : Eden区放满之后并不是马上触发,而是计算一次GC回收年轻代区域的时间与jvm通过 -XX:MaxGCPauseMills设置的回收时间进行比较,如果这个值远小于我们设置的值,那么G1 会增大年轻代区域大小,直到计算出GC回收时间接近我们设置的时间时才进行young GC。
Mixed GC : 不是full GC ,当老年代达到jvm 设置的 最大占比时,触发mixed GC ,这个过程会回收全部年轻代和部分老年代(会根据用户设置的回收时间判断对象回收优先级,并不是每次都是全部回收老年代对象)以及大对象区域(Humongous)。回收过程中采用复制算法将非垃圾对象拷贝到另一个未使用过的内存区域。如果未使用的区域内存不足,会触发一次fullGC 。
Full GC : 暂停用户线程,采用单线程标记整理回收垃圾对象。空闲出region区域提供下次mixedGC复制对象。该过程会非常的慢、耗时。
G1回收器使用场景
50%以上的堆被存活对象占用
对象分配和晋升的速度变化非常大
垃圾回收时间特别长,超过1秒
8GB以上的堆内存(建议值)
停顿时间是500ms以内
jvm调优工具
JMAP
jmap -histo pid 这个命令可以用来查看内存信息,类实例数、占用大小.
jmap -heap pid 堆内存信息
jmap -dump:live,format=b.file=heap.dump pid 生成堆dump 文件,打开jvisualvm ,装入生成的dump 文件。
内存溢出自动导出dump文件:设置jvm 参数 -Xms10M -Xmx10M -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=F:\jvm.dump
Jstack
jstatck pid 查找线程死锁
jstack找出占用cpu最高的线程堆栈信息
top 查找cpu占用高进程id
top -p pid 查出单个进程,输入大写 H 。显示出所以线程信息
jstack 64667 | grep -A 10 fc9c(线程id 十六进制)
Jinfo
jinfo -flags pid 查看java程序jvm等扩展参数
info -sysprops pid 查看java 程序系统参数
Jstat
jstat命令可以查看堆内存各部分的使用量,以及加载类的数量
jstat [-命令选项] [vmid] [间隔时间(毫秒)] [查询次数]注意:使用的jdk版本是jdk8
jstat -gc pid 间隔时间 查询次数 例: jstat -gc 2972 2000 10
第三方框架Arthas
Arthas官方文档十分详细,详见:https://alibaba.github.io/arthas
GC日志
通过jvm参数 生成 gc 日志
java -jar ‐Xloggc:./gc‐%t.log ‐XX:+PrintGCDetails ‐XX:+PrintGCDateStamps ‐XX:+PrintGCTimeStamps ‐XX:+PrintGCCause ‐XX:+UseGCLogFileRotation ‐XX:NumberOfGCLogFiles=10 ‐XX:GCLogFileSize=100Mxxx.jar
gc 日志分析工具进行日志分析
gceasy(https://gceasy.io),可以 上传gc文件,然后他会利用可视化的界面来展现GC情况
常量池
Class常量池
class常量词可以理解为Class文件中的资源仓库,Class 文件中包含 类版本 字段 方法 接口,等描述信息外。其中还有编译时期存在的字面量和符号引用。
字面量
由字母和数字组成的字符串或者数字常量。字面量只可以右值出现,所谓右值是指等号右边的值,如:int a=1 这里的a为左值,1为右值。在这个例子中1就是字面量。
符号引用
符号引用是编译原理中的概念,是相对于直接引用来说的。主要包括了三类常量
类和接口全限定名
字段名称和描述符
方法名称和描述符
运行时常量池
int a = 1, b就是字段名称,就是一种符号引用,还有Math类常量池里的 Lcom/tuling/jvm/Math 是类的全限定名, main和compute是方法名称,()是一种UTF8格式的描述符,这些都是符号引用。这些常量池现在是静态信息,当程序运行加载到内存后就变成了运行时常量池。对应的符号引用在这个过程中会被转换为加载到内存代码的地址信息。这个过程就是动态链接过程。例如,compute()这个符号引用在运行时就会被转变为compute()方法具体代码在内存中的 地址,主要通过对象头里的类型指针去转换直接引用。
字符串常量池
字符串在内存中分配和其他在内存中分配的对象一样,有很高的时间和空间代价。作为java中的基本类型,它会非常频繁的进行创建。为了解决这个问题jvm 引入了字符串常量池,相当于缓存区域。创建一个字符串时,先查询字符串常量池。如果常量池中存在则直接从字符串常量池中获取如果字符串常量池中不存在,则创建一个到常量池中。再返回字符串实例。
八种基本类型的包装类和对象池
java中基本类型的包装类的大部分都实现了常量池技术(严格来说应该叫对象池,在堆上),这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用对象池,也即对象不负 责创建和管理大于127的这些类的对象。因为一般这种比较小的数用到的概率相对较大。
0 条评论
下一页