Java 内存模型及规范

ifeve-Java内存模型FAQ-中译版

The Java Memory Model

The JSR-133 Cookbook for Compiler Writers

JVM 架构

https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html

根据规范可知, 运行时的数据区域有

  • PC 寄存器(program counter register)(线程私有)
  • JVM 栈(JVM stack),以前也称为 Java栈(线程私有,即不可能在一个栈帧之中引用另一个线程的栈帧)=> 它保存的是 栈帧 。它持有本地变量、中间结果,并且在方法的调用和返回中起着重要的作用。
  • 堆(heap) => 即GC所管理的各种对象(线程共享)
  • 方法区(method area) => 类似操作系统进程的 TEXT 段(文本段,即只读段),它是堆的逻辑组成部分,不同的JVM实现,可以将它实现为不需要进行GC的。(线程共享)
  • 运行时常量池(runtime constant pool) => 注意,这个不一定只是编译期的常量池,也可以是运行时解析后生成的,它在方法区中分配。表示了 class 文件中的 constant_pool 表。
  • 本地方法栈(native method stack) => 执行本地方法(例如C语言的方法)时创建的栈

栈帧

用来存储数据、中间结果,也用来处理动态链接、方法返回值和异常分发。每一个栈帧都有:

  • 本地变量表(在编译时确定)
  • 操作数栈(在编译时确定)
  • 指向当前方法所在类的运行时常量池的引用

本地变量表

注意,参数与是本地变量的一部分。

一个本地变量可以保存一个类型为 boolean, byte, char, short, int, float, reference 或 returnAddress 。

long 和 double 要占用两个本地变量。

它通过 索引 来进行定位和访问,从 0 开始(如果是实例方法,则第0个变量,一定是 this,其他参数,则按顺序推算)。

操作数栈

字节码指令的操作数必须是在 操作数栈 的(我个人理解,类似CPU的寄存器,因为JVM是基于栈的,所以,这里的操作数栈就可以认为等同于CPU的寄存器)

注意,操作数栈,是在栈之内的。

JVM栈 包含 栈帧, 而栈帧包含 操作数栈

动态链接

它的作用就是将指向常量池的符号转换为实际方法的直接引用。

方法正常结束

即没有抛出任何异常。这时当前方法返回一个值给它的调用者,然后还要进行恢复调用者状态(即JVM里,它是由被调用者进行上下文恢复的,即要恢复调用者的局部变量表、操作数栈、PC等)

方法异常结束

这种情况下,一定不会有方法返回值给调用者的

参考资料

深入理解JVM-周志明 Java虚拟机规范-周志明等译 http://docs.oracle.com/javase/specs/

JVM 各种参数

参考本博客的 [翻译]Java -XX:+PrintFlagsFinal命令行参数详解

一般的调优都是从

内存、GC类型

Java-Memory-Model.png

[11:17:44] emacsist:~ $ java -X
    -Xmixed           混合模式执行 (默认)
    -Xint             仅解释模式执行
    -Xbootclasspath:<用 : 分隔的目录和 zip/jar 文件>
                      设置搜索路径以引导类和资源
    -Xbootclasspath/a:<用 : 分隔的目录和 zip/jar 文件>
                      附加在引导类路径末尾
    -Xbootclasspath/p:<用 : 分隔的目录和 zip/jar 文件>
                      置于引导类路径之前
    -Xdiag            显示附加诊断消息
    -Xnoclassgc       禁用类垃圾收集
    -Xincgc           启用增量垃圾收集
    -Xloggc:<file>    将 GC 状态记录在文件中 (带时间戳)
    -Xbatch           禁用后台编译
    -Xms<size>        设置初始 Java 堆大小
    -Xmx<size>        设置最大 Java 堆大小
    -Xss<size>        设置 Java 线程堆栈大小
    -Xprof            输出 cpu 配置文件数据
    -Xfuture          启用最严格的检查, 预期将来的默认值
    -Xrs              减少 Java/VM 对操作系统信号的使用 (请参阅文档)
    -Xcheck:jni       对 JNI 函数执行其他检查
    -Xshare:off       不尝试使用共享类数据
    -Xshare:auto      在可能的情况下使用共享类数据 (默认)
    -Xshare:on        要求使用共享类数据, 否则将失败。
    -XshowSettings    显示所有设置并继续
    -XshowSettings:all
                      显示所有设置并继续
    -XshowSettings:vm 显示所有与 vm 相关的设置并继续
    -XshowSettings:properties
                      显示所有属性设置并继续
    -XshowSettings:locale
                      显示所有与区域设置相关的设置并继续

-X 选项是非标准选项, 如有更改, 恕不另行通知。


以下选项为 Mac OS X 特定的选项:
    -XstartOnFirstThread
                      在第一个 (AppKit) 线程上运行 main() 方法
    -Xdock:name=<应用程序名称>"
                      覆盖停靠栏中显示的默认应用程序名称
    -Xdock:icon=<图标文件的路径>
                      覆盖停靠栏中显示的默认图标


[11:27:06] emacsist:~ $

Java 自带工具

Mac 上查看Java安装目录:

[18:37:34] emacsist:~ $ /usr/libexec/java_home
/Library/Java/JavaVirtualMachines/jdk1.8.0_74.jdk/Contents/Home
[18:37:37] emacsist:~ $
appletviewer => applet 查看器,开发 applet 才会用到
extcheck => 用于检测 Jar 包与当前JDK是否有冲突,这个极少用到,一般是JDK更新扩展 ext 包时使用
idlj => 为Java语言来使用 CORBA 功能的 .java 文件进行生成代码用到的。
jar => jar 文件处理工具。类似 zip ,虽然 jar 本质上也是一个 zip 文件
jarsigner => jar 文件签名工具
java => Java 程序启动器
javac => Java源码编译器
javadoc => JavaDoc 处理工具
javafxpackager => 开发 JavaFX 
javah => 开发本地代码,如 C 时用来处理头文件的
javap => Java 类文件反编译器
javapackager => 执行与Java和 JavaFX程序打包和签名相关的任务。比如可以将你的应用捆绑JRE,然后打包发布~
jcmd => JVM诊断命令工具,发送诊断命令请求到一个正在运行的JVM(比如导出内存信息,栈信息等,jmap)
jconsole => 基于 JMX 的图形化监控JVM的工具
jdb => Java调试器
jdeps => Java 类依赖分析器
jhat => 查看 Java 堆 dump 文件的
jinfo => 查看Java进程的配置信息
jjs => 运行 Nashorn (JVM上的JavaScript解释器) 命令行脚本 Shell
jmap => 获取Java进程的内存映像
jmc => The Java Mission Control (JMC) ,Java 程序的性能监控和分析工具
jps => 列出Java进程
jrunscript => Java的脚本 shell
jsadebugd => Javar 调试守护服务代理者,类似一个远程的 debug 服务器
jstack => 获取Java进程当前所有线程栈
jstat => 获取 Java 进程的状态信息
jstatd => jstat 的守护进程
jvisualvm => 可视化的VM工具
keytool => 密钥工具
native2ascii => 将文本文件转换为 Unicode Latin-1 编码
orbd => CORBA 相关的工具。
pack200 => 将 jar 文件转换为 pack200 类型的文件(高度压缩版的 jar 文件,可减少带宽和下载时间等)
policytool => 策略工具
rmic => RMI 编译器
rmid => RMI 守护进程
rmiregistry => RMI注册器
schemagen => XML schema 生成器
serialver => 序列化版本查看器。即查看 serialVersionUID 的值
servertool => 不太明了。。
tnameserv => 不太明了。。
unpack200 => 解压缩 pack200 文件的工具
wsgen => 生成 JAX-WS 相关的工具
wsimport => 导入 JAX-WS 相关的工具
xjc => 根据 XML schema 生成 Java 类

JDK Tools and Utilities

各种性能工具

分析 core dump 文件

  • MAT
  • JDK自带的 jvisualvm

各种 OutOfMemory, Stackoverflow

Stackoverflow 的代码

JVM 参数:(将栈调小点,让它快点出现问题)

-Xss170k
package com.github.emacsist.memory;

public class StackoverFlow {

    public static void main(String[] args) {
        hello();
    }

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

然后它就会报:

Exception in thread "main" java.lang.StackOverflowError
	at com.github.emacsist.memory.StackoverFlow.hello(StackoverFlow.java:10)
	at com.github.emacsist.memory.StackoverFlow.hello(StackoverFlow.java:10)
	at com.github.emacsist.memory.StackoverFlow.hello(StackoverFlow.java:10)

HeapOutOfMemory 的代码

JVM参数:(调小堆大小,让它早点出现异常)

-Xms5m  -Xmx5m
package com.github.emacsist.memory;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by emacsist on 2017/6/29.
 */
public class HeapOutOfMemory {
    public static void main(String[] args) {
        List<Object> container = new ArrayList<>();
        while (true) {
            container.add(new Object());
        }
    }
}

输出

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3210)
	at java.util.Arrays.copyOf(Arrays.java:3181)
	at java.util.ArrayList.grow(ArrayList.java:261)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
	at java.util.ArrayList.add(ArrayList.java:458)
	at com.github.emacsist.memory.HeapOutOfMemory.main(HeapOutOfMemory.java:13)

Young OutOfMemory 的代码

JVM参数:( -Xmn 是控制 Young 区的内存大小的)

-Xms10m -Xmx10m -Xmn6m
package com.github.emacsist.memory;

/**
 * Created by emacsist on 2017/6/29.
 */
public class YoungOutOfMemory {
    public static void main(String[] args) {
        byte[] _10MB = new byte[1024 * 1024 * 10];
        System.out.println("OK => " + _10MB.length);
    }
}

输出

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at com.github.emacsist.memory.YoungOutOfMemory.main(YoungOutOfMemory.java:8)

关于常量池异常代码的问题

因为它在不同版本的JDK的实现不同。可以参考:

String.intern in Java 6, 7 and 8 – string pooling

DirectMemory 的代码

JVM参数:(默认为0,即无限)

-XX:MaxDirectMemorySize=8m

查看:

[12:32:36] emacsist:~ $ java -XX:+PrintFlagsFinal -version | grep MaxDirectMemorySize
    uintx MaxDirectMemorySize                       = 0                                   {product}
java version "1.8.0_74"
Java(TM) SE Runtime Environment (build 1.8.0_74-b02)
Java HotSpot(TM) 64-Bit Server VM (build 25.74-b02, mixed mode)
package com.github.emacsist.memory;

import java.nio.ByteBuffer;

/**
 * Created by emacsist on 2017/6/29.
 */
public class MaxDirectOutOfMemory {
    public static void main(String[] args) {
        ByteBuffer _10MB = ByteBuffer.allocateDirect(1024 * 1024 * 10);
    }
}

输出

Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
	at java.nio.Bits.reserveMemory(Bits.java:693)
	at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
	at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
	at com.github.emacsist.memory.MaxDirectOutOfMemory.main(MaxDirectOutOfMemory.java:10)

个人关于对内存问题分析思路

一般都是先将当前JVM的堆栈 dump 出来、获取当前JVM的内存配置,然后使用 MAT 分析内存占用,看看有没有异常,对比内存的配置是否合理。

使用 jstat 输出GC的统计,查看各种GC的收集程度及其统计信息。

然后再看看具体出现的是哪种 OutOfMemory ,结合上面MAT的内存分析(如果代码有问题,完善代码) 然后再合理配置相应的内存区域大小。