【JVM】内存结构和JVM调优
【JVM】
内存结构
Name | 线程私有/共享 | 设置参数 | Function |
---|---|---|---|
程序计数器(PC Registers) | 私有 | 固定大小 | 保存当前线程执行的方法 |
本地方法栈(Native Method Stack) | 私有 | 固定大小 | JNI调取本地方法 |
方法区(Method Area) | 共享 | -XX | static,class类结构,常量(池) |
堆(Heap) | 共享 | -Xms:初始堆值;-Xmx:最大堆值 | new出来的实例对象和数组 |
JVM栈(JVM Stack) | 私有 | -Xss | (每个线程)方法的栈帧、局部变量表、操作数栈 |
1.程序计数器(PC Registers)
- 一块小内存,每个线程都有
- PC存储当前方法:
- 线程正在执行的方法称为该线程的当前方法
- 当前方法为本地(native)方法时,pc值未定义(undefined)
- 当前方法为非本地方法时,pc包含了当前正在执行指令的地址
- 当前唯一一块不会引发OutOfMemoryError异常
2.本地方法栈(Native Method Stack)
- 存储native方法的执行信息(Java调用C程序执行代码时的信息),线程私有
(C函数就叫Native方法) - 引发的异常:
- 栈的深度超过虚拟机规定深度,就会引发*Error异常
- 无法扩展内存,OutOfMemoryError异常
- 比如:安卓开发用到的C语言,都会使用Java语言JNI,就是Java语言去调用C语言
3.方法区(Method Area)
- 存储JVM已经加载过类的结构
(类加载器:在我们程序运行的时候,需要加载很多类进来,比如rt.jar里面那些基础的类都要加载进来,对于一个JVM来说,它加载的类是有限制的,一个JVM它可能不能负担很多很多类,这个就被方法区给受限,那么方法区时存储JVM已经加载过类的结构) - 所有线程共享,所有线程加载过的类都会放在方法区里面
- 那么,运行时的常量池、类信息、常量、静态变量等
- 在Java(JVM)启动的时候呢,会创建方法区,逻辑上属于堆内存的一部分
- (当类的字节码文件被加载到内存(?这里指方法区)时,类的实例方法不会被分配入口地址,在该类创建对象后,类中的实例方法才分配入口地址(堆)。)
- 很少做垃圾回收
- 无法满足内存分配要求,OutOfMemoryError异常
- “-XX”参数设置大小
- static静态变量或方法不要定义太多,因为GC垃圾回收机制是不会回收的
(什么时候会加载几十万个类呢?比如说我们通常会将JSP编译成Servlet,那么,所有的网页页面都会被编译成一个Java类,如果一个系统里面,它的网页数量过多,它用到的Java类就会非常多,那么这个方法区的大小就会对程序的运行就有影响)
3-1.运行时的常量池(Run-Time Constant Pool)
- Class文件中常量池的运行时表示
- (每个Class里面都有一个Constant Pool,那么在运行的时候,这个类被加载进来,Class里的Constant Pool就会被映射到这块内存区域上)编译期确定的常量
属于方法区的一部分
动态性 - Java语言并不要求常量一定只有在编译期产生(不一定都是编译期就能确定的常量)
- 比如String.intern方法也可以将字符串放入运行时的常量池
引发的异常:
无法满足内存分配要求,OutOfMemoryError异常
4.堆(Heap)
- 虚拟机启动时开始创建,
- 什么时候会把数据放在堆内存上呢?
- 当我们在程序里面采用new操作,或者定义一个数组的时候,那我们都是在堆上分配内存的
- 对象实例和数组都是堆上分配内存的
- 占了太大比例,因此,这一块也是垃圾回收的主要区域:没有用的垃圾对象进行回收
- 设置大小
“-Xms”初始堆值,“-Xmx”最大堆值 - 引发的异常
无法满足内存分配要求,OutOfMemoryError异常:
1:程序消耗的性能太多,内存不够用
2:内存泄漏,有些对象没有及时地被垃圾回收
那么需要对程序进行细微的检查 - 因为线程共享,这里定义的成员变量就是共享数据,存在线程安全的问题
4-1.堆内存结构(新生代和老年代)
5.JVM栈(JVM Stack)
-
(这个变量是放在栈里面的,这个变量是放在堆里面的,实际上就是指JVM栈包含的)
-
每个线程有自己独立的一个Java虚拟机栈:有10个线程就有10个JVM栈
-
“-Xss”设置每个线程堆栈大小
-
Java方法的执行基于栈
每个方法从调用到完成对应一个栈帧在栈中入栈、出栈的过程(压栈) -
栈帧存储局部变量表、操作数栈等
局部变量表存放方法中存放在“栈”里面的东西,也就是局部变量之类的东西 -
(定义一个小int,一个byte这一类的,我们都是在JVM栈上面来定义)
-
局部变量
(在Java字节码那一章里面可以知道栈帧,我们在每一个栈帧里面,都存储着局部变量表、操作数栈等等,你把一个class文件进行反编译以后,你就会看到里面的Code区域都有很多指令,那么每个指令,实际上,都是从局部变量表里面,把数据搬到操作数栈上面处理,处理完都又把它放回局部变量表里面去) -
在多线程情况下,共享同一个局部变量,不会发生线程安全。因为栈就是每个线程都有自己的局部变量表
-
引发的异常:
栈的深度超过虚拟机规定深度,就会引发*Error异常
(使用过多的变量)无法扩展内存,OutOfMemoryError异常
JVM调优
- 一般:初始堆内存(-Xms)和最大堆内存(-Xmx)都是一致
- 初始堆内存设置比较小的话,垃圾回收机制会比较频繁去回收,这样会耗程序。
- -XMS和-XMX设置成一致,这样做的好处是可以减少程序运行时垃圾回收次数,从而提高效率
- 最大堆内存跟电脑硬件配置:一般接近电脑配置
- 操作系统分配给每个进程的内存是有限制的,譬如32位的Windows限制为2GB。
虚拟机提供了参数来控制Java堆和方法区的这两部分内存的最大值。剩余的内存为2GB(操作系统限制)减 Xmx(最大堆容量),再减MaxPermSize(最大方法区容量),程序计数器消耗内存很小,可以忽略掉
- 操作系统分配给每个进程的内存是有限制的,譬如32位的Windows限制为2GB。
- 设置新生代大小与老年代优化参数:
- "-Xmn"新生代大小,一般设为整个堆的1/3到1/4左右
- “-XX:SurvivorRatio”:设置新生代中edenhe from/to空间的比例关系n/1
- “-XX:NewRatio”:老/新
- 需求:可能会有很多经常使用的对象:这时,老年代越大越好,新生代就越小,尽量减少老年代的GC
- 需求:有些对象不怎么经常使用,但是使用新对象特别多。这时新生代要大点好