JVM相关知识记录
JVM内存结构
每启动一个线程,JVM就会在栈空间栈分配对应的 线程栈, 比如 1MB 的空间(-Xss1m
)。
线程栈也叫做Java方法栈。 如果使用了JNI方法,则会分配一个单独的本地方法栈(Native Stack).
线程执行过程中,一般会有多个方法组成调用栈(Stack Trace), 比如A调用B,B调用C。。。每执行到一个方法,就会创建对应的 栈帧(Frame).
栈帧只是一个逻辑上的概念,具体的大小,在一个方法编写完成后基本上就能确定。
比如返回值需要有一个空间存放吧,每个局部变量都需要对应的地址空间,此外还有操作数栈,以及方法指针(标识这个栈帧对应的是哪个类的哪个方法,指向常量池中的字符串常量)。
Java程序除了栈内存之外,最主要内存区域就是堆内存了。
堆内存是所有线程共用的内存空间,理论上大家都可以访问里面的内容。
但JVM的具体实现一般会有各种优化。
比如将逻辑上的Java堆,划分为堆(Heap)和非堆(Non-Heap)两个部分. 这种划分的依据在于,我们编写的Java代码,基本上只能使用Heap这部分空间,发生内存分配和回收的主要区域也在这部分,所以有一种说法,这里的Heap也叫GC管理的堆(GC Heap)。
GC理论中有一个重要的思想,叫做分代。 经过研究发现,程序中分配的对象,要么用过就扔,要么就能存活很久很久。
JVM将Heap内存分为年轻代(Young generation)和老年代(Old generation, 也叫 Tenured)两部分。
年轻代还划分为3个内存池,新生代(Eden space)和存活区(Survivor space), 存活区在大部分GC算法中有2个(S0, S1),S0和S1总有一个是空的,但一般较小,也不浪费多少空间。
具体实现对新生代还有优化,那就是TLAB(Thread Local Allocation Buffer), 给每个线程先划定一小片空间,你创建的对象先在这里分配,满了再换。这能极大降低并发资源锁定的开销。
Non-Heap本质上还是Heap,只是一般不归GC管理,里面划分为3个内存池。
- Metaspace, 以前叫持久代(永久代, Permanent generation), Java8换了个名字叫 Metaspace. Java8将方法区移动到了Meta区里面,而方法又是class的一部分。。。和CCS交叉了?
- CCS, Compressed Class Space, 存放class信息的,和 Metaspace 有交叉。
- Code Cache, 存放 JIT 编译器编译后的本地机器代码。
JVM的内存结构大致如此。
以上来源自:https://github.com/cncounter/translation/blob/master/tiemao_2019/22_chat_jvm_troubleshoot/README.md
基础工具
jps
全称 Java Virtual Machine Process Status Too 。列出目标系统上已检测到的Java虚拟机(JVM)
如没有指定远程主机,则显示当前主机当前用户下的Java应用的PID与标识符
概要
jps [参数] [hostid]
参数
-q 不输出类名、Jar名和传入main方法的参数
-m 输出传入main方法的参数
-l 显示应用程序main
类的完整程序包名称或应用程序JAR文件的完整路径名
-v 显示传递给JVM的参数
示例
在本地启动了一个register服务
1 | ➜ hsrm-register git:(master) jps -l |
jstack
打印Java进程,核心文件或远程调试服务器的Java线程堆栈跟踪。根据堆栈信息可以帮助我们定位到具体的代码
概要
jstack [参数]
pid
为其打印堆栈跟踪的进程ID。该进程必须是Java进程。要获取机器上运行的Java进程的列表,请使用jps
参数
-F 当jstack
[ -l
] pid
没有响应时,强制进行堆栈转储
-l 打印有关锁的其他信息
-m 不仅会输出Java堆栈信息,还会输出C/C++堆栈信息(比如Native方法