- 虚拟机大致分为系统虚拟机(vmware,可运行完整操作系统的软件平台)和程序虚拟机(jvm,专门为执行当个计算机程序而设计),在上面运行的软件都被限制于虚拟机提供的资源中
- 虚拟机的基本结构
虚拟机基本结构.png
1.类加载子系统:负责从文件系统或网络中加载class信息,存放到方法区,也就是一块内存空间
2.*方法区:存放类信息,常量信息,常量池信息,包括字符串字面量和数字常量等
3.*java堆:jvm启动的时候建立java堆,java程序最主要的内存工作区域,几乎所有的对象实例都放在java堆中,堆空间是所有线程共享的,
4.直接内存:java的nio库允许java程序使用直接内存,从而提高性能,通常直接内存速度会优于java堆,读写频繁的场合可能会考虑使用
5.*java栈:每个虚拟机线程都有一个私有的栈,线程栈在线程创建的时候被创建,线程栈中保存着局部变量,方法参数,java的方法调用,返回值等等
6.本地方法栈和java栈非常类似 ,但是本地方法栈用于java调用本地方法(通常使用c编写)
7.*java 自己的一套进行垃圾清理的机制,稍后详细说明
8.pc寄存器,每个线程的私有空间,jvm会为每个线程创建pc寄存器,在任意时刻,一个java线程总是在执行一个方法,这个方法称为当前方法,如果当前方法不是本地方法,pc寄存器就会执行当前正在被执行的指令,如果是本地方法,pc寄存器值为undefined,pc寄存器存放如当前执行环境指针,程序计数器,操作栈指针,计算的变量指针等
9.*虚拟机最核心的组件,负责执行虚拟机的字节码,一般会先进行编译成机器码后执行
带*的是重要部分
类加载之后一些信息(类信息,静态信息被存放于方法区中,快永久区Perm)
类被实例化之后,被存储到java堆中,一块内存空间
当我们使用对象的时候,其实用的是这块内存空间的引用
这个引用存放在java栈中
image.png
- java堆详解
- 几乎所有的对象都放在其中,java堆是自动化管理的,通过垃圾回收机制,垃圾对象会自动清理,不需要显示的释放
- 根据垃圾回收机制的不同,java堆可能会有不同的结构,最常见的是将整个java堆分为新生代和老年代,新生代存放新生的对象或者年龄不大的对象,老年代则存放老年对象
- 新生代分为eden区,s0区,s1区,s0和s1区也被称为from和to区域,他们是俩块大小相等并且可以互换角色的空间
- 绝大多数情况下,对象首先分配在eden区,在一次新生代回收后,如果对象还存活,则会进入s0或者s1区(或者的原因是在任意时间s0和s1只能有一块在使用),之后每经过一次新生代回收,如果对象存活则它的年龄就加1,当对象达到一定年龄(阈值)后,则进入老年代(tenured区,表明在一定考量后,这个数据一直被引用,老年区的数据不会被频繁的回收,新生代则会被频繁回收)
- s0和s1区存在的意义在于,这俩块区域的垃圾回收使用的是复制算法,核心思想是将内存空间分为俩块,每次只使用其中一块,垃圾回收时,将正在使用的内存中的存留对象复制到未被使用的内存块中去,之后去清除之前正在使用的内存块中的所有对象,反复去交换俩个内存的角色,完成垃圾收集。
- java 栈
- 线程私有的内存空间,一个栈一般有三部分组成:局部变量表,操作数栈和帧数据区
- 局部变量表:用于保存函数的参数及局部变量
- 操作数栈:主要保存计算过程的中间结果,同时作为计算过程中变量临时的存储空间
- 帧数据区:除了局部变量表和操作数栈之外,栈还需要一些数据来支持常量池的解析,帧数据区保存着常量池的指针,方便程序访问常量池,此外,当函数返回或者出现异常时,虚拟机必须有一个异常处理表,方便发送异常的时候找到异常的代码,因此异常处理表也是帧数据区的一部分。
- java方法区
- 和堆一样,方法区是一块所有线程共享的内存区域,它保存系统的类信息,比如类的字段,方法,常量池等,方法区的大小决定了系统可以保存多少个类,如果系统定义太多的类,导致方法区溢出。虚拟机同样会抛出内存溢出错误。方法区可以理解为永久区(Perm)
- 虚拟机参数
- 围绕堆,栈,方法区,垃圾回收机制 配置
- 堆分配参数:
- -xx:+PrintGC 虚拟机启动后,只要遇到GC就会打印日志
- -xx:+UseSerialGC 配置串行回收器
- -xx:+PrintGCDetails gc时可以查看详细信息,包括各个区的情况
- -Xms java程序启动时初始堆大小
- -Xmx java程序能获得的最大堆大小
- -Xmn 设置新生代大小,这个参数对gc有较大影响,一般设置为整个堆大小的三分之一到四分之一左右
- -XX:SurvivorRatio 设置新生代中eden空间和from或to空间的比例,如果等于3,说明eden区比上from或to区为3:1
- -XX:NewRatio 设置新生代和老年代的比例
- -XX:+PrintCommandLineFlags 可以将虚拟机的参数输出
- 总结,实际工作中可以将初始堆大小和最大堆大小设置相等,这样可以减少程序运行时的垃圾回收次数,从而提高性能
-
GCdetails解读
GCdetails- def new generation 新生代,分为eden区,from区,to区,后面几列是总空间,使用空间百分比,最后是内存地址
- tenured generation 老年代,后面几列是总空间,使用空间百分比,最后是内存地址
- compacting perm gen 永久区(perm)
- 堆溢出处理
- 如果堆空间不足,则会抛出内存溢出的错误(Out Of Memory,OOM),这类问题如果发生在生产环境,可能会引起严重的业务中断.
- -XX:HeapDumpOnOutOfMemoryError,使用该参数可以再内存溢出时导出整个堆信息
- -XX:HeapDumpPath 设置导出堆信息的存放路径,与上面的参数配合使用
- 例子:
-Xms1m -Xmx1m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/Test03.dump - dump文件分析工具
- 分析方法
- 代码示例
public static void main(String[] args) { //-Xms1m -Xmx1m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=d:/Test03.dump //堆内存溢出 Vector v = new Vector(); for(int i=0; i < 5; i ++){ v.add(new Byte[1*1024*1024]); } }
- 栈参数配置
- -Xss 指定单个线程的最大栈空间,栈空间的大小直接决定了函数可调用的最大深度
- 代码示例
public class Test04 { //-Xss1m //-Xss5m //栈调用深度 private static int count; public static void recursion(){ count++; recursion(); } public static void main(String[] args){ try { recursion(); } catch (Throwable t) { System.out.println("调用最大深入:" + count); t.printStackTrace(); } } }
- 方法区参数配置
- 方法区保存系统的类信息
- 默认情况下,-XX:MaxPermSize 为64MB,如果系统运行时产生大量的类,就需要设置一个相对合适的方法区,以免永久区内存溢出的问题
- -XX:PermSize 初始化方法区大小
- -XX:MaxPermSize 方法区最大大小
- 直接内存参数配置
- -XX:MaxDirectMemorySize,如果不设置,默认值为最大堆空间,即-Xmx,直接内存使用达到上限时,会触发垃圾回收,如果不能有效的释放空间,也会引起系统的OOM,
- jdk1.7之后,不再关注这个参数,配不配都没什么大影响
- Client和Server虚拟机工作模式
- 只有在jdk1.7之前,jdk才分这俩个模式,1.7之后不再提供client模式,使用-client参数使用Client模式,使用-server使用Server模式,
- 区别
- Client模式启动较快,适用于测试,不追求系统的长时间使用性能
- Server模式启动较慢,会对虚拟机的性能进行负载的系统性能信息收集和使用更复杂的算法对程序进行优化,长期运行性能远远快于client模式
- 关于jvm的博客参考