-
Notifications
You must be signed in to change notification settings - Fork 0
/
java.txt
243 lines (201 loc) · 25.8 KB
/
java.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
synchronized的实现机制,以及锁升级过程
在java6之前,synchronized是通过操作系统的互斥锁(mutex_lock)原语实现,这样会导致每次都进行用户态和内核态的切换
在java6之后,synchronized通过偏向锁、轻量级锁、重量级锁控制,是一个逐渐升级的过程。在一个线程进入synchronized代码块时,会先检查该锁对象的对象头里面的标记
1、如果是无锁标记,则将该对象的对象头的锁标志置位01,并标识该对象所属的线程为自己,此时的对象锁为偏向锁。
2、如果是偏向锁,并且锁线程不是自己,则cas等待获取锁,此时的对象锁为轻量级锁
3、如果是轻量级锁,开始cas等待获取锁,如果在一定次数内获取失败,则升级为重量级锁,进入等待队列
volatile关键字是什么意思,底层是怎么实现的?
使用volatile修饰的变量具有线程可见性,即一个线程修改之后,其他线程均立即可见。
说到实现的话,得先说下操作系统的缓存机制,操作系统缓存分为cpu的寄存器、主存。每个线程在cpu中都有自己独立的寄存器,这个是线程隔离的,但是主存是共享的。一般情况下,一个线程操作变量分为三步:
1、从主存中读取变量的拷贝到自己的寄存器中
2、在寄存器中修改变量
3、将寄存器中的变量同步到主存中
但是寄存器到主存的数据同步是不是实时的,因此变量在多线程下存在竞争状态。在加了volatile修饰之后,jvm会将线程寄存器中的该变量实时同步到主存中,因此这样所有的线程都能实时的看到该变量的更新。
说说java中的线程池的几个参数的作用
1、corePoolSize: 核心线程队列大小,当该队列没有满时,对于新来的请求,会立即新开一个线程进行处理
2、maximumPoolSize:最大的线程队列大小,当核心线程队列和阻塞队列均满的时候,对新来的请求,会从阻塞队列中取出一个请求新开一个线程进行处理,然后将新请求放入阻塞队列中;如果线程达到最大的线程队列大小,则直接拒绝请求
3、keepAliveTime: 空间线程存活时间
4、BlockingQueue:当核心线程池满了的时候,对于新来的请求,会优先放入该队列
5、RejectedExecutionHandler:当执行中的线程达到最大线程队列大小的时候,对于新来的请求则执行该拒绝策略,默认提供了4种类型的策略
说一说访问权限
java中有四种 访问权限
1、private:同一个类可访问
2、default:同一个类和同一个包可访问
3、protected:同一个类、同一个包、不同包的子类可访问
4、public:任何地方可访问
说一说ReentrantLock
ReentrantLock是一个可重入锁,同时也是一个独占锁,它具有跟synchronized同样的锁机制和内存语义。它额外提供了定时锁、中断锁、锁投票更功能,分为公平锁和非公平锁
ReentrantLock通过一个FIFO的队列(同时也是一个双向链表)来管理等待该锁的线程,在公平锁的机制下,线程根据FIFO依次排队获取锁,而非公平锁的机制下,则是竞争获取锁
ReentrantLock中有一个实现了AQS()的内部类,所有的锁逻辑都是通过这个内部类来实现的,ReentrantLock只是提供了对外友好的接口。
内部通过state属性来记录锁定的状态,同时实现可重入。当一个线程执行的时候,会首先获取该状态,如果为0.表示没有线程持有该锁,然后会通过CAS操作系统原语去更新state,同时将锁线程更新为本线程;如果state不为0,表示已经有线程持有该锁,然后检查该线程是否为本线程,如果是,则更新state计数;如果不是,有两种情况,如果只是尝试获取锁,那么久直接退出,如果是直接锁定,则会将生成本线程的一个节点加入队列中,等待获取锁。
既然说了ReentrantLock,那再说说ReentrantReadWriteLock呢,以及他们的区别
ReentrantReadWriteLock使用两把锁来解决问题,一个读锁,一个写锁
线程进入读锁的前提条件:
1、没有其他线程的写锁,
2、没有写请求或者有写请求,但调用线程和持有锁的线程是同一个
线程进入写锁的前提条件:
1、没有其他线程的读锁
2、没有其他线程的写锁
具有如下的特性:
1、重入方面其内部的WriteLock可以获取ReadLock,但是反过来ReadLock想要获得WriteLock则不行。
2、WriteLock可以降级为ReadLock,顺序是:先获得WriteLock再获得ReadLock,然后释放WriteLock,这时候线程将保持Readlock的持有。反过来ReadLock想要升级为WriteLock则不可能
3、ReadLock可以被多个线程持有并且在作用时排斥任何的WriteLock,而WriteLock则是完全的互斥。这一特性最为重要,因为对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则可以提高并发量。
4、不管是ReadLock还是WriteLock都支持Interrupt,语义与ReentrantLock一致。
5、WriteLock支持Condition并且与ReentrantLock语义一致,而ReadLock则不能使用Condition,否则抛出UnsupportedOperationException异常。
知道java的线程间协作吗?
java的线程间协作有两种方式
1、notify/wait/notifyall: 因为java中的对象都有一个内置的锁对象,因为java对每个对象也都提供了这三个原语来操作这个锁对象。
2、使用Condition:condition提供了await/signal/signalall方法,分别对应object的那三个方法
说一说java中的同步阻塞队列(LinkedBlockingQueue)
LinkedBlockingQueue是一个单链表形式的多线程同步阻塞队列,可以指定队列大小
使用了两个互斥锁putLock和takeLock来控制队列的竞争访问,当从队列中加入元素的时候进行putLock锁定,当从队列中取出元素的时候进行takeLock锁定。
使用了两个条件变量notEmpty和notFull来进行队列空和满的通知或阻塞,当take一个空队列的时候,使用notEmpty.wait等待,当put一个满队列的时候,使用notFull.wait等待;同时当队列由空变为不空时,使用notEmpty.signal通知等待线程,当队列由满变为不满时,使用notFull.signal通知等待线程
hashmap的实现机制
hashmap不保证输入有序,也不保证序号不随时间变化,有两个重要的参数,容量和负载因子,当hashmap的使用容量达到总容量*负载因子时,会扩容2倍
采用链式地址法进行重hash,也就是说当节点冲突的时候,会在该节点上形成链表,当链表节点数达到一定数量的时候(8)就转换为红黑树,当节点数减少到一定数量的时候就退化为链表
synchronized与reentrantlock区别
ReentrantLock是Lock的实现类,是一个互斥的同步器,在多线程高竞争条件下,ReentrantLock比synchronized有更加优异的性能表现。
Lock必须手动获取与释放锁,而synchronized不需要手动释放和开启锁
Lock只适用于代码块锁,而synchronized可用于修饰方法、代码块等
ReentrantLock的优势体现在:
具备尝试非阻塞地获取锁的特性:当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁
能被中断地获取锁的特性:与synchronized不同,获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放
超时获取锁的特性:在指定的时间范围内获取锁;如果截止时间到了仍然无法获取锁,则返回
对象在内存中的状态
可达状态:当一个对象被创建后,若有一个以上的引用变量引用它,则这个对象在程序中处于可达状态。
可恢复状态:如果程序中某个对象不再有任何引用变量引用它,它就进入了可恢复状态。
不可达状态:当对象与所有引用变量的关联都被切断,且系统已经调用所有对象的finalize()方法后依然没有使对象变成可达状态,那么这个对象将永久的失去引用,最后变成不可达状态。
垃圾回收机制具有的特征
垃圾回收机制只负责回收堆内存的对象,不会回收任何物理资源(例如数据库连接、网络IO等资源)
程序无法精确控制垃圾回收的运行,垃圾回收会在合适的时候进行。当对象永久地失去引用后,系统就会在合适的时候回收它所占的内存
在垃圾回收机制回收任何对象之前,总会先调用它的finalize()方法,该方法可能使该对象重新复活(让一个引用变量重新引用该对象),从而导致垃圾回收机制取消回收。
强制触发垃圾回收方法
调用System类的gc()静态方法:System.gc();
调用Runtime对象的gc()实例方法:Runtime.getRuntime().gc();
Java的反射机制允许我们动态的调用某个对象的方法/构造函数、获取某个对象的属性等,而无需在编码时确定调用的对象。这种机制在我们常用的框架中也非常常见。
反射的原理之一其实就是动态的生成类似于上述的字节码,加载到jvm中运行。
java中反射的是怎么实现的?
首先这得从两个方面说。一个是jvm的层面,一个是java应用层的层面。
应用层层面:用传统的OOP思想来说,任何一个你写好的且编译过的生成的Class文件,在被类加载器加载后,都会对应有一个java.lang.Class<T> 这个类的实例。
所以说,每个类的自有的方法属性(类结构)自然被包含在了这个对应的实例上,因此就可以获取到。
java序列化号的作用 Java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。
1)在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID。
2)当你序列化了一个类实例后,希望更改一个字段或添加一个字段,不设置serialVersionUID,所做的任何更改都将导致无法反序化旧有实例,并在反序列化时抛出一个异常。如果你添加了serialVersionUID,在反序列旧有实例时,新添加或更改的字段值将设为初始化值(对象为null,基本类型为相应的初始默认值),字段被删除将不设置。
synchronized有哪几种锁对象
1. 在对象上使用synchronized,锁对象就是该对象锁
2. 在普通成员方法上使用synchronized,锁对象就是类实例对象锁
3. 在静态成员方法上使用synchronized,锁对象就是类对象锁
什么时候会使用HashMap?他有什么特点?
是基于Map接口的实现,存储键值对时,它可以接收null的键值,是非同步的,HashMap存储着Entry(hash, key, value, next)对象。
你知道HashMap的工作原理吗?
通过hash的方法,通过put和get存储和获取对象。存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。
你知道get和put的原理吗?equals()和hashCode()的都有什么作用?
通过对key的hashCode()进行hashing,并计算下标( n-1 & hash),从而获得buckets的位置。如果产生碰撞,则利用key.equals()方法去链表或树中去查找对应的节点
你知道hash的实现吗?为什么要这样实现?
在Java 1.8的实现中,是通过hashCode()的高16位异或低16位实现的:(h = k.hashCode()) ^ (h >>> 16),主要是从速度、功效、质量来考虑的,这么做可以在bucket的n比较小的时候,也能保证考虑到高低bit都参与到hash的计算中,同时不会有太大的开销。
如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?
如果超过了负载因子(默认0.75),则会重新resize一个原来长度两倍的HashMap,并且重新调用hash方法。
说说常见的垃圾回收算法
1、Mark-Sweep(标记-清除算法):
思想:标记清除算法分为两个阶段,标记阶段和清除阶段。标记阶段任务是标记出所有需要回收的对象,清除阶段就是清除被标记对象的空间。
优缺点:实现简单,容易产生内存碎片
2、Copying(复制清除算法):
思想:将可用内存划分为大小相等的两块,每次只使用其中的一块。当进行垃圾回收的时候了,把其中存活对象全部复制到另外一块中,然后把已使用的内存空间一次清空掉。
优缺点:不容易产生内存碎片;可用内存空间少;存活对象多的话,效率低下。
3、Mark-Compact(标记-整理算法):
思想:先标记存活对象,然后把存活对象向一边移动,然后清理掉端边界以外的内存。
优缺点:不容易产生内存碎片;内存利用率高;存活对象多并且分散的时候,移动次数多,效率低下
4、分代收集算法:(目前大部分JVM的垃圾收集器所采用的算法):
思想:把堆分成新生代和老年代。(永久代指的是方法区)
因为新生代每次垃圾回收都要回收大部分对象,所以新生代采用Copying算法。新生代里面分成一份较大的Eden空间和两份较小的Survivor空间。每次只使用Eden和其中一块Survivor空间,然后垃圾回收的时候,把存活对象放到未使用的Survivor(划分出from、to)空间中,清空Eden和刚才使用过的Survivor空间。
由于老年代每次只回收少量的对象,因此采用mark-compact算法。
在堆区外有一个永久代。对永久代的回收主要是无效的类和常量
有哪几种垃圾回收类型
Minor GC:从年轻代(包括Eden、Survivor区)回收内存。
Major GC:清理整个老年代,当eden区内存不足时触发。
Full GC:清理整个堆空间,包括年轻代和老年代。当老年代内存不足时触发
说说java对象的创建过程
1、虚拟机遇到一条new指令时,首先检查这个指令的参数能否在常量池中定位到一个类的符号引用,并检查这个符号引用代表的类是否已经加载、连接和初始化。如果没有,就执行该类的加载过程。
2、为该对象分配内存。
A、假设Java堆是规整的,所有用过的内存放在一边,空闲的内存放在另外一边,中间放着一个指针作为分界点的指示器。那分配内存只是把指针向空闲空间那边挪动与对象大小相等的距离,这种分配称为“指针碰撞”
B、假设Java堆不是规整的,用过的内存和空闲的内存相互交错,那就没办法进行“指针碰撞”。虚拟机通过维护一个列表,记录哪些内存块是可用的,在分配的时候找出一块足够大的空间分配给对象实例,并更新表上的记录。这种分配方式称为“空闲列表“。
C、使用哪种分配方式由Java堆是否规整决定。Java堆是否规整由所采用的垃圾收集器是否带有压缩整理功能决定。
D、分配对象保证线程安全的做法:虚拟机使用CAS失败重试的方式保证更新操作的原子性。(实际上还有另外一种方案:每个线程在Java堆中预先分配一小块内存,称为本地线程分配缓冲,TLAB。哪个线程要分配内存,就在哪个线程的TLAB上分配,只有TLAB用完并分配新的TLAB时,才进行同步锁定。虚拟机是否使用TLAB,由-XX:+/-UseTLAB参数决定)
3、虚拟机为分配的内存空间初始化为零值(默认值)
4、虚拟机对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到对象的元数据信息、对象的Hash码、对象的GC分代年龄等信息。这些信息存放在对象的对象头中。
5、执行方法,把对象按照程序员的意愿进行初始化。
说说CMS垃圾收集器
一种以获取最短回收停顿时间为目标的收集器。
基于标记-清除算法的实现,不过更为复杂,整个过程为4个步骤:
A、初始标记:标记GC Root能直接引用的对象
B、并发标记:利用多线程对每个GC Root对象进行tracing搜索,在堆中查找其下所有能关联到的对象。
C、重新标记:为了修正并发标记期间,用户程序继续运作而导致标志产生变动的那一部分对象的标记记录。
D、并发清除:利用多个线程对标记的对象进行清除
由于耗时最长的并发标记和并发清除操作都是用户线程一起工作,所以总体来说,CMS的内存回收工作是和用户线程一起并发执行的。
缺点:
A、对CPU资源占用比较多。可能因为占用一部分CPU资源导致应用程序响应变慢。
B、CMS无法处理浮动垃圾。在并发清除阶段,用户程序继续运行,可能产生新的内存垃圾,这一部分垃圾出现在标记过程之后,因此,CMS无法清除。这部分垃圾称为“浮动垃圾“
C、需要预留一部分内存,在垃圾回收时,给用户程序使用。
D、基于标记-清除算法,容易产生大量内存碎片,导致full GC(full GC进行内存碎片的整理)
jvm有哪些优化参数
1、一般来说,当survivor区不够大或者占用量达到50%,就会把一些对象放到老年区。通过设置合理的eden区,survivor区及使用率,可以将年轻对象保存在年轻代,从而避免full GC,使用-Xmn设置年轻代的大小
2、对于占用内存比较多的大对象,一般会选择在老年代分配内存。如果在年轻代给大对象分配内存,年轻代内存不够了,就要在eden区移动大量对象到老年代,然后这些移动的对象可能很快消亡,因此导致full GC。通过设置参数:-XX:PetenureSizeThreshold=1000000,单位为B,标明对象大小超过1M时,在老年代(tenured)分配内存空间。
3、一般情况下,年轻对象放在eden区,当第一次GC后,如果对象还存活,放到survivor区,此后,每GC一次,年龄增加1,当对象的年龄达到阈值,就被放到tenured老年区。这个阈值可以同构-XX:MaxTenuringThreshold设置。如果想让对象留在年轻代,可以设置比较大的阈值。
4、设置最小堆和最大堆:-Xmx和-Xms稳定的堆大小堆垃圾回收是有利的,获得一个稳定的堆大小的方法是设置-Xms和-Xmx的值一样,即最大堆和最小堆一样,如果这样子设置,系统在运行时堆大小理论上是恒定的,稳定的堆空间可以减少GC次数,因此,很多服务端都会将这两个参数设置为一样的数值。稳定的堆大小虽然减少GC次数,但是增加每次GC的时间,因为每次GC要把堆的大小维持在一个区间内。
5、一个不稳定的堆并非毫无用处。在系统不需要使用大内存的时候,压缩堆空间,使得GC每次应对一个较小的堆空间,加快单次GC次数。基于这种考虑,JVM提供两个参数,用于压缩和扩展堆空间。
(1)-XX:MinHeapFreeRatio 参数用于设置堆空间的最小空闲比率。默认值是40,当堆空间的空闲内存比率小于40,JVM便会扩展堆空间
(2)-XX:MaxHeapFreeRatio 参数用于设置堆空间的最大空闲比率。默认值是70, 当堆空间的空闲内存比率大于70,JVM便会压缩堆空间。
(3)当-Xmx和-Xmx相等时,上面两个参数无效
6、通过增大吞吐量提高系统性能,可以通过设置并行垃圾回收收集器。
(1)-XX:+UseParallelGC:年轻代使用并行垃圾回收收集器。这是一个关注吞吐量的收集器,可以尽可能的减少垃圾回收时间。
(2)-XX:+UseParallelOldGC:设置老年代使用并行垃圾回收收集器。
7、尝试使用大的内存分页:使用大的内存分页增加CPU的内存寻址能力,从而系统的性能。-XX:+LargePageSizeInBytes 设置内存页的大小
8、使用非占用的垃圾收集器。-XX:+UseConcMarkSweepGC老年代使用CMS收集器降低停顿。
9、-XXSurvivorRatio=3,表示年轻代中的分配比率:survivor:eden = 2:3
jvm有哪些工具
jps(Java Process Status):输出JVM中运行的进程状态信息(现在一般使用jconsole)
jstack:查看java进程内线程的堆栈信息。
jmap:用于生成堆转存快照
jstat是JVM统计监测工具。可以用来显示垃圾回收信息、类加载信息、新生代统计信息等。
说说类的加载机制
查找并加载类的二进制数据(把class文件里面的信息加载到内存里面),把内存中类的二进制数据合并到虚拟机的运行时环境中
验证类的正确性。包括:
A、类文件的结构检查:检查是否满足Java类文件的固定格式
B、语义检查:确保类本身符合Java的语法规范
C、字节码验证:确保字节码流可以被Java虚拟机安全的执行。字节码流是操作码组成的序列。每一个操作码后面都会跟着一个或者多个操作数。字节码检查这个步骤会检查每一个操作码是否合法。
D、二进制兼容性验证:确保相互引用的类之间是协调一致的。
为类的静态变量分配内存,并将其初始化为默认值
如何判断对象是否要回收
采用可达性分析法:通过一系列“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的。不可达对象不一定会成为可回收对象。进入DEAD状态的线程还可以恢复,GC不会回收它的内存。(把一些对象当做root对象,JVM认为root对象是不可回收的,并且root对象引用的对象也是不可回收的)
哪些对象被认为是root对象
(1) 虚拟机栈(栈帧中本地变量表)中引用的对象
(2) 方法区中静态属性引用的对象
(3) 方法区中常量引用的对象
(4) 本地方法栈中Native方法引用的对象
对象被判定可被回收,需要经历几个阶段
(1) 第一个阶段是可达性分析,分析该对象是否可达
(2) 第二个阶段是当对象没有重写finalize()方法或者finalize()方法已经被调用过,虚拟机认为该对象不可以被救活,因此回收该对象。(finalize()方法在垃圾回收中的作用是,给该对象一次救活的机会)
finalize()方法的作用是什么
(1) GC垃圾回收要回收一个对象的时候,调用该对象的finalize()方法。然后在下一次垃圾回收的时候,才去回收这个对象的内存。
(2) 可以在该方法里面,指定一些对象在释放前必须执行的操作。
说说java的内存模型
1、 Java的并发采用“共享内存”模型,线程之间通过读写内存的公共状态进行通讯。多个线程之间是不能通过直接传递数据交互的,它们之间交互只能通过共享变量实现。
2、 主要目的是定义程序中各个变量的访问规则。
3、 Java内存模型规定所有变量都存储在主内存中,每个线程还有自己的工作内存。
(1) 线程的工作内存中保存了被该线程使用到的变量的拷贝(从主内存中拷贝过来),线程对变量的所有操作都必须在工作内存中执行,而不能直接访问主内存中的变量。
(2) 不同线程之间无法直接访问对方工作内存的变量,线程间变量值的传递都要通过主内存来完成。
(3) 主内存主要对应Java堆中实例数据部分。工作内存对应于虚拟机栈中部分区域。
java运行时数据区
1、程序计数器:指向当前线程正在执行的字节码指令。线程私有的。
2、虚拟机栈:虚拟机栈是Java执行方法的内存模型。每个方法被执行的时候,都会创建一个栈帧,把栈帧压人栈,当方法正常返回或者抛出未捕获的异常时,栈帧就会出栈,线程私有的。
栈帧存储方法的相关信息,包含局部变量数表、返回值、操作数栈、动态链接
a、局部变量表:包含了方法执行过程中的所有变量。局部变量数组所需要的空间在编译期间完成分配,在方法运行期间不会改变局部变量数组的大小。
b、返回值:如果有返回值的话,压入调用者栈帧中的操作数栈中,并且把PC的值指向 方法调用指令 后面的一条指令地址。
c、操作数栈:操作变量的内存模型。操作数栈的最大深度在编译的时候已经确定(写入方法区code属性的max_stacks项中)。操作数栈的的元素可以是任意Java类型,包括long和double,32位数据占用栈空间为1,64位数据占用2。方法刚开始执行的时候,栈是空的,当方法执行过程中,各种字节码指令往栈中存取数据。
d、动态链接:每个栈帧都持有在运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态链接。
3、本地方法栈:调用本地native的内存模型。线程独享。
4、方法区:用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译后的代码等数据,线程共享的;
运行时常量池:
A、是方法区的一部分
B、存放编译期生成的各种字面量和符号引用
C、Class文件中除了存有类的版本、字段、方法、接口等描述信息,还有一项是常量池,存有这个类的 编译期生成的各种字面量和符号引用,这部分内容将在类加载后,存放到方法区的运行时常量池中。