文章目录
JVM笔记
基础知识
-
使用javac,编译**.java**文件为字节码文件,字节码文件是16进制的文件
javac UserItemController.java
Tip: 可以加入-d参数,来指定生成字节码的位置,不指定则默认当前文件路径
-
使用javap,将**.class**文件通过反编译,获得16进制对应的jvm指令
javap -verbose -c UserItemController.class
Tip: 加入-verbose参数可以展示出更多的信息,比如比较关键的类的常量池,也叫class 文件常量池(constant pool,也简称cp)。JVM运行期间的运行时常量池就是由一个个类的class 常量池组成的。在类里的各个方法的调用,也都是使用jvm指令+常量池编号的方式进行调用
-
当一个JVM线程被创建时,每个线程都有属于它自己的PC计数器,同时会创建一个属于这个线程的栈,栈的定义可以参考官方文档,栈里面有存储着帧,当一次线程调用过程中需要调用N个方法,那么调用每一个方法时,都会创建一个帧,当该方法正常调用结束,或者出现异常,则帧被销毁。
-
帧的官方定义
-
帧包含局部变量数组(也叫局部变量表)、操作数栈,这两个东西的大小在编译期间就确定了
- 局部变量表中存储着当前帧(也就是当前调用的这个方法)的参数,如果该方法是一个构造方法或者是实例方法(就是通过一个类的对象调用这种方式,A a = new A(); a.get()😉,那么index0存的是当前类的引用this.为什么第一位置必须存当前类的this引用,因为访问当前类的实例方法时使用this.
- 局部变量表中存储着当前帧(也就是当前调用的这个方法)的参数,如果该方法是一个构造方法或者是实例方法(就是通过一个类的对象调用这种方式,A a = new A(); a.get()😉,那么index0存的是当前类的引用this.为什么第一位置必须存当前类的this引用,因为访问当前类的实例方法时使用this.
-
-
运行时数据区域(官方称为为run-time data areas)包括以下
- 程序计数器-pc Register
- java虚拟机栈-jvm Stacks
- 堆-Heap。所有线程共享
- 方法区-Method Area。所有线程共享
- 运行时常量池-Run-Time Constant Pool
Tip: 官方文档在2.5小节中将以上部分都视为同一级别来介绍,但是在文档中又做了一些说明,如:方法区逻辑上是堆的一部分,方法区中又包含了运行时常量池
注意:上面所说的堆-Heap,目前基本都称作JVM上逻辑的堆,因为Java官方上的Heap并没有明确定义逻辑上的堆。只是一般的不同厂家的JVM实现,将逻辑上的堆又分为了:
堆-GC Heap和非堆-Non-Heap。划分的依据是,程序中对象的创建、回收对象基本都在GC heap上。
至于Non-Heap 里放置是一般都不会改变的东西,metaspace下放置的就是永久代(JDK8已经没有了永久代的说法),还有上面说的方法区、运行池常量池都被移动到了metaspace中
常用启动参数
官方标准参数,参数的设置无非是两种。
boolean类型和=类型,boolean类型参数使用+/-来进行开启/关闭。
如:-XX:+HeapDumpOutOfMemoryError,开启堆内存溢出自动转储堆快照
=类型则是设值用的。
如:-XX:initialHeapSize=4g,设置堆初始大小为4g
-
设置初始堆内存,如果后面跟数字没有任何单位,则默认为byte字节,单位可以为k/K,m/M,g/G
-XX:InitialHeapSize=4g
-
设置最大堆内存,如果后面跟数字没有任何单位,则默认为byte字节,单位可以为k/K,m/M,g/G
-XX:MaxHeapSize=4g
-
设置创建线程时,线程栈的大小。上面基础知识说过,创建线程的时候会同时为该线程创建一个线程栈.在64位的机器上,默认每个线程栈是1m
-XX:ThreadStackSize=1m
-
设置年轻代初始堆内存,官方建议设置为整个堆内存的一半、或者四分之一。年轻代内存太小会导致大量的minor GC.如果太大,当full GC时,清理内存的时候会比较浪费时间
-XX:NewSize=2g
-
设置年轻代最大堆内存
-XX:MaxNewSize=2g
-
设置年轻代中Eden区域和每一个Survivor区域(from、to)所占的比例.默认值是8
-XX:SurvivorRatio=4
-
开启 打印GC日志细节、GC发生时间。一般和非官方标准的第四条一起使用
-XX:+PrintGCDetails -XX:+PrintGCDateStamps # 一般设置了下面的第五条,会默认开启下面的设置 -XX:+PrintGCTimeStamps
-
开启当堆内存溢出时自动dump堆快照
-XX:+HeapDumpOutOfMemoryError
-
保存堆快照到文件中
-XX:HeapDumpPath=/home/java_pid%p.hprof
-
分代回收时,设置年轻代的的提升阈值,对象经历过15次young GC之后,迁移到老年代
# 如果设置为0,则GC之后存活的对象直接迁移到老年代
-XX:MaxTenuringThreshold=15
-
开启串行GC回收算法
-XX:+UseSerialGC
-
开启并行GC回收算法
-XX:+UseParallelGC
-
开启CMS回收算法
-XX:+UseConcMarkSweepGC
-
开启G1回收算法,G1是Garbage-First的简称
-XX:+UseG1GC
非官方标准参数,但是等同于官方参数的缩写
-
设置初始堆内存大小,-xms后面直接跟数字+单位,如果后面跟数字没有任何单位,则默认为byte字节,单位可以为k/K,m/M,g/G
-Xms4g
-
设置最大堆内存,-xmx后面直接跟数字+单位,如果后面跟数字没有任何单位,则默认为byte字节,单位可以为k/K,m/M,g/G
-Xmx4g
-
设置线程的线程栈大小,-xss后面直接跟数字+单位,64为的机器默认为1m
-Xss1m
-
保存GC日志到文件中,一般和官方标准的第四条一起使用。
-Xloggc:/var/log/gclog/gc.log
-
设置年轻代的初始堆内存和最大堆内存大小
# 相当于同时设置了 -XX:NewSize=2g -XX:MaxNewSize=2g -Xmn2g
以上所有参数,以及参数说明,均可在附录中的JVM参数链接中找到
参数简单测试
问:JVM的参数为 -Xms2g -Xmx4g -Xss512k -Xmn1g -XX:SurvivorRatio=3,将上述非标准参数,转为JVM标准参数表示.并解释什么含义
答: -XX:InitialHeapSize=2g -XX:MaxHeapSize=4g -XX:ThreadStackSize=512k --XX:NewSize=1g -XX:MaxNewSize=1g -XX:SurvivorRatio=4.设置初始堆内存为2g,最大堆内存为4g,线程栈大小为512k,年轻代初始所占的堆内存为1g,年轻代最大占的堆内存为1g,年轻代中Eden和Survivor比例为3:1
问:对于上述参数,年轻代中Eden和Survivor个占用具体多少内存
答:年轻代最大所占的堆内存为1g,Eden和Survivor所占比为3:1,即一个Eden/一个Survivor=3.年轻代中有两个Survivor。则1g/(3+1+1)=200m,则Eden = 200 x 3 =600, Survivor = 200 x 2 = 400
对于最大堆内存(-xmx/-XX:MaxHeapSiz=)配置多少合适?
去掉系统或者容器自身用的内存,剩余的可用内存的70%-80%,如果系统中还有其他应用使用内存,则还要降低点。注意,因为Java进程本身的内存占用是不计算在xmx范围内的,所以计算剩余的可用内存的时候,要注意Java进程和其他进程占用的系统内存大概使用了是多少。反例:服务器物理内存8G,出去系统本身可能还有7.5G,如果没有特别多的其他进程,则可以使用7.5G*0.8=6G。如果其他进程很多,就要降低6g这个值了,防止影响其他进程
初始堆内存(-xms/-XX:InitialHeapSize=)和最大堆内存(-xmx/-XX:MaxHeapSize=)要不要配置成一致的?
一般建议配置成一样的。防止初始内存不够,扩容时带来的内存抖动(最后一句话,待查阅资料佐证)
工具
查看字节码工具
在IDEA下安装jclasslib插件,可以很方便的看到当前类的字节码结构,以及很方便的看到各个方法的中具体执行的JVM指令。点击具体的JVM指令,会自动跳转到官方JVM指令的文档,很方便的看到当前指令的作用是干嘛的
附录说明
-
所有Java命令都可以使用 命令 -help 的形式,来查看帮助。Mac下的提示都是中文的,很友好
例如:
# 查看javap反编译工具的使用 javap -help # 查看javac编译工具的使用 javac -help
-
jrebel上的文章,为什么要知道Java字节码,写的不错,还可以在jreble官网的Blog板块搜索java byte code,查看更多有关的博文
疑问
- 当web请求进入Tomcat之后,具体到调用controller的之后,对于JVM来说,进入Tomcat的那一个开始是不是就算创建了一个JVM线程?对于后面的Tomcat->controller->service->mapper都只不过是该线程上的某一个栈帧?
什么是内存泄漏?
Java 中的内存泄漏,就是那些逻辑上不再使用的对象,却没有被 垃圾收集程序给干掉。从而导致垃圾对象继续占用堆内存中,逐渐堆积,最后产生 java.lang.OutOfMemoryError: Java heap space
错误
而内存溢出,则是内存不够用,导致无法分配足够的内存空间,而发生的错误。
其他
Java程序中生成线程,实际会请求操作系统创建一个操作系统级别的线程。所以,这些线程并不会占用Java中的堆内存,而是在操作系统层面分配内存。