深入理解java虚拟机

这个文章是自己看书记录用于以后复习用的,不适用于大家学习,如果不小心搜索到,那现在可以直接关闭了

面试题收集:

1、你知道哪些或者你们线上使⽤什么GC策略?它有什么优势,适⽤于什么场景?
2、Java类加载器包括⼏种?它们之间的⽗⼦关系是怎么样的?双亲委派机制是什么意思?有什么好处?
3、如何⾃定义⼀个类加载器?你使⽤过哪些或者你在什么场景下需要⼀个⾃定义的类加载器吗?
4、堆内存设置的参数是什么?
5、Perm Space中保存什么数据?会引起OutOfMemory吗?
6、做GC时,⼀个对象在内存各个Space中被移动的顺序是什么?
7、你有没有遇到过OutOfMemory问题?你是怎么来处理这个问题的?处理 过程中有哪些收获?
8、JDK 1.8之后Perm Space有哪些变动? MetaSpace⼤⼩默认是⽆限的么? 还是你们会通过什么⽅式来指定⼤⼩?
9、jstack 是⼲什么的? jstat 呢?如果线上程序周期性地出现卡顿,你怀疑可 能是 GC 导致的,你会怎么来排查这个问题?线程⽇志⼀般你会看其中的什么 部分?
10、StackOverflow异常有没有遇到过?⼀般你猜测会在什么情况下被触发?如何指定⼀个线程的堆栈⼤⼩?⼀般你们写多少?

一 运行时数据区域

1.1.1 程序计数器

线程私有,用于记录当前线程运行到的位置,以便随时恢复线程

1.1.2 Java虚拟机栈

线程私有,包括栈帧,局部变量表,操作数栈,动态链接,方法出口
局部变量表保存8种基本类型的局部变量,以及引用类型的指针
栈帧对应着一个方法

1.1.3 本地方法栈

与Java虚拟机栈类似,但是是用于支持native方法的执行的,在hotspot虚拟机中,Java虚拟机栈与本地方法栈共用

1.1.4 Java 堆

所有线程共享,垃圾收集器的主要区域,可以分为新生代和老年代,新生代又分为Eden space, From survivor space , To survivor space。
从内存分配的角度看,可以分为多个线程私有的的分配缓冲区
通过-Xmx -Xms 控制大小

1.1.5 方法区

存放类信息,常量,静态变量,即时编译器编译后的代码等数据
--XX:MaxPermSize 指定上限
这一区域的垃圾回收目标主要是针对常量池的回收和类型的卸载

1.1.6 运行时常量池

这是方法区的一部分,用于存放编译期生成的各种字面量和符号引用

1.1.7 对象的创建

  • 检查new指令的参数是否能在常量池中定位到一个类的符号引用,并且检查类是否已经被加载
  • 为新生对象分配内存空间
    指针碰撞:java堆规整,Serial,ParNew等带Compact过程的收集器使用
    空闲列表:java堆不规整,需要维护列表,CMS等Mark-Sweep算法收集器使用
    如何保证内存分配的原子性:
    方法一:CAS+失败重试的方法
    方法二:使用-XX:+/-UseTABLE来指定使用本地线程分配缓冲

1.1.8 对象的内存布局

对象在内存中可以分为:对象头(Header)实例数据(Instance Data)和对齐填充(Padding)
对象头(储存对象自身的运行时数据):哈希码,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳,类型指针(有的虚拟机可能没有),如果是数据对象,还要记录长度
实例数据:默认分配策略会把同大小的分配到一起,父类的变量会出现在子类之前,但是子类中较窄的变量也可能会插入到父类的间隙中去。
对齐填充:HotSpot VM 要求对象的起始地址必须是8字节的整数倍,所有要填充

1.1.9 对象的访问定位

句柄访问:java堆中划分一块内存来作为句柄池,reference保存的是句柄池的地址,句柄池中包含对象的实例数据指针和对象的类型指针。缺点是多一次访问,优点是reference不用变。
直接指针访问:reference直接保存对象实例数据地址,对象头中包含元类型数据地址
HotSpot使用的是直接访问的方式

二 垃圾收集器与内存分配策略

2.1 对象已经死了吗

引用计数法
可达性分析算法:GC Roots 包括虚拟机栈中引用的对象、方法区中类静态属性引用的对象、方法区中常量引用的对象、本地方法栈中JNI引用的对象

2.2 引用类型

强引用:普通new Object()产生的引用
软引用:内存溢出时会回收
弱引用:下次GC时一定会回收
虚引用:无法通过虚引用来找到一个对象,这种引用只是想在对象回收时收到一个通知

2.3 finalize()方法

对象第一次可达性分析,如果没有与GC ROOTs相连,则会调用finalize方法,在finalize方法中可以自救,但是finalize方法只会被调用一次

2.4 回收方法区

方法区回收的效果不好,主要是回收废弃常量和无用的类
废弃常量:与堆中的对象类似,没有任何引用的对象
无用的类:必须要满足以下三个条件,才会被回收

  • 该类的实例已经全被回收
  • 加载该类的ClassLoader已经被回收
  • 该类的Class对象被回收

参数-Xnoclassgc 参数可以控制是否回收无用的类

2.5 垃圾回收算法

标记清除法(Mark-Sweep):算法分为标记-清除两个阶段

  • 效率不高
  • 容易产生碎片

复制算法:每次只用一块,用完时把对象复制到另一块中

  • 实际使用的内存只有原来的一半
  • HotSpot新生代Eden:Survivor = 8:1 需要老年代担保

标记整理算法(Mark-Compact): 先标记,然后把存活对象往一边移动

分代回收的思想:新生代使用复制算法,老年代使用标记-清理或者标记-整理算法

2.6 HotSpot 算法实现

2.6.1.Serial收集器

虚拟机在Client模式下新生代的默认收集器,单线程,采用复制算法

2.6.2 ParNew收集器

与Serial收集器一样,只是回收新生代时采用的是多线程
这是Server模式下新生代的首选收集器
-XX:SurvivorRatio:Eden区:两个Survivor区 的比值
-XX:ParallelGCThreads : 指定线程的数量
-XX:+UseParNewGC强制指定使用这种收集器

2.6.3 Parallel Scavenge 收集器

新生代收集器,与ParNew一样使用的是复制算法,多线程,但是这个收集器的目标是使垃圾收集的时间尽量短
-XX MaxGCPauseMillis:控制最大垃圾收集时间
-XX:GCTimeRatio:用来控制吞吐量
-XX:UseAdptiveSizePolicy:自动调用新生代大小,Eden和Survivor区的比例

2.6.4 Serial Old 收集器

Serial 收集器的老年代版本,单线程,使用标记整理算法

2.6.5 Parallel Old 收集器

Parallel Scavenge 收集器的老年代版本,多线程,标记-整理算法

2.6.5 CMS收集器

以最短停顿时间为目标,标记清除算法
步骤一:初始标记:从GC Roots 递归找到所有存活对象
步骤二:并发标记:GC Roots Tracing过程
步骤三:重新标记:修正并发标记期间的变动
步骤四:并发清除:清除对象
只有初始标记和重新标记会暂停用户线程
缺点:

  • 对CPU资源比较敏感、占用(CPU+3) / 4 的线程
  • 无法处理浮动垃圾(并发清理阶段用户线程新产生的垃圾),不能等到老年代几乎满了才运行(默认68%,-XX:CMSInitiatingOccupancyFraction设置阀值),可能会出现Concurrent Mode Failure、
  • 有碎片问题 -XX:+UseCMSCompactAtFullCollection设置FullGC后整理碎片,-XX:CMSFullGCsBeforeCompation设置多少次不整理后就会整理一次
2.6.6 对象分配策略
  • 优先在Eden区分配
  • 大对象直接进入老年代: -XX:PretenureSizeThreshold 参数用于设置直接进入老年代的阀值,这个参数只对Serial和ParNew收集器
  • 长期存活对象进入老年代:-XX: MaxTenuringThreshold参数设置进入老年代的对象年龄
  • 对象动态年龄判断:若则在某个年龄,Survivor空间中相同年龄所有对象大小总和大于Survivor一半,则年龄大于或等于该的年龄的对象全进入老年代

三 常用监控分析工具

  • jps:显示指定系统内所有的hotspot虚拟机进程
  • jstat:用于收集HotSpot虚拟机各方面的数据
  • jinfo:显示虚拟机配置信息
  • jmap:生成虚拟机内存转储快照文件
  • jhat:用于分析headmap文件
  • jstack:显示虚拟机线程快照
3.1 jps
  • -q:只输出虚拟机ID,忽略其他参数
  • -m:输出启动主类时传递给main的参数
  • -l:输出主类的全类名,如果执行的是jar包,输出jar包位置
  • -v:输出jvm参数

你可能感兴趣的:(深入理解java虚拟机)