浅入理解JVM
JVM全称java虚拟机。HelloWorld.java(源代码) ->Hello.class->010101(2进制编码),class文件和2进制编码需要在JVM中运行。
jvm运行时的数据区:
蓝色的是数据区,绿色的是指令区。
程序计数器:一块较小的内存空间,指向当前线程所执行的字节码指令的地址和行号,每条线程都有一个独立的程序计数器。唯一不会OOM的区域。
虚拟机栈:线程私有,存储当前运行方法所需要的数据、指令、返回地址。
每个方法在执行的同时都会创建一个栈帧(Stack Frame)用于存储局部变量表、操作数栈、动态链接(比如存储的一个接口,运行时会解析找到实现类)、方法出口等信息。一个方法对应一个栈帧。局部变量表存放了各种基本类型、对象引用和returnAddress类型(指向了一条字节码指令地址)。其中64位长度long 和 double占两个局部变量空间,其他只占一个。可能出现2种异常:1.线程请求的栈的深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;2.如果虚拟机可以动态扩展,如果扩展时无法申请到足够的内存,就抛出OutOfMemoryError异常。设置JVM参数”-Xss228k”(即栈大小为228k)。
本地方法栈:和Java虚拟机栈很类似,不同的是本地方法栈为Native方法服务。在HotSpot虚拟机实现中是把本地方法栈和虚拟机栈合二为一的。同理也会抛出StackOverflowError和OutOfMemoryError。
Java堆:是Java虚拟机所管理的内存中最大的一块。由所有线程共享,在虚拟机启动时创建。堆区唯一目的就是存放对象实例。堆中可细分为新生代和老年代,新生代又可分为Eden空间、From Survivor空间、To Survivor空间(8:1:1)。堆无法扩展时,抛出OutOfMemoryError异常。设置JVM参数” -Xms20M -Xmx20M“(前者表示初始堆大小20M,后者表示最大堆大小20M)。
方法区:所有线程共享,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。当方法区无法满足内存分配需求时,抛出OutOfMemoryError。
另外一个说法——永久代(Permanent Generation),呼应堆的新生代和老年代。方法区和堆的划分是JVM规范的定义,而不同虚拟机有不同实现,对于Hotspot虚拟机来说,将方法区纳入GC管理范围,这样就不必单独管理方法区的内存,所以就有了”永久代“这么一说。设置JVM参数为”-XX:MaxPermSize=20M”(方法区最大内存为20M)。
在JDK1.7之前运行时常量池逻辑包含字符串常量池存放在方法区, 此时hotspot虚拟机对方法区的实现为永久代
在JDK1.7 字符串常量池被从方法区拿到了堆中, 这里没有提到运行时常量池,也就是说字符串常量池被单独拿到堆,运行时常量池剩下的东西还在方法区, 也就是hotspot中的永久代
在JDK1.8 hotspot移除了永久代,取而代之的是”元空间(Metaspace)“,所以在JDK8中虚拟机参数”-XX:MaxPermSize”也就没有了任何意义,取代它的是”-XX:MetaspaceSize“和”-XX:MaxMetaspaceSize”等, 这时候字符串常量池还在堆, 运行时常量池还在方法区, 只不过方法区的实现从永久代变成了元空间(Metaspace) 。
直接内存:并不是虚拟机运行时数据区的一部分,也不是Java虚拟机规范中定义的内存区域。JDK1.4加入了NIO,引入一种基于通道与缓冲区的I/O方式,它可以使用Native函数库直接分配堆外内存,然后通过一个存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作。因为避免了在Java堆和Native堆中来回复制数据,提高了性能。当各个内存区域总和大于物理内存限制,抛出OutOfMemoryError异常。
Question: 为什么堆的新生代三个区域的大小比为8:1:1?
因为新生代使用复制回收算法。每次用来存放对象的是Eden区和其中一块Survivor区。当回收时,将Eden区和Survivor from中还存活着的对象一次性复制到另一块Survivor to区,复制算法所需要的担保内存为9:1。之所以Eden区:Survivor from区是8:1,是因为两个Survivor区的from和to区会进行位置交换。MinorGC后哪个区被清空没有对象了,这个区就会成为to区,而通过复制算法复制的还存活下的对象所在的那个区,也就是有对象的区即为from。(PS:新生代中Survivor to区内存不够用时,会触发老年代的担保机制进行分配担保。新生代与老年代默认内存比为1:2)
总结一下就是:
- 用来担保复制回收算法执行的内存为9:1。(留出一片空地)
- S1:S2应为1:1,可用内存中Eden:S1区为8:1。所以新生代三个区比为8:1:1。(使用的内存中还有一块要成为下次的空地)
JVM基础的参数和指令
jvm三种参数类型:
- 标配参数(java -version等)
- X参数(-Xint解释执行,-Xcomp第一次使用就编译成本地代码,-Xmixed混合模式先编译再执行)
- XX参数(分为Boolean和KV类型)
jvm常见命令和参数:
java -XX:+PrintFlagsInitial ——查看所有默认参数(:=说明被人工修改)
jps -l ——查看正在运行中的进程号
jinfo -flag 参数 进程号 ——查看某进程时候开启某布尔型参数
jinfo -flags 进程号 ——查询系统参数(Non-defauls)和人工参数(Command line)
-XX:+Boolean参数 ——表示开启某布尔型参数
-XX:-Boolean参数 ——表示关闭某布尔型参数
-XX:KV参数=??? ——表示设置KV类型参数为??
from ??? to ??? ——表示从初始默认值到自我期望值(最好一致)
-Xss: 初始栈内存大小 (等价于-XX:ThreadStackSize)
-Xms: 初始堆内存大小 -Xmx:最大堆内存大小(等价于InitialHeapSize 和XX:MaxHeapSize )
java -XX:+PrintCommandLineFlags -version ——一种查看配置的方式,可以查看默认使用哪种垃圾回收器
-XX:SurvivorRatio ——调整新生代eden区对from区和to区的倍数,默认为8:1:1(例如设置SurvivorRatio=6,比例为6:1:1)
-XX: NewRatio ——调整堆中老年代对新生代的占比,默认为2:1(例如设置为NewRatio=3,比例为3:1)
-XX:MaxTenuringThreshold ——设置升到老年代的最大年龄(默认15,新生区15次gc)
例如:
-XX:+PrintGCDetails
-XX:MaxTenuringThreshold=15 ——设置元空间大小为128m
-XX:MetaspaceSize=128m ——设置元空间大小为128m
-XX:InitialHeapSize ——初始化堆内存(默认本机内存的64分之1)
-XX:MaxHeapSize ——最大堆内存(默认本机内存的4分之1)
-XX:ThreadStackSize ——单个线程栈的大小,查出来单位是k,其他都是b(windows根据本机内存大小一般默认为512k~1024k,linux64为1024k)
利用jps -l 和 jinfo -flag 组合查看某进程的某参数是多少,例如查看元空间大小
在VmOptions设置:-XX:MetaspaceSize=128m
再次查看,元空间大小变成了128m
jinfo -flags pid