JVM内存模型
堆:
主要存存储对象实例和数组,属于线程共享
方法区:
常量池,类型信息(包名,类的修饰符,实现类等),域信息(成员变量信息,名称,类型,修饰符),方法信息(方法名称,返回值,参数等),属于线程共享
虚拟机栈:
由一个个栈帧组成,每个栈都会存储局部变量,程序运行的状态,返回值,例如a()方法里面调用一个b()方法,入栈顺序就a()->b(), 出栈就是b()->a(), 如果是a()调用b(),b()又调用a(),最终就是递归死循环,会把栈占满,最终抛出StackOverFlowError错误,属于线程私有
本地方法栈:
也是由栈帧组成,不过他是存储调用native的方法,属于线程私有
程序计数器:
又称为PC寄存器,用于存储即将要执行的指令代码,属于线程私有
对象创建过程
1. 类加载检查
当接收到new指令的时候,会去常量区检查是否有对应的这个类的符号引用(代表这个类中出现的类包,类,接口,字段,方法等),并检查这个类是否被已加载,解析,初始化过,若没有,就会走类加载的过程
2. 分配内存
java中分配内存是很频繁的,虚拟机为了保证线程安全,会采用以下两种方式:
1. CAS+重试,每次不加锁去操作,如果操作失败进入重试
2. TLAB, 线程本地存储缓冲,每个线程往Eden区中预分配一块小内存,如果当前对象大于TLAB剩余内存或者TLAB内存用完就会进入到CAS判断
指针碰撞法:
如果java堆分配工整,假如一半是已分配内存,一半是未分配内存,当为变量分配内存时会往分界线未分配内存区去占用内存,保证一边是已分配内存,一边是未分配内存
空闲列表法:
如果java堆内存分配不工整,虚拟机会预先准备一个未分配内存的空闲列表,当为变量分配内存时会重新去维护这个空闲列表
3. 初始化0值
分配内存后,会将给分配的内存空间设置为0值
4. 设置对象头
初始化0值后,会给对象设置头,比如这个是属于哪个类的实例,哈希码,GC分代年龄等
5. 执行init方法
执行init方法后,一个真正的对象才算被创建出来
类加载机制
加载顺序:加载(读取.class文件)-> 验证(验证class文件的完整性) -> 准备(为类分配内存和类变量赋初始值) -> 解析(虚拟机将符号引用替换成真实地址引用) -> 初始化(new出对象,取出对应的类) -> 卸载(类使用完成,准备GC)
3种类加载器:
1. BootstrapClassLoader,启动类加载器,主要加载bin下所有的jar或者参数额外指定的jar
2. ExtClassLoader, 扩展类加载器,主要加载jre/bin/ext下的jar
3. AppClassLoader, 应用类加载器, 主要加载应用写的主函数
回收顺序:
1. 新建的对象都会到Eden区
2. Eden区达到一定比例时就触发Minor GC回收,将剩下不能回收的对象转移到S0区
3. 当Eden区又存满了就会再次触发Minor GC,他会将S0中不能回收的对象转到S1,同时也会把Eden区不能回收的对象转到S1区,清空Eden和S0区
4. S区多次回收都没有清空的对象会转到老年区
5. 当老年区达到一定阈值的时候,会触发一次完整的回收(full GC)
双亲委派原则:
优先找父类加载器,如果父类都没有加载过,就会在再逐层往子类加载
回收
堆结构:
分为年轻区,老年区,S0,S1区,1.7为永久区,1.8后改为元空间,并且元空间使用的不是堆内存,而是使用本地内存
性能调优
根据机器配置设置堆的大小 -Xmx -Xms,以及元空间大小
内存泄露排查
1. 使用jdk自带bin目录的jvisualvm查看
2. jamp生成一份堆内存快照文件,然后用Mat工具分析
3. 如果当时不在场,也可以配置参数-XX:+HeapDumpOnOutOfMemoryError来自动dump
CPU 100%
1. top -c 查看cpu最高的进程,找到PID
2. top -Hp PID 找到使用最高的线程ID
3. printf "%c\n" 线程ID 将线程ID转为16进制
4. jstack PID | grep 线程ID16进制 -C 30 找到16进制文本前后分别30行