一,初识jvm
jvm java virtual machine 顾名思义java虚拟机。
用于执行jvm规范的代码的。也是java跨平台的实现支撑。
二,jvm知识体系
内存结构是jvm核心内容。其他,垃圾回收,性能调优,JVM自身优化技术,执行引擎,
监控工具,类文件结构,类加载。都需要围绕内存结构。
二.一内存模型。
java运行时的数据区被java直接管理的区域叫运行时数据区,java将这个区域划分为了不同的区域。
没被java管理的部分是操作系统的直接内存必须操作系统支持才行(例如NIO中的DirectByteBuffer MappedByteBuffer ,EHcache,各种中间件都会大量使用,因为快。直接内存零拷贝,没有用户态内核态的频繁切换,也无数据的拷贝,所有快)。
注java文件零拷贝
class ZeroCopyFile {
public void copyFile(File src, File dest) {
try (FileChannel srcChannel = new FileInputStream(src).getChannel();
FileChannel destChannel = new FileInputStream(dest).getChannel()) {
srcChannel.transferTo(0, srcChannel.size(), destChannel);
} catch (IOException e) {
e.printStackTrace();
}
}
}
运行时数据区分为线程共享的区域及线程私有区域。
方法区(存放类的结构信息及方法,运行时常量池等信息(1.8之前实现方式是永久代,1.8实现方式是元空间)),堆(存放对象实例,或者大的对象的区域),是共享的
虚拟机栈(线程执行的栈),本地方法栈(执行native方法的栈),程序计数器(存储程序下次执行的指令地址。唯一不会oom的区域)是私有的
2.2 虚拟机栈的详细结构
虚拟机栈存储运行当前java线程所需的数据,指令及返回地址。
包含:1局部变量表(存储局部变量,包含入参和方法内部的局部变量,对象的引用)
2,操作数栈(java执行引擎直接操作的区域,如果java执行引擎类比成cpu,局部变量表就是内存,操作数栈就是cpu的高速缓存)
3,动态链接()
在一个class文件中,一个方法要调用其他方法,需要将这些方法的符号引用转化为其在内存地址中的直接引用,而符号引用存在于方法区中的运行时常量池。
Java虚拟机栈中,每个栈帧都包含一个指向运行时常量池中该栈所属方法的符号引用,持有这个引用的目的是为了支持方法调用过程中的动态连接(Dynamic Linking)。
这些符号引用一部分会在类加载阶段或者第一次使用时就直接转化为直接引用,这类转化称为静态解析。另一部分将在每次运行期间转化为直接引用,这类转化称为动态连接。
4,完成出口()
当一个方法开始执行时,可能有两种方式退出该方法:
正常完成出口
异常完成出口
正常完成出口是指方法正常完成并退出
一般来说,方法正常退出时,调用者的PC计数值可以作为返回地址,栈帧中可能保存此计数值。而方法异常退出时,返回地址是通过异常处理器表确定的,栈帧中一般不会保存此部分信息。
例如main方法调用 a方法,再调用b方法。main被a压栈,a执行完后,a的完成出口就是main里面调用a的那个位置。
可以使用 -Xss 来指定大小,默认是1m。他的大小于代码的嵌套层数相关,因为每次调用新的方法会压入新的栈帧。
虚拟机栈优化
压栈进来的方法,即被调用方法的局部变量表,会跟当前 被压栈的方法的操作数栈共享内存地址。
因为这部分变量是相同的,可以共享。
3.1JHSDB java Hotspot Debugger
可以根据jps获取进程信息。在JHSDB中查看内存使用状况。
启动在jdk lib 目录下执行 java -cp .\sa-jdi.jar sun.jvm.hotspot.HSDB
4,常量池与字符串
class常量池:存放类常量的东西。类被编译成class文件后,执行 javap -c xxx.class反汇编,就能看到这个类有哪些常量。
包含,字面量 :String a = "b"; int x = 3; 符号引用:Cat cat = new Cat()的cat就是符号引用,没有实际的值
运行时常量池:
运行时的各种常量,例如符号引用运行时会生成的直接引用如cat运行时时间的直接引用地址。
1.7后运行时常量池放在堆空间,逻辑上还是属于方法区。
5,类加载过程
加载
获取累得二进制流
转为方法区结构
再堆中生成对应的java.lang.Class对象
链接 (验证,准备,解析)
验证的目的:保证class流的格式正确:1,文件格式,是否以0xCAFEBABE开头,版本号是否合理。2,元数据验证,是否有父类,继承了final?非抽象的类实现了所有抽象方法?3,字节码验证(很复杂),运行检查,栈数据类型和操作码数据参数吻合,跳转指令到合理的位置。
准备:分配内存,为类设置初始化值。
内存分配的方法:指针碰撞,空闲内存表。
避免内存分配并发的方式:CAS方式 。TLAB thread local alocation buffer 线程本地分配缓冲。在Eden区。
初始化值,除了static final的会直接赋值,其他的都是给初始化值。
解析:将符号引用替换成直接引用。
初始化
执行clinit构造器,static 变量赋值,static{}语句执行。
子类的<clinit> 调用前先调用父类的<clinit> (<clinit>是线程安全的)
对象的内存分布结构
哈希吗是对象的hash
gc分代年龄 2进制4位,即最大1111 是15.
对齐填充,当对象实例不是8字节的倍数时,会存在填充。
对象的访问定位方式
句柄池方式:
优点,对象的实际地址在gc时会改变。不会影响到栈。
缺点,有句柄池代理,访问有中间对象。
直接指针方式:
优点:访问快,没有中间人
缺点:当对象直接地址发生改变会影响到栈空间。
垃圾回收
如何判断垃圾
引用计数法。问题:无法解决循环引用。 python就用的这个,需要额外代码解决循环引用的问题处理。
可达性分析:从GCroot找是否可达。
GCRoot:静态变量,线程栈变量,常量池,JNI指针,JVM内部引用(class对象,异常对象,系统类加载器),所有被synchronize同步锁持有的对象,JVM背部的JMXBean 、JVMTI注册的回调、本地代码缓存等,JVM实现中的临时性对象,跨代引用等。
class的回收需同时满足:1,所有实例被回收,2,加载该类的classloader被回收,3,该类对应的java.lang.class对象没有被任何地方引用,且无任何地方通过反射访问该类及该类的方法。
可以使用:-Xnoclassgc 关闭类回收。
System.gc()并不会马上执行。会先标记,后面什么时候执行需要等。销毁对象是,会调研finalize方法,可在该方法中重新引用以自救。
强软弱虚
强引用一直可达。软,引用在快发生OOM时,视为不可达。弱,每次GC时,引用不可达。虚,不知道什么时候一会儿就不可达了。
对象分配策略
是否栈上分配,得打开逃逸分析,如果该对象不可逃逸,即只在当前线程可用,且不大,则可栈上分配。