Java程序员的自我修养
Contents
Java 内存模型及规范
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类型
[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 类
各种性能工具
- TProfiler-alibaba
- crashub
- greys 个人强烈推荐使用这个
分析 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的内存分析(如果代码有问题,完善代码) 然后再合理配置相应的内存区域大小。