对象是否死亡
引用计数法:
- 分配对象的时候,额外为对象分配一段空间,用于记录指向该对象的引用个数;
- 如果有一个新引用指向改对象,则计数器加1;当一个引用不再指向该对象,则计数器减一;
- 如果计数器的值为0,则该对象为垃圾对象;
问题:
无法回收循环引用对象:当两个对象互相引用时,由于它们互相引用导致对象的计数器都不为0,进而两个对象无法回收。
哪些对象可以作为GC Roots:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 本地方法栈(Native 方法)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 所有被同步锁持有的对象
引用
逐渐代替finalize()方法。
补充:
强引用:是指创建一个对象并把这个对象赋值给一个引用变量(new 出的对象、等号赋值);如何消除强引用:
- 将被引用的对象显式的置为null消除强引用;
- 继承weakReference实现弱引用,防止强引用的内存泄漏;
java创建对象的过程
对象包括:
- 对象头(
类型指针ClassPointer:指向类元数据的指针,确保是哪个类的实例。
);
);
- 实例数据:对象真正存储的有效信息;
- 对齐填充:JVM的自动内存管理系统需要对象初始地址必须是8字节的整数倍;
创建对象的详细流程:
类加载检查:
- 检查这个 指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有需要先进行类加载过程
分配内存:
- 对象分配内存空间的任务等同于把一块确定大小的内存从Java堆中划分出来(对象所需要的内存大小在类加载完成后便可确定)
初始化置零
- JVM会将分配的内存空间都初始化为零值(不包括对象头)、保证了对象的实例字段能在不赋初始值就直接使用;
设置对象头
- 对象头包含(属于哪个类的实例、Mark Word)联系到可偏向锁等、参考链接:https://blog.nowcoder.net/n/a9446e3cb3d24a68bfe5d91de6a35ace
执行init方法
- 此时对象的所有字段还都为零,需要按照需求进行初始化;
补充:
分配内存具体分为两种?
指针碰撞:用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界值指针,只需要向没用过的内存方向将该指针移动对象内存大小位置即可;
- 使用场景:没有内存碎片的情况下;
- GC收集器:Serial、ParNew;
空闲列表:虚拟机会维护一个列表,该列表中会记录那些内存块是可用的,在分配的时候,找一块儿足够大的内存块来划分对象实例,最后更新列表记录。
- 使用场景:内存不规整的情况下
- GC收集器:CMS;
创建对象的过程中如何保证线程安全:
- TLAB(Thread Local Allocation Buffer):会在Eden区分配一块内存,JVM 给线程中的对象分配内存时,首先会在TLAB分配,TLAB的容量不够用的时候,会按照CAS进行内存分配;
每个线程在 Java堆中预先分配一小块内存,然后再给对象分配内存的时候,直接在自己这块 “ 私有 ” 内存中分配,当这部分区域用完之后,再分配新的 “ 私有 ”内存 。这样每个线程都在自己的TLAB空间分配,不存在竞争的情况,可以大大提高分配效率。
- CAS乐观锁。
JVM调优
常见指令:
- jps:显示指令系统内所有的HotSpot虚拟机进程;
- jstat:用于监视虚拟机各种运行状态信息的命令行工具;可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据;
- jinfo:实时的查看和调整虚拟机的各项参数;
- jmap:用于生成堆转储快照(heapdump、dump文件);
- jhat:分析jmap生成的堆转储快照,jhat内置了一个微型的HTTP/HTML服务器,分析后可以在浏览器中查看
- jstack:生成虚拟机当前时刻的线程快照(每条线程正在执行方法堆栈的集合)
调优过程:
- 定位找出CPU高、内存高的进程;jps:列出所有的java进程、top:察看进程CPU、内存利用率。
- 线上调优:
- 如果CPU高、可能是线程池使用不当、或者产生死锁;使用 jstack -l pid:导出具体的线程信息、寻找线程状态和waiting on <XX>字段。根据线程状态判断哪个线程持有<XX>锁,进而该线程有问题;
- 如果内存高,可能是OOM;使用 jmap -histo pid | head -20:可以查看占用字节最多的类,进而寻找问题。
- 线下调优:如果服务器高可用、有备份,停掉此服务器对其他不影响
- 导出dump文件(可能会造成内存卡顿):jmap -dump:format=b, file = hshuo.bin pid(进程号);或者使用+HeapDumpOnOutOfMemoryError参数,可以让虚拟机在OOM异常出现之后自动生成dump文件。
- 分析dump文件:jhat hshuo.bin
查看GC回收:
监视垃圾回收线程、250毫秒查询一次pid的垃圾收集状况:jstat -gc pid 250
图形化(了解):主要用于测试、上线之前
jconsole、jvisualVM、jProfiler
JVM的线程模型
JVM线程与操作系统线程之间存在着某种映射关系,这两种不同维度的线程之间的规范和协议。
类型:
多对一 (java早起版本,后来被抛弃)
优点:
- 提升并发量上限,大部分调度和同步操作都在用户空间内完成,减少状态切换,能够提升性能。
缺点:
- 当一个用户线程进行内核调用并阻塞了,那么其他线程在这段时间里都无法进行内核调用。
一对一 (大部分主流JVM都采用这种线程模型)
优点:
- 每个线程都是独立的调度单元,直接利用操作系统内核提供的调度功能。
缺点:
- 用户线程的阻塞,会直接映射到内核线程上,容易引起频繁切换,降低性能。但是一些语言引入了CAS来避免一部分的内核调用,例如Java引入了AQS这种函数级别的锁,减少使用内核级别的锁,就能提升性能;
- Linux内核能够创建的资源毕竟是有限的,所以这在一定程度上会限制并发量。
多对多
- 多个用户线程对应一个轻量级进程;一个轻量级进程对应一个内核线程。例如:Go语言采用的GMP线程模型。