JVM相关知识记录

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
2
3
4
5
6
➜  hsrm-register git:(master) jps -l
19334 org.jetbrains.jps.cmdline.Launcher
19335 org.hsrm.register.RegisterApplication
19304 org.jetbrains.idea.maven.server.RemoteMavenServer36
537 nutstore.client.gui.NutstoreGUI
19658 sun.tools.jps.Jps

jstack

打印Java进程,核心文件或远程调试服务器的Java线程堆栈跟踪。根据堆栈信息可以帮助我们定位到具体的代码

概要

jstack [参数]

pid

​ 为其打印堆栈跟踪的进程ID。该进程必须是Java进程。要获取机器上运行的Java进程的列表,请使用jps

参数

-F 当jstack[ -l] pid没有响应时,强制进行堆栈转储

-l 打印有关锁的其他信息

-m 不仅会输出Java堆栈信息,还会输出C/C++堆栈信息(比如Native方法