一、JVM与系统
JVM是运行在操作系统之上的,他与硬件没有直接的交互。
二、JVM体系结构
1. Class Loader类加载器
负责加载class文件,class文件在文件开头有特定的文件标示,并且ClassLoader只负责class文件的加载,值与他是否可以允许,则由Execution Engine决定
classd到Class类的模板
2. Native Interface 本地接口
- java语言本身不能对操作系统底层进行访问和操作,但是可以通过JNI接口调用其他语言(C、C++)来实现对底层的访问。
3. Execution Engine执行引擎
- 负责解释命令,提交操作系统执行
4. Runtime data area(运行时数据区)
---------以下为运行时数据区-----------
1). Native Method Stack 本地方法栈
java在内存中专门开辟了一块区域处理标记为native的代码,他的具体做法是Native Method Stack中登记native方法,在Execution Engine执行时加载native libraies。
2). heap 堆
3)Method Area方法区
方法去是被所有线程共享,所有字段和方法字节码、以及一些特殊方法如构造函数,接口代码也在此定义。简单说,所有定义的方法的信息都保存在该区域,此区属于共享区间。用来保存装载的类的元结构信息。
静态变量+常量+类信息+运行时常量池存放在方法区
实例变量存在堆内存中
4)PC Register 程序计数器
每个线程都有一个程序计数器,就是一个指针,指向方法区中的方法字节码(下一个将要执行的指令代码),有执行引擎读取下一条指令,是一个非常小的内存空间,可以忽略不记
栈管运行,堆管存储
5)Java Stack 栈
栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。基本类型的变量、实例方法、引用类型变量都是在函数的栈内存中分配
注意:
栈管运行,堆管存储
对象类型数据即是 Class,类模板信息。这样堆中的实例属性字段才是一样的。
三、栈(Stak)
栈也叫栈内存,主管Java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的。基本类型的变量、实例方法、引用类型变量都是在函数的栈内存中分配
3.1 栈存储什么
先进后出,后进先出即为栈
栈帧中主要保存3类数据
- 本地变量(Local Variables):输入参数和输出参数以及方法内的变量;
- 栈操作(Operand Stack):记录出栈、入栈的操作;
- 栈帧数据(Frame Data):包括类文件、方法等。
3.2 栈运行原理
栈中的数据都是以栈帧(Stack Frame)的格式存在,栈帧是一个内存去块,是一个数据集,是一个有关方法(Method)和运行期数据的数据集,
当一个方法A被调用时就产生一个栈帧F1,并被压入到栈中,
A方法调用了B方法,于是产生栈帧F2也被压入到栈,
B方法调用了C方法,于是产生栈帧F3也被压入到栈。。。
执行完毕后,先弹出F3,再弹出F2,再弹出F1.。。。
遵循“先进后出/后进先出”的原则。
图示在一个栈中有两个栈:
栈2是最先被调用的方法,先入栈,
然后方法2调用了方法1,栈帧1处于栈顶的位置,
栈帧2处于栈底,执行完毕后,依次弹出栈帧1和栈帧2,
线程结束,栈释放。
每执行一个方法都会产生一个栈帧,保存到栈(后进先出)的顶部,顶部栈就是当前的方法,该方法执行完毕后会自动将此栈帧出栈。
四、堆(Heap)
4.1 堆内存
新生区:伊甸区 s0 s1
老年代
4.2 新生区
新生区是类的诞生、成长、消亡的区域,一个类再这里产生,应用,最后被垃圾回收器收集,结束生命。
新生去又分两部分:伊甸区(Eden Space)和幸存者区(Survivor Space),所有的类都是再伊甸区被new出来。幸存区2个:0区和1区。
4.3 养老区
养老区用于保存从新生区筛选出来的JAVA对象,一般池对象都在这个区域活跃。
5、 永久区
永久存储区是一个常驻内存区域,用于存放JDK滋生所携带的Class,Interface的元数据,也就是说它存储的是运行环境必须的类信息,被装载进此区域的数据是不会被垃圾回收器回收掉的,关闭JVM才会释放此区域所占用的内存。
5.1定义
逻辑上是堆的一部分,但是jvm厂商实现不一定遵从
5.2结构
idk1.6
因为串池用的很频繁,但是只有老年代不足时才会触发FULLGC,这样的频率不高,容易造成溢出,所以将它转移到了堆内存中
5.3运行时常量池
1)常量池,就是一张表,虚拟机指令根据这张常量表找到要执行的类名、方法名、参数类型、字面量等信息
2)运行时常量池,常量池是 *.class 文件中的,当该类被加载,它的常量池信息就会放入运行时常量池,并把里面的符号地址变为真实地址
上图中的#1、2等会变成运行时的真实地址
5.4 StringTable 特性(串池)
1)常量池中的字符串仅是符号,第一次用到时才变为对象
2)利用串池的机制,来避免重复创建字符串对象
3)字符串变量拼接的原理是 StringBuilder (1.8 new对象在堆里)
4)字符串常量拼接的原理是编译期优化
5)可以使用 intern 方法,主动将串池中还没有的字符串对象放入串池
6)1.8 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有则放入串池, 会把串池中的对象返回
1.6 将这个字符串对象尝试放入串池,如果有则并不会放入,如果没有会把此对象复制一份,放入串池, 会把串池中的对象返回
s3==s5!=s4
5.5 StringTable优化
因为StringTabl底层是hashtable,调整桶个数会影响入池的效率。桶太少,链表长发生hash碰撞概率大。效率慢。
调整 -XX:StringTableSize=桶个数(默认60000)
6、直接内存
6.1、定义
常见于 NIO 操作时,用于数据缓冲区
分配回收成本较高,但读写性能高
不受 JVM 内存回收管理
普通io和nio
nio在系统内存中开辟出直接内存,java可以直接访问,更快
6.2 分配和回收原理
使用了 Unsafe 对象完成直接内存的分配回收,并且回收需要主动调用 freeMemory 方法
ByteBuffer 的实现类内部,使用了 Cleaner (虚引用)来监测 ByteBuffer 对象,一旦
ByteBuffer 对象被垃圾回收,那么就会由 ReferenceHandler 线程通过 Cleaner 的 clean 方法调
用 freeMemory 来释放直接内存