1. JVM内存结构
所有线程都会用到堆,和方法区
而每个线程会用到各自的虚拟机栈、本地方法栈、程序计数器
堆内存是JVM中空间最大的区域,分为新生代、老年代和永久代,而JDK1.8以后,元空间代替了永久代,并且不再是堆内存的一部分,而是本地内存
新生代分为eden和survivor,而survivor又分为from survivor和to survivor
JDK8以后,堆内存就分为新生代和老年代了
Xmn、Xms、Xmx、Xss都是JVM对内存的配置参数,我们可以根据不同需要区修改这些参数,以达到运行程序的最好效果。
-Xms 堆内存的初始大小,默认为物理内存的1/64
-Xmx 堆内存的最大大小,默认为物理内存的1/4
-Xmn 堆内新生代的大小。通过这个值也可以得到老生代的大小:-Xmx减去-Xmn
-Xss 设置每个线程可使用的内存大小,即栈的大小。在相同物理内存下,减小这个
值能生成更多的线程,当然操作系统对一个进程内的线程数还是有限制的,不能无
限生成。线程栈的大小是个双刃剑,如果设置过小,可能会出现栈溢出,特别是在
该线程内有递归、大的循环时出现溢出的可能性更大,如果该值设置过大,就有影
响到创建栈的数量,如果是多线程的应用,就会出现内存溢出的错误。
另外linux当前用户下,对句柄和线程数都有限制,可参考https://blog.51cto.com/qiangsh/2132763
例如查看 当前JVM的线程XSS大小
java version “1.8.0_131”
Java™ SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot™ 64-Bit Server VM (build 25.131-b11, mixed mode)
[liuqiang6@mu02 bin]$ ^C
[liuqiang6@mu02 bin]$java -XX:+PrintFlagsFinal -version | grep ThreadStackSize
intx CompilerThreadStackSize = 0 {pd product}
intx ThreadStackSize = 1024 {pd product}
intx VMThreadStackSize = 1024 {pd product}
java version “1.8.0_131”
Java™ SE Runtime Environment (build 1.8.0_131-b11)
Java HotSpot™ 64-Bit Server VM (build 25.131-b11, mixed mode)
2. 虚拟机栈
虚拟机栈是线程独享的,当创建一个线程的时候,就会创建一个虚拟机栈;
虚拟机栈由栈帧组成
每一次的方法调用,都会创建一个栈帧,然后去压栈,当方法返回的时候,就执行出栈操作。
也就是说每调用一个方法的时候,都会往里面压入一个元素。
栈帧里面又存放这一系列的数据,第一是局部变量表,第二是操作数栈。
方法里面的代码在执行的时候会从局部变量表或者对象实例的字段里面,赋值变量或者常量,然后放到操作数栈里面。
当操作的时候,会使用一系列的指令往操作数栈中存入数据或者取走数据进行计算。可以认为操作数栈是存放临时数据的地方。
另外栈帧中还包括:执行运行时常量池的引用,方法返回地址以及动态链接等。
3. 本地方法栈和程序计数器
本地方法栈和虚拟机栈类似,虚拟机栈管理的是java方法,而本地方法栈管理的是native方法。
例如unsafe类都是本地方法,本地方法都是c语言实现的。
程序计数器用来记录各个线程执行的字节码的地址。
java是多线程的语言,当线程的数量超过cpu核心数量的时候,线程之间就会根据时间片去争抢cpu资源
4. 方法区
方法区也是多个线程之间共享的
方法区主要包括了类信息、运行时常量池、静态变量、字符串常量池。
方法区主要是存放jvm加载的类信息。
从如上图可以看出,包括三种常量池,静态常量池,运行时常量池,字符串常量池。
这些常量池都是用来做什么的呢?
常量池–静态常量池:
存放字面量:例如文本字符串,final修饰的常量;
存放符号引用:例如类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。
常量池–运行时常量池:
当类加载到内存中后,jvm就会将静态常量池中的内容存放到运行时的常量池中,运行时常量池里面存储的主要是编译期间生成的字面量、符号引用等等。
常量池–字符串常量池
字符串常量池,也可以理解成运行时常量池中分出来的一部分,类加载到内存的时候,字符串,会存放到字符串常量池里面。
方法区和堆是存在交接的,静态变量和字符串常量池是存放在堆里面,而类信息和运行时常量池是存放在元空间里面。
由于1.8以前的jvm将加载的类都存放在持久代,如果需要加载的类很多,但是设置的持久代小的话,就容易造成溢出,而如果设置的太大又会导致内存上的浪费;所以把持久代去掉了,用元空间代替。
用元空间代替有什么好处呢?
元空间是本地内存,理论上取决于操作系统可以分配的内存大小,这样就解决了持久代空间难以分配的问题。
程序系统,会将jvmTest和demo的class加载到方法区中,在栈中放入局部边浪demo,并引用对内存中的对象。
在执行每个方法的时候,就会进行压栈和出栈的操作。
当调用demo.printName的时候,会创建一个栈帧,压到栈中,当调用完毕后,栈帧会从栈中弹出来。
5. 方法区包含哪些内容
存放数据(static,final,Class,常量池)
方法区存储的是每个class的信息:
1.类加载器引用(classLoader)
2.运行时常量池
所有常量、字段引用、方法引用、属性
3.字段数据
每个方法的名字、类型(如类的全路径名、类型或接口) 、修饰符(如public、abstract、final)、属性
4.方法数据
每个方法的名字、返回类型、参数类型(按顺序)、修饰符、属性
5.方法代码
每个方法的字节码、操作数栈大小、局部变量大小、局部变量表、异常表和每个异常处理的开始位置、结 束位置、代码处理在程序计数器中的偏移地址、被捕获的异常类的常量池索引
特点
1.方法区是线程安全的。由于所有的线程都共享方法区,所以,方法区里的数据访问必须被设计成线程安全的。例如,假如同时有两个线程都企图访问方法区中的同一个类,而这个类还没有被装入JVM,那么只允许一个线程去装载它,而其它线程必须等待。
2.方法区的大小不必是固定的,JVM可根据应用需要动态调整。同时,方法区也不一定是连续的,方法区可以在一个堆(甚至是JVM自己的堆)中自由分配。
3.方法区也可被垃圾收集,当某个类不在被使用(不可触及)时,JVM将卸载这个类,进行垃圾收集
代码缓存 个缓存区域是用来存储编译后的代码。编译后的代码就是本地代码(硬件相关的),它是由JIT(Just In Time)编译器生成的,这个编译器是Oracle HotSpot JVM所特有的。