Skip to content

Latest commit

 

History

History
76 lines (65 loc) · 9.04 KB

[05]JVM.md

File metadata and controls

76 lines (65 loc) · 9.04 KB

# JVM内存模型

Java内存模型中规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,工作内存中保存了该线程使用到的变量的主内存中的共享变量的副本拷贝。 线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。 不同线程之间无法直接访问对方工作内存中的变量,线程间的通信一般有两种方式进行,一是通过消息传递,二是共享内存。Java 线程间的通信采用的是共享内存方式。

# JVM里面内存区域是怎么划分的?

  1. 程序计数器:如果线程执行的是非native方法,则程序计数器中保存的是当前需要执行的指令的地址;如果线程执行的是native方法,则程序计数器中的值是undefined;
  2. Java虚拟机栈:Java栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表(Local Variables)、操作数栈(Operand Stack)、指向当前方法所属的类的运行时常量池(运行时常量池的概念在方法区部分会谈到)的引用(Reference to runtime constant pool)、方法返回地址(Return Address)和一些额外的附加信息;
  3. 本地方法栈:本地方法栈与Java栈的作用和原理非常相似。区别只不过是Java栈是为执行Java方法服务的,而本地方法栈则是为执行本地方法(Native Method)服务的;
  4. 堆:用来存储对象本身的以及数组;
  5. 方法区:存储了每个类的信息(包括类加载器、类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等;
  6. 直接内存(堆外内存):主要是指 java.nio.DirectByteBuffer 在创建的时候分配内存;

# 本地方法栈是干嘛的,为什么要有它?

栈中存放的是一个个的栈帧,每个栈帧对应一个被调用的方法,在栈帧中包括局部变量表、操作数栈、指向当前方法所属的类的运行时常量池的引用、方法返回地址和一些额外的附加信息。

# 对方法区和永久区的理解以及它们之间的关系?

永久代是 HotSpot 的概念,方法区是 Java 虚拟机规范中的定义,是一种规范,而永久代是一种实现,一个是标准一个是实现。

# Java GC实现机制?JVM里GC回收对象的过程是什么样的?

分代收集

  • 年轻代Minor GC:使用“Stop-Copy”算法进行清理,对象都会首先在 Eden 区域分配,在一次新生代垃圾回收后,如果对象还存活,则会进入 Survivor(“To”),并且对象的年龄还会加 1(Eden 区->Survivor 区后对象的初始年龄变为 1),当它的年龄增加到一定程度(默认为 15 岁),就会被晋升到老年代中。对象晋升到老年代的年龄阈值,可以通过参数 -XX:MaxTenuringThreshold 来设置。经过这次GC后,Eden区和Survivor(“From")区已经被清空。这个时候,“From"和"To"会交换他们的角色,也就是新的"To"就是上次GC前的“From”,新的"From"就是上次GC前的"To”。不管怎样,都会保证 Survivor(“To”) 区域是空的。Minor GC会一直重复这样的过程,直到“To”区被填满,"To"区被填满之后,会将所有对象移动到年老代中。
  • 老年代Full GC:标记-整理算法,即:标记出仍然存活的对象(存在引用的),将所有存活的对象向一端移动,以保证内存的连续。
    • 调用System.gc时,系统建议执行Full GC,但是不必然执行
    • 老年代空间不足
    • 方法区(1.8之后改为元空间)空间不足
    • 创建大对象,比如数组,通过Minor GC后,进入老年代的平均大小大于老年代的可用内存
    • 由Eden区、From Space区向To Space区复制时,对象大小大于To Space可用内存,则把该对象转存到老年代,且老年代的可用内存小于该对象大小。
  • 永久代GC:常量池中的常量,无用的类信息。常量的回收很简单,没有引用了就可以被回收。对于无用的类进行回收,必须保证3点:1.类的所有实例都已经被回收;2. 加载类的ClassLoader已经被回收;3. 类对象的Class对象没有被引用,也没有通过反射引用该类的地方;

# GC Roots有哪些?

  • 虚拟机栈中引用的对象;
  • 方法区中类静态属性引用的对象;
  • 方法区中常量引用的对象;
  • 本地方法栈JNI引用的对象;

# 如何判断一个对象是否存活?

所有实例都没有活动线程访问,没有被其他任何实例访问的引用实例。

  • 引用计数算法:给对象添加一个引用计数器,每当有一个地方引用它时,计数器值就加1;当引用失效时,计数器值就减1;任何时刻计数器为0的对象就是不可能再被使用的,这就是引用计数算法的核心。客观来讲,引用计数算法实现简单,判定效率也很高,在大部分情况下都是一个不错的算法。但是Java虚拟机并没有采用这个算法来判断何种对象为死亡对象,因为它很难解决对象之间相互循环引用的问题。
  • 可达性分析算法:通过一系列“GC Roots"作为起始点,从这些结点开始向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是不可用的。

# CMS和G1的垃圾回收机制?

  • CMS:只会回收老年代和永久代,不会收集年轻代;年轻带只能配合Parallel New或Serial回收器; CMS是一种预处理垃圾回收器,它不能等到老年代或者永久代内存用尽时回收,需要在内存用尽前,完成回收操作,否则会导致并发回收失败;所以CMS垃圾回收器开始执行回收操作,有一个触发阈值,默认是老年代或永久带达到92%;

    1. 初始标记(CMS-initial-mark) ,会导致STW;该阶段进行可达性分析,标记GC ROOT能直接关联到的对象;
    2. 并发标记(CMS-concurrent-mark),与用户线程同时运行;该阶段进行GC ROOT TRACING,在第一个阶段被暂停的线程重新开始运行;
    3. 并发预处理;此阶段标记从新生代晋升的对象、新分配到老年代的对象以及在并发阶段被修改了的对象;
    4. 重新标记(CMS-remark) ,会导致STW;收集器线程扫描在CMS堆中剩余的对象。扫描从"跟对象"开始向下追溯,并处理对象关联;
    5. 并发清除(CMS-concurrent-sweep);清理垃圾对象,这个阶段收集器线程和应用程序线程并发执行;
    6. 并发重设状态;重置CMS收集器的数据结构,等待下一次垃圾回收;
  • G1:G1是一个分代的,增量的,并行与并发的标记-复制垃圾回收器。G1垃圾回收器是compacting的,因此其回收得到的空间是连续的;G1将内存划分一个个固定大小的region,每个region可以是年轻代、老年代的一个。内存的回收是以region作为基本单位的;

    1. Marking cycle phase:标记阶段,该阶段是不断循环进行的;
    2. Evacuation phase:该阶段是负责把一部分region的活对象拷贝到空Region里面去,然后回收原本的Region空间,该阶段是STW(stop-the-world)的;

# 为什么G1要去除物理分区?

  • 在物理上不需要连续,有的分区内垃圾对象特别多,有的分区内垃圾对象很少,G1会优先回收垃圾对象特别多的分区,这样可以花费较少的时间来回收这些分区的垃圾,这也就是G1名字的由来,即首先收集垃圾最多的分区。
  • 每个角色(eden、survivor、old、humongous)并不强制规定大小数量,可以动态变化。

# JVM 问题工具,jps,jinfo,jmap...

  • jps:JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程;
  • jstat:JVM Statistics Monitoring Tool,用于手机HotSpot虚拟机各方面的运行数据;
  • jinfo:Configuration info for java,显示虚拟机配置信息;
  • jmap:Memory Map for java,生成虚拟机的内存转储快照(heapdump);
  • jhat:JVM Heap Dump Browser,用于分析heapdump文件,它会建立一个Http/HTML服务器,让用户可以在浏览器上查看分析结果;
  • jstack:Stack Trace for java,显示虚拟机的线程快照;