对象是否死亡

引用计数法:

  • 分配对象的时候,额外为对象分配一段空间,用于记录指向该对象的引用个数;
  • 如果有一个新引用指向改对象,则计数器加1;当一个引用不再指向该对象,则计数器减一;
  • 如果计数器的值为0,则该对象为垃圾对象;

问题:

无法回收循环引用对象:当两个对象互相引用时,由于它们互相引用导致对象的计数器都不为0,进而两个对象无法回收。

哪些对象可以作为GC Roots:

  • 虚拟机栈(栈帧中的本地变量表)中引用的对象
  • 本地方法栈(Native 方法)中引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 所有被同步锁持有的对象

引用

逐渐代替finalize()方法。

补充:

强引用:是指创建一个对象并把这个对象赋值给一个引用变量(new 出的对象、等号赋值);

如何消除强引用:

  • 将被引用的对象显式的置为null消除强引用;
  • 继承weakReference实现弱引用,防止强引用的内存泄漏;

java创建对象的过程

对象包括:

  • 对象头(
                   MarkWord :大小8个字节、存储对象自身的运行时数据(包含hashCode信息、synchronized锁信息(https://blog.nowcoder.net/n/a9446e3cb3d24a68bfe5d91de6a35ace)、GC垃圾回收标记
                   类型指针ClassPointer:指向类元数据的指针,确保是哪个类的实例。
        )
  • 实例数据:对象真正存储的有效信息;
  • 对齐填充:JVM的自动内存管理系统需要对象初始地址必须是8字节的整数倍;


创建对象的详细流程:

类加载检查:

  • 检查这个 指令的参数是否能在常量池中定位到这个类的符号引用,并且检查这个符号引用代表的类是否已被加载过、解析和初始化过。如果没有需要先进行类加载过程

分配内存:

  • 对象分配内存空间的任务等同于把一块确定大小的内存从Java堆中划分出来(对象所需要的内存大小在类加载完成后便可确定

初始化置零

  • JVM会将分配的内存空间都初始化为零值(不包括对象头)、保证了对象的实例字段能在不赋初始值就直接使用;

设置对象头

执行init方法

  • 此时对象的所有字段还都为零,需要按照需求进行初始化;


补充:

分配内存具体分为两种?

指针碰撞:用过的内存全部整合到一边,没有用过的内存放在另一边,中间有一个分界值指针,只需要向没用过的内存方向将该指针移动对象内存大小位置即可;

  1. 使用场景:没有内存碎片的情况下;
  2. GC收集器:Serial、ParNew;

空闲列表:虚拟机会维护一个列表,该列表中会记录那些内存块是可用的,在分配的时候,找一块儿足够大的内存块来划分对象实例,最后更新列表记录。

  1. 使用场景:内存不规整的情况下
  2. GC收集器:CMS;

创建对象的过程中如何保证线程安全:

  • TLAB(Thread Local Allocation Buffer):会在Eden区分配一块内存,JVM 给线程中的对象分配内存时,首先会在TLAB分配,TLAB的容量不够用的时候,会按照CAS进行内存分配;
每个线程在 Java堆中预先分配一小块内存,然后再给对象分配内存的时候,直接在自己这块 “ 私有 ” 内存中分配,当这部分区域用完之后,再分配新的 “ 私有 ”内存 。
这样每个线程都在自己的TLAB空间分配,不存在竞争的情况,可以大大提高分配效率。


JVM调优

常见指令:

  • jps:显示指令系统内所有的HotSpot虚拟机进程;
  • jstat:用于监视虚拟机各种运行状态信息的命令行工具;可以显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据;
  • jinfo:实时的查看和调整虚拟机的各项参数;
  • jmap:用于生成堆转储快照(heapdump、dump文件);
  • jhat:分析jmap生成的堆转储快照,jhat内置了一个微型的HTTP/HTML服务器,分析后可以在浏览器中查看
  • jstack:生成虚拟机当前时刻的线程快照(每条线程正在执行方法堆栈的集合)

调优过程:

  • 定位找出CPU高、内存高的进程;jps:列出所有的java进程、top:察看进程CPU、内存利用率。
  • 线上调优:
  1. 如果CPU高、可能是线程池使用不当、或者产生死锁;使用 jstack -l pid:导出具体的线程信息、寻找线程状态和waiting on <XX>字段。根据线程状态判断哪个线程持有<XX>锁,进而该线程有问题;
  2. 如果内存高,可能是OOM;使用 jmap -histo pid | head -20:可以查看占用字节最多的类,进而寻找问题。
  • 线下调优:如果服务器高可用、有备份,停掉此服务器对其他不影响
  1. 导出dump文件(可能会造成内存卡顿):jmap -dump:format=b, file = hshuo.bin pid(进程号);或者使用+HeapDumpOnOutOfMemoryError参数,可以让虚拟机在OOM异常出现之后自动生成dump文件。
  2. 分析dump文件:jhat hshuo.bin

查看GC回收:

监视垃圾回收线程、250毫秒查询一次pid的垃圾收集状况:jstat -gc pid 250

图形化(了解):主要用于测试、上线之前

jconsole、jvisualVM、jProfiler


JVM的线程模型

JVM线程与操作系统线程之间存在着某种映射关系,这两种不同维度的线程之间的规范和协议。

类型:

多对一 (java早起版本,后来被抛弃)

优点:
  • 提升并发量上限,大部分调度和同步操作都在用户空间内完成,减少状态切换,能够提升性能。
缺点:
  • 当一个用户线程进行内核调用并阻塞了,那么其他线程在这段时间里都无法进行内核调用。

一对一 (大部分主流JVM都采用这种线程模型)

优点:
  • 每个线程都是独立的调度单元,直接利用操作系统内核提供的调度功能。
缺点:
  • 用户线程的阻塞,会直接映射到内核线程上,容易引起频繁切换,降低性能。但是一些语言引入了CAS来避免一部分的内核调用,例如Java引入了AQS这种函数级别的锁,减少使用内核级别的锁,就能提升性能;
  • Linux内核能够创建的资源毕竟是有限的,所以这在一定程度上会限制并发量。

多对多

  • 多个用户线程对应一个轻量级进程;一个轻量级进程对应一个内核线程。例如:Go语言采用的GMP线程模型。