JVM 详解

1 jdk  和jre 的区别

    jre 石 Java 运行环境,只能运行 class 不能编辑 Java文件,不能dubug。 

 

2  jdk下面的  bin/jconsole.exe 监控 一些内存,线程,jvm 。

 

3 Java 的 层级 ,以前我们关注的是三面 三次。jvm 是最下面一层

  

 

 

4 Java的  作者是  詹姆斯·高斯林

  

  

5 Java 的前生叫做 OAK ,1995 年  OAK 改名Java 也就是 Java  ,并且提出一次编译到处运行。并且 1996 年 1月 发布 jdk 1.0

 

6 实现  jVM 规范的 JVM 有哪些 产品

  sum classic Vm          第一个商用 JVM, 只能使用纯解释器的方式来执行Java代码。

  Exact Vm  准确内存管理虚拟机 ,只在 Solaris 平台发布, 编译器和解释器可以混合工作了,在 linux 和windows 平台还没来的级发布就 被别JVM的锁取代。

  Sum Hotspot 全面替代前面2种JVm,目前还是主流。

  KVM  全称 kilobyte Vm ,使用与移动平台,简单,轻量,但是慢,在 安卓以前的  java 程序 有些用的就是这个。

  BEA(被oracle 收购了) JRockit 自称世界上最快的JVM,missionControl 服务套件 争端内存泄漏,并且开销非常小。

  IBM J9 主要是IBM的产品使用。

  Azul VM 在hotspot 上改的,自己专用,性能很高。可以管理 数十个CPU和数百G内存。

  Liquid Vm 本身就是一个操作系统,性能很高。不用通过操作系统去管理硬件资源。自己就可以调用硬件资源。

  Dalvik VM 安卓用的  虚拟机,它不完全是按照jvm 规范来的。它使用的是寄存器架构,而不是栈架构。不能直接执行 class。

  microsoft jvm 只能运行在 windows 平台,是当初 windows 平台最快的 jvm 。和sum打官司以后,就没了。

  TaobaoVm 淘宝的基于hotspot 的 JVm。

 

7 jvm 的 内存管理  

 

 

8 java 内存模型

  

 

 

 

9 程序计数器

  程序计数器是一小块内存空间,这个计数器记录的是正在执行的虚拟机字节码指令的地址。如果是native方法 这个计数器的值是 undefined。

  程序计数器位于 独占线程内存区中,并且 是唯一一个在jvm规范中没有规定任何outofmeeoryerror 情况 的区域。也就是说 除了 程序计数器区,别的都可能会抛出 outofmemry 异常。

 

10 虚拟机栈

  虚拟机栈描述的是java方法执行的动态内存模型。

  栈帧:每个方法的执行都会创建一个栈帧,伴随着方法从创建到执行完成。用于存储局部变量表。操作数栈,动态链接,方法出口等。

  局部变量表:存放基本类型变量局部变量和应用类型的指针。返回值类型。局部变量变在编译期完整分配。

  如果 栈的数量超过最大值,会抛出 StackOverFlowError 异常。

11 本地方法栈,为 java执行 native 方法 分配的区域,类似 虚拟机栈,在 hotspot vm 里面,这两块没有区分。

 

 

12 堆 存放 java对象。是java管理的最大的一筐聂村区域。也是垃圾回收主要管理的区域。

 

13 方法区 

  存储 虚拟机 加载的 类信息,常量,静态变量,静态变量,及时编辑器编译后的代码数据

  类的版本

  字段

  方法

  接口

  有些虚拟机使用永久区 作为方法区。

 

14 控制jvm 内存大小的几个参数

  

  1. -Xms 为jvm启动时分配的内存,比如-Xms200m,表示分配200M
  2. -Xmx 为jvm运行过程中分配的最大内存,比如-Xms500m,表示jvm进程最多只能够占用500M内存
  3. -Xss 为jvm启动的每个线程分配的内存大小,默认JDK1.4中是256K,JDK1.5+中是1M

 

15 常量池位于方法区。string  常量(  "abc" 这种方式创建的字符串 )不会放在堆内存中,而是放在方法区的常量池。但是 new的 String 是 放在堆内存中。

 

16 String对象的 intern 方法把这个对象移动到 常量池,并且返回常量池中这个 字符串常量的地址。堆内存中的这个 这个字符串对象不会别删除。并且这个对象的指针依旧指向 堆内存的 这个字符串对象。

 

 17 nio 以后允许申请 直接内存,他不是巨魔的规范。也可能内存溢出。

 

18 内存分配的两种方式一种是 指针碰撞 一种是维护一个 空闲列表。使用 那种 分配策略依赖于GC 是够有整理内存的功能。

 

19 对象创建的过程

  1 检查类是否装载

  2 如果内与装载就装载这个类,放到 方法区

  3 在堆中分配内存

  4 初始化分配的内存,基本数据类型有初始值,引用类型初始化为null;

  5电泳对象的 init 方法。

  

  

 

20  内存分配线程安全问题

  解决方案1 使用同步执行,这样就不会冲突了,但是这样效率低。

  解决方案2 让每个线程在指定的区域内分配内存(TLAB(线程本地程序缓冲区) ),这样就不会冲突了。如果某个线程这个区域 满了,就再给它 一块。

 

21 对象结构 

  1 header(对象头)

     自身运行时数据(Mark Word)(哈希值,GC分代年龄,锁抓国泰标志,线程持有的锁,偏向线程Id,偏向时间戳 )

     类型指针 ,数组长度是存这里的。

  2 instanceData(实例数据)  

      我们看到的 对象变量数据。

      相同长度的变量会放在一起。

  3 Padding(边缘区) 

     虚拟机要求对象大小是8个字节的整数倍,对象头是32 个字节。如果 实例数据区不是 8 的整数倍,就需要边缘区来对齐。

      

 22 对象定位的两种方法

  使用句柄 栈里面的对象存的是句柄地址,通需要通过句柄池,才能找到 堆里面的 对象。

  使用指针  栈里面的对象是指针,通过指针可以找到堆里面的对象。(hotspot用的这个)

 

23 如何判断对象是否为垃圾

    1 引用计数法  在对象中记录有多少变量指向它。如果有个新的变量指向它就+1,有指向它的变量只想别的地方就-1.,但是如果 对象相互持有对方的引用,即便 外部已经不能访问到,他们的计数依旧不是0.

     2 可达性分析算法  从GCroot 表,并且从GCroot 向下找,没有找到的对象就是应该删除的。 一般 JVM 都是用的这个。

 

        

 

24 如何回收

   回收策略有

      1 标记-清楚算法 ,

        确定垃圾以后,做上标记,然后清除掉,这样的算法会让 堆内存有很多不连续的空间。分配新的大型对象改的时候还要调空间

      2 复制算法 

        在触发垃圾回收的时候,吧不需要删除的都移动 别的区域。,然后本区域全部删除。 针对回收效率高的 新生代内存。  

      3 标记-整理算法

         想标记要删除的对象,然后把要删除的移动到一端,并且把这一点要保留的移动到另一端,然后标记删除的那一端 就可以直接删除了。

       4 分代收集算法

         新生代用 复制算法,老年代用 标记 整理算法。

   垃圾回收器

      1 Serial  

        最基本,历史最悠久的 垃圾回收器,它是单线程的,并且它阻塞 java其他线程执行,等他执行完后java的别的线程才能继续执行。适用于桌面端,内存比较小的地方。

      2 Parnew 和 parallel 

         多线程的,使用复制算法

          Parnew  的设计目标是缩短 回收时间( 回收依旧阻塞 ,java线程 )

          parallel 的设计目标是控制吞吐量。

            吞吐量 = 代码执行时间/(代码执行时间+垃圾回收时间)

            parallel  可以设置 垃圾回收器最大 停顿时间  -XX:MaxFCPauseMills 

            parallel  可以设置 吞吐量 -XX:GCTimeRatio  默认99 

      3 Cms  (  concurrent mark sweep )

        使用的标签清除算法,适用于老年区。 

        工作工程

          1 初始标记

          2 并发标记

          3 重新标记

          4 并发删除

        优点:

          并发手机

          低停顿

        缺点:

           占用CPU资源多

            无法处理浮动垃圾(当打扫回收了,就产生的垃圾)

            出现 concurrent mode  failure

            空间碎片 

      4 G1   最牛垃圾回收器

        有点:

          支持并行(多线程)收集

          并发收集(不阻塞java 线程)

          分代收集

          支持碎片整理

          可一控制 吞吐量和 最大收集时间

          可预测的停顿

        

        工作工程

          1 初始标记

          2 并发标记

          3 最终标记

          4 筛选回收

  

    

25 合适回收

 

 

26 打印垃圾回收信息(  下面两句用处一样 都是打印垃圾回收信息 )

   -verbose:gc

   -xx:+PrintGcDetail

 

27  那些地方的引用可以作为GCroot 

        1 虚拟机栈

        2 方法区中常量属性所引用的对象

        3 方法区中类属性所引用的对象

        4 本地方法栈中引用的变量的对象

 

28  复制清除算法的的堆内存的分区

    新生代

      Eden  伊甸园

      Survivor 存活区

      Tenured Gen 老年代

 

 

 

 

29 内存分配 策略

  1 优先分配到 eden

    如果eden区域不够,那么就 向 存活区借(放到存活区),如果存活区还不够,就放到老年区。

    

  2 大对象直接分配带老年区

    需要指定参数指定: -XX:PretenureSiseThreshold 大于这个尺寸的对象会知己分配到老年区。

  3 长期存活的对象分配到老年区

  4 空间分配担保

  5 动对象年龄判断

 

 30 指定 垃圾回收器类型  +XX:Use(GCName)Gc  

 

31 hotspot  使用的 GC类型

    如果是 server 环境 默认是 parallel 

    如果是cilent  默认使用的 serial

 

32 指定 存活对象静茹老年代的参数( JDK 7 以后不一定生效)

  -XX:MaxTenuringThreshold ( 默认15) 当存活年龄大于 这个值以后会进入 老年代。

 

33 空间分配担保 默认是开启的,可以通过

      -XX:+HandlePromotionFailure (开启)

      -XX:-HandlePromotionFailure (关闭) 

  

34 空间担保的过程。

  1 eden 区域不够分配 新的对象了。

  2 上层区域有足够的区域容纳 下层 区域的 所有存活对象

  3 上层区域的最大连续空间可以容纳下,历史晋升到此区域的存活对象的平均大小。如果可以才才空间担保,不够就不担保。  

 

35 栈上分配  对象除了可以分配在 堆中,还可以分配在 栈中,前提是他没有发生逃逸,也就是它 只在方法内使用。

 

 

36 对象逃逸  如果一个局部定义的对象,赋值 给成员变量 或者成员变量的属性  ,返回这个对象 让外部可见 了就是 叫做对象逃逸。

 

 

37 工具  jps  查看java进程

    jps -m  能显示 主类main方法参数,和进程id。

   jps -l  能显示 主类文件全名,和进程id。

      jps -v  能显示 虚拟机参数,和进程id。

 

38  工具  jstat 查看类加载 内存 垃圾收集 jit编译的信息

    jstat -gcutill 进程Id

 

39 元空间,1.8 以后 元空间代替了永久代。元空间不在虚拟机中。而是使用的本地内存,因此元空间的大小仅受本地内存限制。

  备注:

      Java6和6之前,常量池是存放在方法区中的。

      Java7,将常量池是存放到了堆中,常量池就相当于是在永久代中,所以永久代存放在堆中。

      Java8之后,取消了整个永久代区域,取而代之的是元空间。没有再对常量池进行调整。

 

 

40 工具  jinfo 实时查看,调整 jvm 参数。

    jinfo -flag  参数名  查看某个参数是否使用。

    jinfo -flag  +-参数名  调整某个参数。

 

41 工具 jmap  生成堆快照

 

42 工具  jhat 分析 jmap 到处的 堆的快照 

 

43 工具 jstack  生产 虚拟机的 线程快照

 

45 上面的这几个东西命令 ,都可以用jconsole 图形用户界面的方式看到。

 

46 jconsole 里面线程菜单里面可以方便的检查死锁。

 

 

 47 VisualVM  jvm 可视化监控工具 ,比 jconsole 更加强大 ,

   备注:MAT 也不错

 

 

48 class 文件是字节码文件,能在windows 下面执行的文件是二进制文件。

 

49 class 文件的 第一行 cafebabe 后面的 8 个字节是 魔数(magic),表示 编译 class文件的时候 的 JDK 版本。

 

50 编译器 和 jvm 都是 实现了相应规范的产物,要是是按照这个规范做出来的 编译器就可以编译 java文件。就可以执行class文件

 

51 jvm 是向下兼容的。该版本的 jvm 可以执行第版本编译器编译的 class文件。

 

 

52 class 文件格式

    

 

 53 jvm 是基于栈的指令集架构,Dalvik  是基于寄存机的 指令集架构。所以 Dalvik  不算是一个严格的 JVM.

 

54 字节码 指令是 用一个字节来 表示某种操作的数字,有256 个。后面 可以跟多个参数数。表示参与这个 指令的 参数。

 

55 新版本的 try  catch 在程序正常执行的时候,不会影响性能,在字节码文件中,新版的 异常 处理 是写到 Exception table里面去的。

 

56 类生命周期

    

  备注 类的加载和 连接是并行的。 但是 加载一定在 连接之前 ,并且 连接一定在加载以后 结束。中间过程会 并行。

 

57 类的初始化 有 4 个触发条件

  1 读取到 new(创建对象), getstatic(获取 静态属性), putstatic(设置 静态属性), invokestatic( 调用 静态方法 ) 这4 条 字节码指令的时候(但是 ),但是读取静态 属性的时候 不会 触发

  

  2 使用java反射调用的时候活初始化这个类。

 

  3 初始化一个类的时候,如果父类还没初始化,那么久初始化子类

 

  4 当虚拟机启动。会首先初始化 Main 方法的的类。

 

  5 1.8 以后 支持动态语言以后,如果 调用一个方法,这个方法 的参数是的类型是一个没有初始化的类,那么久初始化这个类。

 

  不会被初始化的 场景:

    1 通过子类 引用父类的静态字段,子类不会被初始化,父类会。

    2 通过数组定义来引用类(比如  User[] us = new User[10]; )

    3 调用类的常量

 

58 静态属性(final 修饰的 字段) 放在常量池,而不是方法虚拟机栈栈中。

 

 

59 类加载过程

   1 通过类的全限定名来获取此类的二进制流。

    2 将这个字节流所代表的静态存储结构转化成为方法区的运行时数据结构

   3 在内存中生成一个代表这个类的Class对象,作为这个类的各种数据的范文入口。

 

     备注:Class 对象不再 堆内存中,Hotport 放在 方法区。

 

60 类 连接 过程 

    

 

 

 

 

 

61 校验的 过程,验证 并非必须,可以通过jvm 参数 指定不验证。 验证的目的是 1 保证这个class的正确性,2 保并且保证jvm 的安全。

  1 文件格式验证

  2 元数据验证

  3 字节码验证

  4 符号引用验证 

 

62 准备阶段

  正式为类变量(静态变量)分配内存,并且设置这些变量的初始值( 初始化成默认值)。这些变量使用的内存都将在方法区中进行分配。静态常量是放在 常量池。

 

63 解析阶段

  1 吧常量池符号引用替换成直接引用。

  2 字段解析

  3 类方法解析

   3 接口方法解析

  

64 类的初始化 (clint)

  1 clint 活手机 静态变量和静态语句块,并且是按照顺序搜集执行。所以如果 静态语句块在前面,静态标量在后面,静态语句块是不能 使用,但是可以赋值。

  2 父类的 clinit 方法会先执行。

  3 接口中有类变量需要赋值,也会执行clinit方法,但是不会像执行父类的clinit方法。支付父类接口中定的变量使用是才会初始化。

  4 对个线程同时初始化一个类,那么只会有一个线程执行,别的线程会阻塞并且等待这个线程执行完毕。也就是说 clinit 是线程安全的。也就是说类的 静态代码块是线程安全的。因为类的静态语句块会阻塞别的线程,所以使用的时候要注意。

 

65 类加载器。

  同一个类如果被不同的类加载器加载,那么产生的不是系统一个类对象  用instance  判断是不等的。

  

66 类加载器的等级 

  1 启动类加载器 ,C++实现,是JVM的一部分,加载 javahome/jre/lib 下面的 类

  2 拓展类加载器 ,加载 javahome/jre/lib/ext 目录中的类

  3 应用类加载器  加载用户类路径上的类

  4 自定义类加载器 想加载 那个类就加载那个各类。

 

67  类加载器是通过 双亲委派模型 协同工作的。

  双亲委派模型:

    1类加载器加载一个类的时候,会=把这个加载任务委派给父级加载器。

    2 如果父级加载器可以加载这个类,加完整加载,如果不可以就。还给子级的加载器,但是最低不会超过发起的 类加载器级别。

    3 如果 父级别加载器认定这个 类的限定名已经加载过了,那么这个类不会再次被加载。

    4 如果装了一圈回来都无法加载,那么会抛出找不到类的异常。

 

  双亲委派模型是类的加载有了优先级层次关系,越是基础的类,会被越上层的类加载器加载。保证了java程序的稳定执行。

 

68 不同成层的 类加载器 直接没有代码上的继承关系。但是他们是父子关系的。classLoder 里面的 Parent 就指向他们的父类。

    启动类加载器(不是java实现的 是C++)>拓展类加载器>引用类加载器>自定义类加载器。

 

 

69 看看下面的 代码,如果一个 ClassLoader 没有传入父 类加载器拿了会自动给他找一个 。 getSystemClassLoader 获取的就是程序启动的时候用的引用类加载器。也就是说如果。没有指定父类,那么 父类就是它。

  

 

 

70 出现线程安全的 三个条件

  1 多线程

  2 资源共享

  3 对共享资源的非原子操作

 

71 运行时栈帧的结构

  1 局部变量表

  2 操作数栈

  3  动态链接(栈帧 里面 指向对应方法区的 连接)

  4 方法返回地址 

  5 附加信息 (  虚拟机的实现自己添加的东西,和不是虚拟机规范里面定义的 )

   栈帧:也叫过程活动记录。它是虚拟机运行时的虚拟机栈元素。栈帧的大小是在编译的时候就已经确定了的。

72  局部变量表是用来存放方法局部变量的。局部变量表的最小存储单元是 slot(槽) 。

    局部变量不会赋初始值

    局部变量表的大小在编译的时候就已经确定了。

    slot 32 位的JDK slot 的长度就是32 位,64 位额就是64 位。

    slot 是最小存储单元,所以 不管什么数据类型最少 占用一个slot,如果不够就多个。

    slot 复用,当一个变量的pc寄存器的值大于Slot 的作用于的时候,Slot是可以复用的(在这个局部变量作用于外部的 变量可以 复用 ,因为内部的应该失效了,所以可以复用 )。

 

73 java虚拟机的 解释执行引擎被称为 基于栈的执行引擎  ,这里面的 栈 是指操作数栈。  

   

74 操作数栈

    操作数栈大小也是在编译的时候确定的

    

75 一个 加法 的 操作栈 执行过程

  

  

  

 

 

76 方法调用的方法

    1 解析调用:在编译期间就可以确定调用哪个方法

        解析调用包含的方法类型,静态方法(),构造方法,私有方法,filnal 修饰的方法。( 所有 没有重写,和重载的方法 都是 解析调用),使用的 invokestatic 和  invokespecial

    2 分派调用

        静态分派:  方法重载的时候,如果 jvm 选择调用哪个方法是通过静态分配来选择的 ,静态分派也是在编译期决定的。 使用的指令是 invokevirual  

          静态:指的是形参的类型是 和实参 类型 匹配的过程中,实参 的数据类型是通过直接的 引用类型来确定,而不会通过 实现类的 真是类型来确定。

        

          备注: 静态分派是通过参数的静态类型来调用的,但是如果重载的方法参数 可以通过 自动类型转换( 一个是Object 一个是User,或者一个 char ,一个 int  ),这时候回根据 静态参数的类型选择最匹配的一个。

        动态分派:方法在 重写的时候,也就事多态的情景,动态分派 是在运行时。

          动态分派,调用的实际类型的方法。使用的指令是 invokevirual  

        

 

 

 

  备注:方法调用不等同于方法的执行,方法电泳的唯一任务就是确定被调用发方法版本。 

77 面向对象的三大特征 封装 继承 和多态

 

78 动态分派的 调用 调用过程 (多态就依赖它 )

    

79 java是一个静态类型的语言,js 是 动态类型的语言。

    静态类型语言:在非运行期间,变量类型是确定的。 又叫做强类型的语言。

     动态语言:在非运行期间,变量的类型是不确定的。但是值的类型是确定的。又叫做弱类型的语言。

      

 

80 invokedynamic 是 jvm 对动态语言的支持。

 

82 invokeInterface  调用接口方法是时候使用的这个指令。

 

82  jdk8 加入了  js引擎的 可以直接执行 js。

  通过 ScriptEngineManager 来支持的,支持 Javascript、Groovy、Ruby、Scheme、Haskell

    

 

 

83 java 的 技术体系

    

 

posted on 2019-07-05 14:34  zhangyukun  阅读(458)  评论(0编辑  收藏  举报

导航