- 虚拟机
- 系统虚拟机
- 程序虚拟机
- Java虚拟机
- Java语言虚拟机:能够执行Java语言的高级语言虚拟机
- Java虚拟机:通过兼容性测试的Java语言虚拟机,虚拟机不一定要执行Java程序
- 业界三大商业虚拟机
- Oracle的Hotspot
- 热点代码探测技术:将Java程序中比较热门的代码直接编译成二进制文件,加快速度,即JIT技术
- Oracle的JRockit(速度最快)
- IBM的J9 VM(在IBM产品上速度最快)
- Oracle的Hotspot
- 其他虚拟机:谷歌,微软,淘宝
- Java虚拟机核心部分:
PC Register:pc寄存器
Method Area:方法区,1.7以前永久代,1.8以后元空间
Native Method Stack:本地方法栈
- 虚拟机设计理念:公有设计,私有实现
- 类加载器:负责动态加载Java类到Java虚拟机内存空间中
- 按需加载,第一次使用该类才加载
- Idea中 -verbose:class :显示类加载过程
- rt.jar runtime.jar 运行时jar包
- 类加载器分类
- 三个默认加载器(父子层级关系,并不是继承关系)
- 引导类加载器(Bootstrap):
- 由原生代码(c语言)编写
- 不继承ClassLoader
- 加载核心jar包
- 用getparent()获取不到
- 扩展类加载器(Extensions)
- 和系统类加载器拥有同一个父类
- 获取扩展类加载器
- 引导类加载器(Bootstrap):
- 三个默认加载器(父子层级关系,并不是继承关系)
-
-
-
- Apps类加载器(也称系统类加载器)
- 获取系统类加载器
- Apps类加载器(也称系统类加载器)
-
-
-
- 类加载器过程
-
-
- 加载
- 验证
- 准备
- 解析
- 初始化
-
- Java -jar 名称.jar cmd命令打开jar包
- 导入配置文件
- 方法一代表着绝对路径,因此根据文件位置不同。/代表着进去此级文件
-
- 方法二代表着相对路径,因此/代表在上级目录搜索
-
- 方法一代表着绝对路径,因此根据文件位置不同。/代表着进去此级文件
- 程序计数器:每个线程私有一份。也称钩子,用来指示代码执行
- 一个完整的栈帧包括局部变量表,操作数栈,动态连接,方法返回地址,附加信息(异常信息)五部分
- 局部变量表一个槽slot最大存32位,除了double和long两个槽,其他都一个
- Stack代表操作数栈的数量,locals代表局部变量表的数量,linenumbertable代表操作指令与Java程序行数关系,localVarialTable代表局部变量表
- Store以及load后边跟着都是代表相应槽数对应的变量
- 非静态的方法局部变量表有一个位置存的是this
- Load表示将数加载到操作数栈中
- 动态链接与静态链接(静态解析)
- 有些符号引用则是每次运行期间转化为直接引用,这种转换叫做动态链接.这体现为Java的多态性
- 有些符号引用是在类加载阶段或是第一次使用时就会转换为直接引用,这种转换叫做静态解析
- 虚方法和非虚方法
- 如果方法在编译期就确定了具体的调用版本,这个版本在运行时是不可变的,这样的方法称为非虚方法;静态方法、私有方法、final方法、实例构造器、父类方法都是非虚方法
-
- 不是非虚方法的方法,都是虚方法
- GC:Garbage Collection
- -XX:+PrintGc打印Gc信息或者Javap -verbose:gc
- -XX:+PrintGcdetails打印Gc详细信息
- Metaspace:元空间
- -XX:+PrintCommangLineFlags打印jvm参数详细信息
- Jps 可以查看线程id
- Jinfo -flag NewRatio 线程id:可以查看比例
- 对象包括:
-
- 对象头,
- 标记字:MarkWord(默认8字节)
- 经历Gc后的年龄
- 线程锁
- 哈希值
- 偏向线程id
- 偏向时间戳
- 类型指针:KlassPointer(默认4字节)
- 表示什么类型
- 如果是数组,还包括数组长度(默认4字节)
- 标记字:MarkWord(默认8字节)
- 实例数据:对象属性信息
- 对齐补白:必须是8的倍数个字节,不够则补
- 对象头,
- 类对象在堆里面
- 锁消除:-xx:EliminateLocks
- -xx:MaxTenuringThreshold:设置新生代垃圾的最大年龄,默认15
- 新生代垃圾回收:minior Gc
- 老年代垃圾回收:Major Gc
- 整区回收:Full Gc
- Java中Stop-The-World机制简称STW,是在执行垃圾收集算法时,Java应用程序的其他所有线程都被挂起(除了垃圾收集帮助器之外)。Java中一种全局暂停现象,全局停顿,所有Java代码停止,native代码可以执行,但不能与JVM交互;这些现象多半是由于gc引起。
- 堆是分配对象的唯一空间吗?
不是,原因如下:
-
-
-
- 栈上分配:如果对象不能逃逸出方法,可以进行栈上分配,从而不在堆上开辟空间,只要可能在方法外部调用,就认为发生了逃逸分析
- 注:Stringbuffer中tostring方法返回此序列中数据的字符串表示形式。分配一个新的 String 对象,并将它初始化,以包含当前由此对象表示的字符串序列。然后返回此 String。对此序列的后续更改不影响该 String 的内容,因此在需要返回string类型时可以利用tostring方法来避免逃逸,从而实现栈上分配
- 标量替换优化技术:有的对象可能不需要一个连续存在的内存结构也可以被访问到,那么对象的部分(全部)可以不存储在内存,而是存储在CPU寄存器中
- 标量是指一个无法再分解为更小的数据,Java原始数据类型就是标量
- 聚合量,还可以被拆解更小的数据的叫聚合量,比如对象
- 如果通过逃逸分析,发现一个对象不会被外界访问,那么经过JIT优化,就可以把这个对象拆解成若干个其中包含的若干个成员变量来替换,这个过程就是标量替换
- 然后经过标量替换,就可以把标量存储到栈帧的局部变量表中,从而大大减少了堆中空间的占用,因为不需要建对象了,为栈上分配提供了很好的基础
- 标量替换默认是开启的:-XX:+EliminateAllocations
- 其实就是将对象打散了存到栈上
- 淘宝jvm的GCIH技术,可以把生命周期较长的对象移动至对外,从而对此对象不考虑gc回收,达到提高GC回收效率和降低GC回收频率
- 栈上分配:如果对象不能逃逸出方法,可以进行栈上分配,从而不在堆上开辟空间,只要可能在方法外部调用,就认为发生了逃逸分析
-
-
- 如何快速判断是否发生逃逸分析:就看new的对象实体是否有可能在外部被调用
- 类中方法返回对象
- 为成员属性赋值
- 引用成员变量的值
- 逃逸分析的结论:能在方法内定义的局部变量,就不要在方法外定义
- 使用逃逸分析可以对代码进行的优化
- 栈上分配
- 同步省略:如果一个对象只能从一个线程中获取,那么加锁可以省略,即为锁消除,好处就是提高并发性和性能,因为线程同步的代价非常高,降低性能和并发性
- 标量替换
- 但是Hotspot的jvm并没使用栈上分配,因为不能确定逃逸分析所消耗的性能一定高于所节约的性能,hotspot虚拟机因此采用的是标量替换这一技术
- 常量空间以及静态变量在JDK7之后都在堆上,并且官方的文档也表示对象都是在堆上,因此对象不一定只能通过堆来分配,但对象绝大多数甚至说全部时候都是存在于堆上
- 运行时数据区结构图
- 从运行时数据区线程共享与否的角度:
注:程序计数器无异常无GC
本地方法栈和虚拟机栈有异常,无GC
- 栈,堆,方法区的交互关系
- 方法区:
- 方法区的基本理解:
- 可以把方法区看作接口,而永久代和元空间则是对方法区接口的不同实现
- 到了JDK1.8以后,采用jrockit一致,用元空间替代永久代
元空间和永久代本质区别:元空间不在虚拟机设置的内存中,而是使用本地内存,并且内部细节也有调整,如符号表被移到Native Heap中,字符串和静态常量被移动到Java堆中
- 方法区主要存储类型信息,常量,静态变量,即使编译器(JIT)编译后的代码缓存
- 类型信息:
-
- 成员变量(域信息):
-
- 方法信息:
-
- 加载进方法区的类会记录是谁加载的,加载器也会记录了加载过的所有类,他们相互记录
- 类型信息,域信息,方法信息,异常表都会加载到方法区
- 将class文件反编译并写入txt文件,再用editplus打开:
- 运行时常量池vs常量池
- 方法区内部包含的是运行时常量池,class文件内部包含的是常量池
字面量:双引号内部的
-
- 为什么需要符号引用:
因为类加载时需要加载父类信息,一段短代码可能需要加载上千类,因此将这些加载的信息放入常量池变为符号引用,这样能够节约空间,并且在动态编译的时候进行编译,这样节省空间并且能加快效率
-
- Class文件中常量池好比做菜时候的调料,而代码好比炒菜,常量池可以看作是一张表,虚拟机指令根据这张常量表要找到执行的类名,方法名,参数类型,字面量等类型
- 运行时常量池是方法区的一部分,在加载类和接口到虚拟机后,就会创建对应的运行时常量池
- 永久代为什么要被元空间替换???
- 为永久代设置空间大小难确定,在web工程中,如果动态记载类过多,要不断加载很多类,容易出现oom,而元空间大小仅受本地内存限制,出现oom少
- 对永久代调优难,即Full Gc难
- 为什么字符串常量要放到堆中??
因为永久代中回收效率很低,只有在full gc中才会触发,而full gc是老年代空间不足,永久代空间不足才会触发,这就导致字符串常量回收效率低,而开发中会有大量字符串创建,回收效率低,导致永久代空间不足,放到堆里,能及时回收内存
- 静态引用的对象实体始终都在堆空间中,从始至终,变得只是指向对象实体的变量的位置,而不是对象的实体
- 判断类型是否不再使用
判断对象对应的类是否加载,链接,初始化
分配内存空间:
内存规整,指针碰撞
内存不规整,空闲内存分配
说明
设置对象头
Init进行初始化
- New操作新建对象时,会进行如下三步操作
- 判断方法区是否加载方法,如果没加载就加载
- 在堆空间开辟一款存储区存储对象
- 对对象的所有属性赋初始值
- Dup的操作
- 栈空间里面操作数栈开辟空间
- 生成一个当前实体引用
- 在当前实体引用上面复制一份
- 原因,本体用来赋值操作,赋值的用来充当句柄进行调用操作
- New和dup操作又称临时初始化
句柄访问
优点:
发生移动的情况:s0s1区互相移动或者压缩算法
直接访问:
优点:1.访问效率高,一步到位2.节省空间