JVM的位置
JVM是运行在操作系统上的虚拟机,存在于JRE当中
JVM的类型
JVM的体系结构



本地方法接口JNI
- JNI的作用
- 拓展java的使用,融合不同的编程语言为java所用
- 因为最初java诞生的时候,市面上全是C/C++,java要想立足,必须有能调用C/C++的方法
- 于是在内存中设置了本地方法栈,专门用来登记native方法
- 然后由JNI去调用本地方法库
- 凡是带了native关键字的方法
- 说明java的作用范围达不到了
- 会进入本地方法栈
- 执行引擎会调用本地方法接口JNI
- 去调用底层c语言的库
- 常见的本地方法
- 现在除了通过JNI,也有其他方法去调用其他语言的方法,比如说Socket
类加载器
ClassLoader
用于加载类
分类
- 虚拟机自带的加载器
- 启动类(根)加载器
- 扩展类加载器
- 应用程序加载器
除此之外,用户也能自定义类加载器,用来加载指定路径的class类
双亲委派机制
- 类加载器收到类加载的请求
- 将这个请求向上委托给父类加载器去完成
- 当前加载器检查是否能够加载当前这个类
- 能加载就结束,使用当前的加载器
- 否则通知子加载器进行加载
- 重复步骤3
- 若没有任何类加载器可以加载
双亲委派机制的作用:
- 沙箱隔离机制,安全,防止Java的核心API类被篡改
- 避免重复加载
类加载的过程
程序计数器
- 可以看作
- 当前线程所执行的字节码的行号指示器
- 指向下一个将要执行的指令代码的地址
- 如果是Java方法,记录的是虚拟机字节码指令的地址
- 如果是native方法,记录的是Undefined
- 由执行引擎来读取下一条指令
- 更确切地说
- 一个线程的执行
- 是通过字节码解释器改变当前线程的计数器的值
- 来获取下一条需要执行的字节码指令
- 在物理上是通过寄存器来使用的
- 不存在OOM
栈
- 线程运行需要的内存空间
- 虚拟机栈为java方法服务
- 本地方法栈为native方法服务
- 两者在作用上是非常相似的
- 下面主要描述虚拟机栈
栈中存储的是什么
- 栈帧(stack frame)是栈的元素
- 栈帧主要包含四个部分
- 局部变量表(local variable)
- 操作数栈(operand stack)
- 动态连接(dynamic linking)
- 方法出口

局部变量表
- 用于存储数据
- 存储的类型有两种
- 所需的内存空间在编译期间完成分配
- 变量槽(Variable Slot)
- 局部变量表的容量的最小单位
- 一个slot最大32位
- 对于64位的数据类型(long和double)会分配两个连续的slot
- java通过索引定位的方法使用局部变量表
- 从0开始
- 一个Slot占1位
- 非static方法第0个槽存储方法所属对象实例的引用

- slot复用
- 为了节省栈帧空间,slot是可以复用的
- 如果某个变量失效了
- 那么这个变量的slot就会交给其他变量使用
- 副作用(这一段存疑):
- 会影响系统的垃圾收集行为
- 当某个变量失效后,因为它的slot可能还会交给其他变量复用,所以它占用的slot就不会被回收
- 线程安全
- 当局部变量表中的引用逃离了线程的范围
- 也就是当一个引用可以被另一个线程拿到的时候
- 就变成线程不安全的了
操作数栈
- 一个栈
- 元素可以是任意的java数据类型
- 主要作用
- 栈帧中用于计算的临时数据存储区
- 举例
- public class OperandStack{
- public static int add(int a, int b){
- int c = a + b;
- return c;
- }
- public static void main(String[] args){
- add(100, 98);
- }
- }
复制代码

动态连接
指向运行时常量池中该栈帧所属方法的引用
返回地址
- 存放调用该方法的pc寄存器的值
- 正常退出时会使用
- 异常退出时会通过异常表来确认
可能出现的异常
- StackOverflowError
- 栈溢出错误
- 如果一个线程在计算时所需的栈大小>配置允许最大的栈大小
- 那么jvm将抛出该错误
- OutOfMemoryError
- 内存不足
- 栈进行动态扩展时如果无法申请到足够的内存
- 会抛出该错误
设置栈参数
jstack命令
- jstack是JVM自带的JAVA栈追踪工具
- 它用于打印出给定的java进程ID、core file、远程调试Java栈信息
- 常用命令:
- jstack [option] pid
- 选项
- -F强制输出
- -m显示本地方法的堆栈
- -l显示锁信息
- 使用案例
堆
被所有线程共享
主要存储
- new关键字创建的对象实例
- 静态变量
- string池(1.8之后)
GC就是在堆上收集对象所占用的内存空间
堆的空间结构

- 新创建的对象会存储在生成区
- 年轻代内存满之后,会触发Minor CG,清理年轻代内存
- 长期存活的对象和大对象会存储在老年代
- 当老年代内存满之后,会触发Full CG,清理全部内存
- 如果清理后仍然无法存储进新的对象
- 会抛出OutOfMemoryError
堆内存诊断
- jps工具
- jmap工具
- 查看堆内存占用情况
- jmap -heap pid
- jmap -dump:format=b,live,file=1.bin pid
- 将堆内存占用情况转储
- format=b:以二进制的形式
- live:抓取之前调用一次垃圾回收
- file=1.bin:将文件导出为1.bin
- jconsole工具
- jvisualvm
- 案例:调用垃圾回收后,占用的内存依然非常大
- 使用jvisualvm
- 查看对象个数
- 使用堆转储dump
方法区
被所有线程共享
主要存储
- 类信息
- 运行时的常量池
- 字面量
- final修饰的常量
- 基本数据类型的值
- 字符串(1.8之前)
- 符号引用
- 当类被加载时,.class中的常量池会被放进运行时常量池中
永久区
JDK1.7及之前,方法区的具体实现是PermSpace永久区
MetaSpace
JDK1.8后,使用MetaSpace元空间替代PermSpace
元空间不在JVM中,而是使用本地内存
有两个参数:
- MetaSpaceSize
- MaxMetaSpaceSize
使用常量池的优点
- 避免了频繁的创建和销毁对象而影响系统性能
- 实现了对象的共享
Integer常量池
- public void TestIntegerCache()
- {
- public static void main(String[] args)
- {
- Integer i1 = new Integer(66);
- Integer i2 = new Integer(66);
- Integer i3 = 66;
- Integer i4 = 66;
- Integer i5 = 150;
- Integer i6 = 150;
- System.out.println(i1 == i2);//false
- System.out.println(i3 == i4);//true
- System.out.println(i5 == i6);//false
- }
- }
复制代码 <ul>为什么i1 == i2为false
为什么i3 == i4为true
<ul>当Integer i3 = 66时,其实进行了一步装箱操作
通过Integer.valueOf()将66装箱成Integer
[code]public static Integer valueOf(int i) { if (i >= IntegerCache.low && i |