一、什么是JVM
1.JVM的定义
Java程序的运行环境,java二进制字节码的运行环境
2.JVM的好处
①一次编写,到处运行
②自动内存管理,垃圾回收功能
③数组下标越界检查
④多态
3.jvm,jre,jdk的比较
3.常见的JVM
主要学习的是HotSpot虚拟机
4.jvm的学习路线
①ClassLoader:java代码编译成二进制后,会经过类的加载器,这样才能加载到JVM运行
②Method Area:类是放在方法区的
③Heap:类的实例化对象放在堆区
④当类调用方法时,会用到虚拟机栈,程序计数器,本地方法栈
⑤方法的每行代码执行是在执行引擎的解释器逐行执行,频繁调用的方法热点代码在JIT即时编译器执行。GC垃圾回收对堆的不用对象进行回收
⑥需要和操作系统打交道的是本地方法接口。
二、内存结构
1.程序计数器
①定义
程序计数器(寄存器)记录下一条jvm指令的执行地址
②特点
线程私有的,不会存在内存溢出。
2.虚拟机栈
定义:
①每个线程运行需要的内存空间,称为虚拟机栈。
②每个栈由栈帧Frame组成,对应着每次调用方法(参数,局部变量,返回地址)时所占的内存
③每个线程只能有一个活动栈帧,对应着当前正在执行的方法
问题分析
①垃圾回收是否涉及栈内存?
不会。栈内存由栈帧组成,对应着每次调用方法时占用的内存。每次方法调用结束会自动弹出栈。
②栈内存分配越大越好吗?
不是。默认栈内存是1M,物理内存是一定的,所以栈内存越大,能够支持更多的递归调用。但是线程会变少。
③方法内的局部变量是否线程安全?
线程安全:方法内的局部变量且无返回值。
线程不安全:方法内的局部变量有返回值。方法内的参数。
栈内存溢出 java.lang.stackOverflowError
①栈帧过多(方法递归调用)
②栈帧过大
③第三方类库的操作
修改栈内存大小-Xss
栈溢出线程运行诊断
案例1:cpu占用过多怎么排查
①用top命令查看占用cpu最高的进程
②ps H -eo pid,tid,%cpu|grep 进程id 进一步定位哪个线程引起的cpu占用过高
③jstack 进程id 可以根据线程id找到有问题的线程
3.本地方法栈
在本地方法栈有带有native关键字的方法,作用是java调用本地的C或C++方法跟系统底层交互。
4.堆
Heap堆
通过new关键字,创建对象都会使用堆内存
堆的特点
①线程共享,堆中的对象需要考虑线程安全的问题
②有垃圾回收机制
堆内存溢出
大量的对象占据了堆内存的空间java.lang.OutOfMemoryError:java heap space导致堆内存空间溢出
使用-Xmx内存大小 修改堆内存
排查堆内存溢出的原因
①jps工具
查看当前系统有哪些java进程
②jmap工具
查看堆内存占用情况 jmap -heap 进程id
③jconsole工具
图像界面,多功能检测,连续监测
5.方法区
定义
①方法区线程共享的(堆也是)
②方法区在JVM启动创建且内存空间不连续,可以实现扩展
③方法区类似编译代码的存储区域。方法区是存放类的信息(成员变量,方法数据,成员方法和构造器的代码)
方法区组成
①在jdk1.6中:
方法区是概念的,用PermGen永久代实现方法区。存储类的信息,存储类的加载器,运行时常量池
②在jdk1.8中:
方法区是概念的,用Metaspace云空间实现方法区(使用系统内存,不由JVM管理内存,由操作系统管理)。存储类的信息,存储类的加载器,运行时常量池
方法区内存溢出 类加载过多
①1.8前:永久代内存溢出java.lang.OutOfMemoryError:PermGen
使用-XX:MaxPermSize=8m 指定永久代内存大小
②1.8后:元空间内存溢出 java.lang.OutOfMemoryError:Metaspace
使用-XX:MaxMetaspaceSize =8m 指定元空间内存大小
溢出的场景
Spring -生成大量的类
Mybatis
运行时常量池
①常量池
常量池就是一张常量表,虚拟机指令根据这张常量表找到要执行的类名,方法名,参数类型,字面量等信息
②运行时常量池
常量池是*.class文件,当类被加载,常量池信息就会放入运行时常量池,并把里面的符号地址改为真实地址。
5.1StringTable面试题
特性
①常量池的字符串只是符号,第一次用到时才变为对象
②利用字符串机制,避免创建重复的字符串对象
③字符串变量的拼接是StringBuilder
④字符串常量的拼接是编译器优化
⑤使用intern方法把字符串对象放入串池
常量池与字符串池的关系
①运行时常量池的信息,都会被加载到运行常量池中,这时的a b ab都是常量池的对象,还没有变为java字符串对象,什么时候执行到响应代码然后创建
②ldc #2 会把a符号变为“a”字符串对象,放入StringTable
③ldc #3 会把b符号变为“b”字符串对象,放入StringTable
④ldc #4 会把ab符号变为“ab”字符串对象,放入StringTable
⑤StringTable[a,b,ab] hashtable结构,不能扩容,不重复的
字符串变量拼接
问:s3==s4? False。
s3的ab在字符串常量池中,s4是变量拼接new String(“ab”)了一个对象在堆中。地址不一样是false
编译器优化
问:s3==s5? True
s3的ab在字符串常量池中。当s5进行字符串常量拼接时,会从常量池看是否存在,发现存在s5就指向了常量池的ab
intern_jdk1.8
s.intern();将这个字符串对象s尝试放入串池,如果字符串池有就返回ab地址,没有就放入返回对象s地址
分析:
intern_jdk1.6
s.intern();将这个字符串对象s尝试放入串池,如果字符串池有就返回ab地址,没有就放入返回ab新地址
以上的分析,常量池没有ab的时候,s2=s.intern()放入ab,返回的不是s的地址
面试题
5.2StringTable位置
①jvm内存结构1.6
永久代只有full GC情况下进行内存垃圾回收效率低,StringTable属于常量池的一部分
②jvm内存结构1.8
垃圾回收效率高,属于堆的一部分
5.3 StringTable能垃圾回收
在内存紧张的时候会发生垃圾回收
5.4 StringTable性能调优
①因为StringTable由HashTable实现的,增加HashTable桶的个数,减少字符串放入串池的时间
-XX:StringTableSize=xxxx
//最低为1009
②通过intern方法减少重复入池,保证相同的地址在StringTable只存储一份。
6.直接内存Direct Memory
定义
①常见于NIO操作时,用于数据缓冲区
②分配回收成本高,但读写性能高
③不受JVM内存回收管理
基本使用
Direct Memory直接内存是操作系统和java代码都可以访问的一块区域。磁盘文件读取的时候到直接内存,java代码也可以访问直接内存。
内存溢出
①直接内存也会导致内存溢出问题
②OutOfMemoryError:Direct buffer memory
释放原理
直接内存的回收不是通过JVM的垃圾回收来释放的,通过unsafe.freeMemory手动释放
直接内存分配和回收原理
①使用unsafe对象完成直接内存的分配回收,回收需要手动调用freeMemory方法
②ByteBuffer实现类内部,使用了Cleaner虚引用来监测ByteBuffer对象。一旦ByteBuffer对象被垃圾回收,那么会由ReferenceHandler守护线程通过Cleaner的clean方法调用freeMemory释放直接内存。