jvm

概述

  1. JVM运行时内存区域划分
  2. 内存溢出OOM和堆栈溢出SOE的示例及原因、如何排查与解决
  3. 如何判断对象是否可以回收或存活
  4. 常见的GC回收算法及其含义
  5. 常见的JVM性能监控和故障处理工具类:jps、jstat、jmap、jinfo、jconsole等
  6. JVM如何设置参数
  7. JVM性能调优
  8. 类加载器、双亲委派模型、一个类的生命周期、类是如何加载到JVM中的
  9. 类加载的过程:加载、验证、准备、解析、初始化
  10. 强引用、软引用、弱引用、虚引用
  11. Java内存模型JMM
    1. 什么是Java内存模型
    2. Java内存模型的作用是什么
    3. JMM中内存间交互操作有哪些
    4. JMM中有哪些规则
    5. 内存访问操作有有哪些特性

      JVM运行时内存区域划分

      主要分为5个部分:
  • 程序计数器
  • 虚拟机栈
  • 本地方法栈
  • 方法区

程序计数器

Java是一门解释型的语言,java文件被javac编译成class的字节码文件,字节码解释器会将编译好的字节码文件解释执行,这也java语言可以很好的实现的跨平台的真正原因,而程序计数器中的值保存的是下一条将要被执行的执行的字节码指令的地址,每条线程都会有一个独立的程序计数器,各线程之间互不影响,独立存储。

java虚拟机栈

虚拟机栈描述的是java方法执行的内存模型,方法的执行的同时会创建一个栈祯,用于存储方法中的局部变量表、操作数栈、动态链接、方法的出口等信息,每个方法从调用直到执行完成的过程,就对应着一个栈祯在虚拟机栈中入栈到出栈的过程。

本地方法栈

本地方法栈与java虚拟机栈类似,也会抛出OutOfMemoryError,和StackOverflowError
在HotSpot虚拟机中,本地方法栈和虚拟机栈合二为一

方法区

方法区

java堆是java虚拟机所管理的内存中最大的一块,是所有线程共享的内存区域,主要用来保存对象实例,在虚拟机进程启动是创建,按照分带收集算法来说,java对可以细分为新生代和老年代,还可以细分为Edean空间、From Survivor空间,To Survivor空间,java对可以划分出多个线程私有的分配缓冲区,TLAB,进一步划分的目的是为了更好的内存回收,或者更快的内存分配,java堆可以处于物理上不连续的内存区域,逐流的虚拟机都将java堆实现成可扩展的,当java没有内存完成实例分配,且无法再扩展时,将会抛出OutOfMemoryError

内存溢出OOM和堆栈溢出SOE的示例及原因、如何排查与解决

OOM示例

java堆内存发生OOM
原因: 程序中有大量的对象生成,并且这些对象不能被GC回收,当java堆不能为实例分配内存时就会发生内存溢出OOM
排查: 在启动参数中加上-XX:+HeapDumpOnOutOfMemoryError,当发生内存溢出时,会dump当时的内存堆转储快照文件
解决: 通过分析对转储快照文件,是内存溢出还是内存泄漏,如果是内存泄漏,根据泄漏的对象实例确定对象类型,以及GC roots的引用链,从而确定泄漏的代码;如果是内存溢出,查看java堆参数,是否能调大-Xms和-Xmx的值
实验:vm args -Xms10M -Xmx10M -XX:+HeapDumpOnOutOfMemoryError

1
2
3
4
5
6
7
public static void main(String[] args){
List<String> list = new ArrayList<String>();
int i = 0;
while(true){
list.add(String.valueOf(i++));
}
}

在JDK 1.8下
运行结果如下:

1
2
3
4
5
6
7
8
java.lang.OutOfMemoryError: GC overhead limit exceeded
Dumping heap to java_pid859.hprof ...
Heap dump file created [12059282 bytes in 0.075 secs]
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
Disconnected from the target VM, address: '127.0.0.1:50160', transport: 'socket'
at java.lang.Integer.toString(Integer.java:403)
at java.lang.String.valueOf(String.java:3087)
at com.tuyu.oom.JavaHeapOOM.main(JavaHeapOOM.java:47)

分析:参考链接
垃圾回收器出现了异常

sun官方说明:并行/并发回收器在GC回收时间过长时会抛出OutOfMemroyError。过长的定义是,超过98%的时间用来做GC并且回收了不到2%的堆内存。用来避免内存过小造成应用不能正常工作。

SOE示例

java虚拟机栈发生SOE
原因:线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError
排查:通过错误日志,定位发生SOE的代码
解决:发生SOE一般是程序中出现了递归调用,检查代码递归是否写错,如果没有写错能否用尾递归优化代码,或者用迭代的方式替换递归算法,或者适当调整-Xss的大小,增大虚拟机允许的最大栈深度
实验:vm args: -Xss160k

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static int i = 0;
public static void main(String[] args){
try{
hello();
}catch(Throwable e){
System.out.println("i = " + i);
e.printStackTrace();
}
}

public static void hello(){
i++;
hello();
}

在JDK 1.8下
运行结果如下:

1
2
3
4
i = 847
java.lang.StackOverflowError
at com.tuyu.oom.JavaHeapOOM.hello(JavaHeapOOM.java:54)
....

分析: 在我的电脑中运行此程序,递归调用了847次,160K的栈容量能够支持的栈的最大深度是847,这个也跟不同的方法有关,不同方法的栈帧大小不一

如何判断对象是否可以回收或存活

常见的GC回收算法及其含义

常见的JVM性能监控和故障处理工具类:jps、jstat、jmap、jinfo、jconsole等

JVM如何设置参数

JVM性能调优

类加载器、双亲委派模型、一个类的生命周期、类是如何加载到JVM中的

类加载的过程:加载、验证、准备、解析、初始化

强引用、软引用、弱引用、虚引用

java内存模型

JMM是什么?

Java内存模型规定了除线程私有变量外,所有的变量都保存在主内存中;其次每条线程都有自己的工作内存;然后,线程对变量的访问只能在工作内存,不能直接读写主内存中的变量;最后线程间无法直接访问对方工作内存中的变量,线程间变量值的传递需要通过主内存来完成

JMM的作用是什么?

Java内存模型的作用是屏蔽各种硬件和操作系统间内存访问差异,以达到让java程序在平台能有一致的内存访问效果。

JMM中定义了哪些内存间交互操作?

Java内存模型中定义8个内存间交互操作,分别是lock, unlock, read, write, use, assign, load, store, 前四个是作用在主内存中的,后四个是作用在线程工作内存中的。

JMM中有哪些规则

对于volatile型变量的特殊访问规则,具体规则是:被volatile修饰的变量对于所有线程的可见性,禁止指令重排序优化

以及对于long、double的特殊访问规则,具体规则是:long、double的非原子性协定,允许Java虚拟机实现不保证64位数据类型的read, write, load, store操作的原子性。

内存访问有哪些特性?

原子性, 可见性, 有序性
说说我对原子性的理解:大多数基本数据类型变量的访问读写操作都是原子性的,Java内存模型提供的8个内存交互操作都是原子性的,其中lock, unlock操作提供了更大范围的原子性,这两个操作对应到字节码层面就是monitorenter和monitorexit,对应到java代码中就是synchronized同步代码块,所以说synchronized代码块之间的操作都是原子性的。

说说我对可见性的理解:可见性
java内存模型,即JMM,java虚拟机规范定义了一种java内存模型来屏蔽掉各种硬件和操作系统之间的内存访问差异,使得java程序在各平台上有一致的内存访问效果
java程序中除线程私有变量之外的所有变量都保存在主内存之中
每条线程都有自己的工作内存,存放线程的私有变量(比如局部变量、方法参数、以及被线程使用到的变量值的主内存的副本拷贝)
线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存中的变量
线程间无法直接访问对方的工作内存,线程间值的传递必须通过主内存完成
JMM定义了8个操作来完成变量从主内存拷贝到工作内存,从工作内存同步回主内存,分别是:lock, unlock, read, load, use, assign, store, write
JMM还定义了许多规则
比如:
对于volatile型变量的特殊规则

  1. 保证了被volatile修饰的变量对所有线程的可见性
  2. 禁止指令重排序优化
    这里的可见性指的是:当一个线程修改了共享变量的值,新值对于其他线程来说是立即可见的
    禁止指令重排序优化,引出了双锁检测实现单例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    class Instance{
    private static volatile Instance instance;
    private Instance(){}

    public static Instance getInstance(){
    if(instance == null){
    synchronized(Instance.class){
    if(instance == null){
    instance = new Instance();
    }
    }
    }
    return instance;
    }
    }
    对于long,double型变量的特殊规则
    非原子性协定
    指的是允许java虚拟机实现可以不保证64为数据类型的load, store, read, write的原子性

    并发当中的三个特性:
    原子性
    可见性
    有序性

    大致可以认为所有基本类型的变量读写操作都是原子性的,例外情况就是非原子性协定,几乎不会出现,
    JMM提供了read, load, use, assign, store, write操作保证边变量的原子性
    对于更大范围的原子性保证,JMM提供了lock, unlock操作,它们对应与java字节码指令是moniterenter和mointerexit,而这两个字节码对应与java代码中的synchronized关键字,所以sychronized代码块之间的操作也是原子性的

    可见性指的是,当一个线程修改了共享变量的值,新值对于其他线程来说是立即可见的,前面已经说过,volatile变量的特殊规则保证了新值能立即同步到主内存,及要使用前能立即从主内存刷新。
    对于可见性,java语言提供了3个关键字,synchronized, volatile, final

    有序性指的是,在线程中观察,所有代码都是有序的;在一个线程中观察另一个线程,所有代码都是无序的。前半句指的是程序内表现为串行语义,后半句指的是指令重排序和工作内存到主内存的同步延迟

    先行发生原则
    指的是JMM中定义的两个操作之间的偏序关系,如果操作A现行发生于操作B,也就是说在操作B发生之前,操作A产生的影响被操作B来观察到
    该原则的作用是,判断数据是否存在竞争,线程是否安全

    JMM中8中先行发生关系
    程序次序原则
    管程锁定原则
    volatile变量原则
    线程启动原则
    线程终止原则
    线程中断原则
    对象终结原则
    传递性原则

参考链接