JVM
jvm是java程序的运行环境(字节码)
一次编写, 到处运行是jvm的功劳
自动内存管理, 垃圾回收机制
jvm组成部分
程序计数器: 线程私有的(线程安全), 内存保存字节码的行号, 记录指令的地址
1
javap -v xxx.class
堆:属于线程共享区域, 主要用于保存对象实例, 数组等, 当堆中没有内存空间可以分配给实例, 也无法扩展时, 则抛出
OutOfMemoryError异常
Java8 内存结构
年轻代分成三部分: eden区和两个大小相同的survivor区, 根据jvm策略, 经过几次垃圾回收后, 依然存活在survivor区的对俩被移动到老年代区间
老年代: 主要保存生命周期长的对象, 一般是一些老对象
虚拟机栈:
每个线程在运行时需要的内存就是虚拟机栈(私有的, 线程安全的), 栈先进后出
每个栈由多个栈帧组成, 对应每次方法调用时所占用的内存
每个线程只能有一个活动栈帧, 对应当前执行的方法
垃圾回收主要只的是堆内存, 栈帧弹出后,内存会自动释放
栈内存越大越好吗?
未必, 默认栈内存1024kb
栈帧过大会导致线程数变少
方法内的局部变量是线程安全的吗?
如果方法内局部变量没有逃离方法的作用域范围, 它是线程安全的, 如果局部变量引用了对象, 并逃离了方法的作用范围, 需要考虑线程安全
什么情况会导致栈内存溢出
栈帧过多导致, 典型: 递归调用
栈帧过大导致内存溢出
堆和栈的区别:
栈内存一般用来存储方法调用和局部变量, 堆内存用来存储java对象和数组. 堆会GC回收, 栈不会
栈内存是线程私有的, 堆是线程共享的
内存溢出报错不同: 栈: stackOverFlowError, 堆: OutOfMemoryError
方法区
是线程共享去区域
主要存储类的信息, 运行时常量池
虚拟机启动时创建, 关闭虚拟机时释放
如果方法区中内存无法满足分配请求, 则会抛出OutOfMemoryError: Metaspace
常量池
可以看做是一张表, 虚拟机指令根据这张表找到要执行的类名, 方法名, 参数类型,字面量等信息
运行时常量池
常量池在*.class文件中, 当该类被加载, 他的常量池信息就会放到运行时常量池, 并把里面的符号地址替换成真实地址
直接内存: 不属于jvm内存, 不由jvm管理, 是操作系统内存,常见于NIO操作,用于数据缓冲区, 他的回收成本高, 但读写性能高
常规IO: 数据缓存了两次
NIO数据拷贝: 只有一份缓存, 所以效率比较高
类加载器
把字节码文件加载到jvm中, 好让jvm运行字节码
双亲委派机制
加载某一类时, 先委托上一级的类加载器, 如果类加载器也有上级, 则会继续向上委托, 如果该类委托上级没有被加载, 子类加载器会尝试加载该类
为什么使用双亲委派机制
1.避免某个类被重复加载, 当父类被加载后则无需重复加载, 保证唯一性
2.为了安全, 保存类库的api不会被修改
类装载的执行过程
加载:
验证: 验证类是否符合jvm规范, 安全性检查
文件格式,元数据,字节码检查
符号引用验证: 检查常量池中是否存在自己使用到的引用
准备: 为静态变量分配内存,并设置默认值. 静态常量(基本类型)值在该阶段已经确定, 静态常量-引用类型在初始化阶段设置值
解析: 把符号引用转换直接引用, 把符号引用替换成指针直接指向方法(地址)
初始化阶段: 对类的静态变量, 静态代码块执行初始化操作, clinit, 如果父类没有被初始化, 先初始化父类
使用: 调用静态成员, 或者使用new创建对象实例
垃圾回收
什么是垃圾
没有引用指向的对象就是垃圾
如何确定垃圾
引用计数法, 计算对象引用数量多少, 引用为0就是垃圾, 缺点: 解决不了循环引用
可达性分析算法(现在jvm采用的)
哪些对象作为GCROOT
虚拟机栈中引用的对象
方法区静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI引用的对象
垃圾回收算法
标记清除算法
1.根据可达性分析找出垃圾
2.对这些垃圾进行回收
优点: 速度快
速度: 内存碎片化严重, 数组可能用不了(数组要使用连续的内存)
标记整理算法(老年代经常使用)
回收完, 整理内存
复制算法(年轻代使用)
分成两个区域,每次使用一块, 回收完放复制到另一块内存, 清空另一块
缺点: 占用两块内存
分代收集算法
新创建的对象都会先放到eden区,
minorGC, mixedGC, FullGC
垃圾回收器分类
串行垃圾回收器
并行垃圾回收器
CMS(并发)垃圾回收器
G1垃圾回收器(在jdk9后默认使用)
Young Collection
强,软,弱,虚引用
强引用:
只有所有GCRoots对象都不通过强引用引用该对象时, 该对象才能被回收
1
User user = new User()
软引用
弱引用
虚引用
jvm实践
jvm参数设置
jar包, 在jar启动参数时设置
war包, 在tomcat的catalina.sh中修改java_ops
调优就是调整年轻代, 老年代, 元空间的内存空间大小及使用的垃圾回收类型
常用jvm调优参数
设置堆空间大小
虚拟机栈
jvm调优工具
命令工具
jps 当前运行java程序
jstack 查看线程堆栈信息
jmap 生成内存快照
jhat
jstat
可视化工具
jconsole
visualVM
java内存泄露排查
cpu飙高排查
使用top命令, 查看cpu占用率,找到PID
再查看是哪一个线程占用
再使用jstack pid
再从打印的信息中找出对应16进制的线程id的信息, 可以定位具体的代码行数