一、JDK体系结构
JDK: JDK提供了编译、运行Java程序所需的各种资源和工具;包括Java编译器,Java运行时环境【JRE】;开发工具包括编译工具(javac.exe) 打包工具(jar.exe)等。
JRE: 即JAVA运行时环境,JVM就是包括在JRE中,以及常用的JAVA类库等;
SDK: SDK是基于JDK进行扩展的,是解决企业级开发的工具包。如JSP、JDBC、EJB等就是由SDK提供的 ;
JVM(Java Virtual Machine),Java虚拟机,可以看做是一台抽象化的计算机,它有一套完整的体系架构,包括处理器、堆栈 、寄存器等。
在运行时环境,JVM会将Java字节码解释成机器码。机器码和平台相关的(不同硬件环境、不同操作系统,产生的机器码不同),所以JVM在不同平台有不同的实现。
目前JDK默认使用的实现是Hotspot VM。
二、Java语言的跨平台特性
一次编译,到处执行(Write Once ,Run Anywhere)。
用Java创建的可执行二进制程序,能够不加改变的运行于多个平台。从软件层面屏蔽不同操作系统底层硬件与指令上的区别
三、JVM整体结构及内存模型
3.1 内存模型
官方文档参考:[https://doc](https://doc)s.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5
Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为个不同的数据区。这些区域有各自的用途,以及创建和销毁事件。
JVM用来存储加载的类信息、常量、静态变量、编译后的代码等数据。
PC寄存器(线程私有)
PC寄存器,也叫程序计数器。JVM支持多个线程同时运行,每个线程都有自己的程序计数器。倘若当前执行的是JVM方法,则该寄存器中保存当前执行指令的地址;倘若执行的是native方法,则PC寄存器为空。
这个内存区域是唯一一个在虚拟机中没有规定任何OutOfMemoryError情况的区域。
虚拟机栈(线程私有)
每个线程有一个私有的栈,随着线程的创建而创建。栈里面存放着一种叫做“栈帧”的东西,每个方法在执行的时候会创建一个栈帧,存储了局部变量表(基本数据类型和对象引用),操作数栈,动态连接,方法出口等信息。
每个方法从调用到执行完毕,对应一个栈帧在虚拟机栈中的入栈和出栈。
(方法中的局部变量的空间可以进行释放)
通常所说的栈,一般是指虚拟机栈中的局部变量表部分。局部变量表所需的内存在编译期间完成分配。
栈的大小可以固定也可以动态扩展,当扩展到无法申请足够的内存,则OutOfMemoryError。
当栈调用深度大于JVM所允许的范围,会抛出StackOverflowError的错误
演示栈帧:
public class StackTest {
public int method2(){
int a=1;
int b=2;
int c=a+b;
return c;
}
public int method1(){
return method2();
}
public static void main(String[] args) {
StackTest stackTest = new StackTest();
int i = stackTest.method1();
System.out.println(i);
}
}
本地方法栈(线程私有)
和虚拟机栈类似,主要为虚拟机使用到的Native方法服务。 也会抛出StackOverflowError和OutOfMemoryError。
堆(线程共享)
堆内存是JVM所有线程共享的部分,在虚拟机启动的时候就已经创建。
和程序开发密切相关,应用系统对象都保存在Java堆中。所有的对象和数组都在堆上进行分配。这部分空间可通过GC进行回收。对分代GC来说,堆也是分代的,是GC的主要工作区间。当申请不到空间时,会抛出OutOfMemoryError。
演示内存溢出:
public class HeapOutOfMemoryErrorTest {
byte[] arr = new byte[1024 * 1000];//1M
public static void main(String[] args) throws InterruptedException {
ArrayList<HeapTest> list = new ArrayList<>();
while (true) {
list.add(new HeapTest());
}
}
}
结果:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at com.zengqingfa.exercise.jvm.HeapTest.<init>(HeapTest.java:13)
at com.zengqingfa.exercise.jvm.HeapOutOfMemoryErrorTest.main(HeapOutOfMemoryErrorTest.java:17)
方法区(线程共享)
方法区也是所有线程共享的。主要用于存储类的信息、常量池、方法数据、方法代码等。方法区逻辑上属于堆的一部分,但是为了与堆进行区分,通常又叫“非堆”。
这个区域的内存回收目标主要针对常量池的回收和对类型的卸载。
当方法区无法满足内存分配需求时,则抛出OutOfMemoryError异常。
在HotSpot虚拟机中,用永久代来实现方法区,将GC分代收集扩展至方法区,但是这样容易遇到内存溢出的问题。
JDK1.7中,已经把放在永久代的字符串常量池移到堆中。
JDK1.8撤销永久代,引入元空间。
运行时常量池
运行时常量池就是将编译后的类信息放入方法区中,也就是说它是方法区的一部分。** 运行时常量池用来动态获取类信息**,包括:class文件元信息描述、编译后的代码数据、引用类型数据、类文件常量池等。 运行时常量池是在类加载完成之后,将每个class常量池中的符号引用值转存到运行时常量池中。
3.2 演示
堆内存回收
堆内存的回收过程,使用jvisualvm工具
public class HeapTest {
byte[] arr = new byte[1024 * 100];//100kb
public static void main(String[] args) throws InterruptedException {
ArrayList<HeapTest> list = new ArrayList<>();
while (true) {
list.add(new HeapTest());
Thread.sleep(10);
}
}
}
使用命令行:
jvisualvm
图形界面:
没有VisualGC界面,可以使用插件,下载即可,重启
启动程序,查看程序的运行过程:
四、jvm内存参数设值
Spring Boot程序的JVM参数设置格式(Tomcat启动直接加在bin目录下catalina.sh文件里):
java ‐Xms2048M ‐Xmx2048M ‐Xmn1024M ‐Xss512K ‐XX:MetaspaceSize=256M ‐XX:MaxMetaspaceSize=256M ‐jar eureka-server.jar
关于元空间的JVM参数有两个:-XX:MetaspaceSize=N和 -XX:MaxMetaspaceSize=N
-XX:MaxMetaspaceSize: 设置元空间最大值, 默认是-1, 即不限制, 或者说只受限于本地内存大小。
-XX:MetaspaceSize: 指定元空间触发Fullgc的初始阈值(元空间无固定初始大小), 以字节为单位,默认21M,达到该值就会触发full gc进行类型卸载, 同时收集器会对该值进行调整: 如果释放了大量的空间, 就适当降低该值; 如果释放了很少的空间, 那么在不超 过-XX:MaxMetaspaceSize(如果设置了的话) 的情况下, 适当提高该值。这个跟早期jdk版本的**-XX:PermSize**参数意思不一样,- XX:PermSize代表永久代的初始容量。
由于调整元空间的大小需要Full GC,这是非常昂贵的操作,如果应用在启动的时候发生大量Full GC,通常都是由于永久代或元空间发生 了大小调整,基于这种情况,一般建议在JVM参数中将MetaspaceSize和MaxMetaspaceSize设置成一样的值,并设置得比初始值要大, 对于8G物理内存的机器来说,一般我会将这两个值都设置为256M。
linux中jdk8 栈内存的默认大小:1M
[root@k8s-node02 ~]# java -XX:+PrintFlagsFinal -version | grep ThreadStackSize
intx CompilerThreadStackSize = 1024 {pd product} {default}
intx ThreadStackSize = 1024 {pd product} {default}
intx VMThreadStackSize = 1024 {pd product} {default}
openjdk version "11.0.10" 2021-01-19 LTS
OpenJDK Runtime Environment 18.9 (build 11.0.10+9-LTS)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.10+9-LTS, mixed mode, sharing)
演示栈内存溢出:
public class StackOverflowTest {
//jvm设置 -Xss128k, -Xss默认1M 8倍 12548/8=1588
static int count = 0;
static void method() {
count++;
method();
}
public static void main(String[] args) {
try {
method();
} catch (Throwable e) {
e.printStackTrace();
System.out.println(count);
}
}
}
运行结果:
java.lang.StackOverflowError
at com.zengqingfa.exercise.jvm.StackOverflowTest.method(StackOverflowTest.java:15)
at com.zengqingfa.exercise.jvm.StackOverflowTest.method(StackOverflowTest.java:16)
at com.zengqingfa.exercise.jvm.StackOverflowTest.method(StackOverflowTest.java:16)
at com.zengqingfa.exercise.jvm.StackOverflowTest.method(StackOverflowTest.java:16)
1098
**结论: -Xss设置越小count值越小,说明一个线程栈里能分配的栈帧就越少,但是对JVM整体来说能开启的线程数会更多 **
JVM内存参数大小该如何设置? JVM参数大小设置并没有固定标准,需要根据实际项目情况分析