JDK(java develop kit,java开发工具包)包含开发需要环境工具,编译(javac:java compiler)反编译工具(javap:可以反编译,也可以查看java编译器生成的字节码),监控工具和JRE
jdk对javap的解释
JRE(java runtime environment,java运行环境)包含JVM和运行java程序所需环境和JVM
JVM(java Java Virtual Machine,java虚拟机)可以理解成一个在电脑中虚构的机器,真正执行java程序的机器,可以跨平台(windows,linux,unix)等的核心所在
class文件为java文件编译后形成的二进制字节码文件
根据其中内容可以清楚它是用来描述类的信息,因为没有非常必要理解,大致理解其内容即可,深入理解可在官网
javaVirtualMacineTechnology
1>加载:将class文件读取到jvm,创建一个表示该类型的java.lang.Class类的实例,作为方法区这个类的各种数据的访问入口
2>验证:验证class文件合法(通过class头的描述信息验证),主要验证内容为文件格式、元数据、字节码、符号引用。可以通过设置参数略过。
3>准备:准备方法和变量的空间,设置为初始值(0值)。这些变量使用的内存都将在方法区中进行分配。这时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。
4>解析:将符号引用(java描述中的变量名)改为直接引用(jvm内地址信息)
5>初始化:给static变量赋值,此处按照从父到子,从上到下的原则,有个例子CSDN颜群一道JVM面试题,答错率超90%
6>使用:对象的初始化、对象的垃圾回收、对象的销毁
7>卸载
Sun Classic VM
已经淘汰,是世界上第一款商用虚拟机,只能使用纯解释器(没有JITJust in time编译器)的方法来执行Java代码。
Exact VM
Exact Memory Management 准确式内存管理;
编译器和解释器混合工作以及两级即时编译器。
HotSpot VM
热点代码技术,使用最多的虚拟机产品,并非由Sun公司开发。官方JDK均采用HotSpot VM。
KVM
kilobyte 简单、轻量、高度可移植,在手机平台运行,运行速度慢。
JRockit
BEA公司开发,是世界上最快的Java虚拟机,专注于服务端应用,全部靠编译器执行。
J9
IBM开发 原名:IBM Techn0ology for Java Virtual Machine IT4j
Davik
不是java虚拟机,寄存器架构而不是栈结构,执行dex(dalvik Executable)文件。
Microsoft JVM
只能运行在windows下面。
Azul VM Liquid VM
高性能的java虚拟机,在HotSpot基础上改进,专用的虚拟机。
Taobao VM
淘宝公司开发
虚拟机栈用于存放虚拟机线程信息,用于加载类
1、栈帧:可以匹配上eclipse中断点的栈,每个栈帧会有自己的局部变量表、动态链接、方法出口
2、局部变量表大小在编译时决定
存储类的方法,接口,版本,字段。其中有常量池存储常量信息,比如字符串和数字等类中的常量
调用的native方法(C语言方法)服务
占用较小分配空间,记录栈方法的动态地址,native方法为undefined。cpu分时分段工作,重新切换回具体方法时,不从头执行方法,继续栈帧里面的具体行执行可以理解为程序计数器的作用
堆分为新生代和老年代,HotSpot有永久代
1、新生代:分为eden(伊甸园)和survivor(幸存者区),一般为老年代的一半大小,survivor分为两个相等大小区(用于支持复制算法GC)
2、老年代:old,一般为堆内存大小的2/3(参数可控制),用于存放survivor区中超过年龄数的变量(参数可控制),或者过大对象可直接存放在老年代(参数可控制),老年代一般使用标记整理算法
3、永久代:
指内存的永久保存区域,主要存放Class和Meta(元数据)的信息,Class在被加载的时候被放入永久区域. 它和和存放实例的区域不同,GC不会在主程序运行期对永久区域进行清理。所以这也导致了永久代的区域会随着加载的Class的增多而胀满,最终抛出OOM异常。在Java8中,永久代已经被移除,被一个称为“元数据区”(元空间)的区域所取代。
new对象会分配到堆内存中,首先分配给eden,若变量过大,有可能通过分配担保机制直接分给old,eden满了之后会进行一次minorGC,之后将仍然存活的对象分配到survivor,老年代通过majorGC和fullGC清理
经历过多次minorGC后的对象,会从survivor中分配给老年代
空间分配担保
-XX:+HandlePromotionFailure 开启
-XX:-HandlePromotionFailure 禁用
取之前每一次回收晋升到老年代对象容量的平均值大小作为经验值,与老年代的剩余空间进行比较,决定是否FullGC来让老年代腾出更多空间。
1、引用计数法
根据被引用的计数判断,若引用数为0则可以回收,问题缺陷:两个变量互相引用,则均为1,其他环状引用都会导致此问题
2、root根可达性分析
根据GCroot的引用表,向外找引用,未被引用的对象或对象环着表示可以清理
可作为GCRoot的对象根节点
1、复制算法
survivor中主要算法,回收可回收对象后,将其余内存空间整体复制到surviivorTo中
现在的商业虚拟机都采用这种收集算法来回收新生代,IBM公司的专门研究表明,新生代中的对象98%是“朝生夕死”的,所以并不需要按照1:1的比例来划分内存空间,而是将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性的复制到另外一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性的复制到另外一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。HotSpot虚拟机默认Eden和Survivor的大小比例是8:1,也就是每次新生代中可用内存为整个新生代容量的90%(80%+10%),只有10%的内存会被“浪费”。
2、标记清除算法
用可达性分析标记可回收的对象,然后清理,此算法会造成产生内存碎片,导致无法存储大变量引发GC
3、标记整理算法
old区主要算法,标记后整体整理排序,使剩余空间连续,相比其他算法效率慢
4、垃圾收集器
1、若创建一个类对象,实例在堆内存中创建,类的方法和静态变量创建在方法区
2、jvm参数
iteye leichenlei jvm参数(全)
3、堆内存设置的大,fullGC时间也就会随之增长,30G内存fullGC一次4s算比较正常
4、思维导图
1、字符串长度方式溢出:
测试代码:
private static void testOldHeap() {
List<String> list = new ArrayList<String>();
String s = "ssa";
list.add(s);
while(true){
list.add(s+list.toString());
}
}
测试原理:通过不断扩大list的大小和长度,以及单个list节点的长度试图塞满整个old区溢出
测试结果:成功,抛出 java.lang.OutOfMemoryError: Java heap space
2 数组长度大小方式溢出:
测试代码:
private static void testOldHeap2() {
List<String> list = new ArrayList<String>();
String s = "ssa";
list.add(s);
while(true){
list.add(s);
}
}
测试原理:通过不断扩充list大小,将堆内存塞满,引起OutOfMemory
测试结果:成功 1
测试代码:
package testJVM;
public class BlowUpJVM {
public void testHeapDeepOOM(){
this.testHeapDeepOOM();
}
}
// 堆深度溢出
BlowUpJVM a = new BlowUpJVM();
a.testHeapDeepOOM();
测试原理:通过不断调用自身构造方法创造递归深度
测试结果:成功抛出java.lang.StackOverflowError
测试代码:
private static void testStockOOM() {
while (true) {
Thread thread = new Thread(new Runnable() {
public void run() {
while (true) {
}
}
});
thread.start();
}
}
测试原理:通过不断的创建线程,使虚拟机栈塞满溢出
测试结果:发现大概jdk1.7之后,此区域放在了直接内存区(系统内存),导致直接将电脑内存占满,然后死机了
付上完整代码:
package testJVM;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class testMain {
/**
* @param args
*/
public static void main(String[] args) {
// jdk1.8
// JVM栈内存溢出 此方法极度消耗内存,会死机
// testStockOOM();
// 堆深度溢出 java.lang.StackOverflowError 堆存储方法,所以堆内存溢出最简单的方法就是递归调用自身
// BlowUpJVM a = new BlowUpJVM();
// a.testHeapDeepOOM();
// 老年代堆满 java.lang.OutOfMemoryError: Java heap space
// testOldHeap();
testOldHeap2();
//动态代理类放在了堆内存中,会被回收,没办法溢出
// testPergemOutOfMemory3();
}
private static void testOldHeap2() {
List<String> list = new ArrayList<String>();
String s = "ssa";
list.add(s);
while(true){
list.add(s);
}
}
private static void testOldHeap3() {
List<String> list = new ArrayList<String>();
String s = "ssa";
list.add(s);
while(true){
list.add(list.get(list.size()-1));
}
}
private static void testOldHeap() {
List<String> list = new ArrayList<String>();
String s = "ssa";
list.add(s);
while(true){
list.add(s+list.toString());
}
}
private static void testStockOOM() {
while (true) {
Thread thread = new Thread(new Runnable() {
public void run() {
while (true) {
}
}
});
thread.start();
}
}
public static void testPergemOutOfMemory3(){
while(true){
final BlowUpJVM oom = new BlowUpJVM();
Proxy.newProxyInstance(oom.getClass().getClassLoader(), oom.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = method.invoke(oom, args);
return result;
}
});
}
}
}
package testJVM;
public class BlowUpJVM {
public void testHeapDeepOOM(){
this.testHeapDeepOOM();
}
}
Sun公司自带了许多虚拟机工具,在bin目录下,其exe文件所依赖的源码在tools.jar包下,利用jar包中的文件可自己开发。
jps
Java process status
可查看本地虚拟机唯一id lvmid (local virtual machine id):
enter description here
可以加入参数:
enter description here
jps -m 运行时传入主类的参数;
jps -v 虚拟机参数;
jps -l 运行的主类全名 或者jar包名称;
也可以一块使用 jsp -mlv。
Jstat
监视虚拟机运行时的状态信息,包括监视类装载、内存、垃圾回收、jit编译信息。官方文档有操作命令:https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jstat.html
jstat -gcutil 3344 1000 10 每隔1000毫秒执行一次 10次
enter description here
注:元空间的本质和永久代类似,都是对jvm规范中的方法区的实现。不过元空间与永久代之间最大的区别是:元空间并不在虚拟机中,而是使用本地内存。因此在默认情况下,元空间的大小仅受本地内存限制。
jinfo
实时查看和调整虚拟机的各项参数
enter description here
第一条指令为:是否启用Serial垃圾回收,输出-,表示不启用;
第二条指令为:是否启用G1垃圾回收,输出+,表示启用。
jmap
命令jmap是一个多功能的命令。它可以生成 java 程序的 dump 文件, 也可以查看堆内对象示例的统计信息、查看 ClassLoader 的信息以及 finalizer 队列。
可实现与-XX:+HeapDumpOnOutOfMemoryError相同的效果
jhat
JVM heap Analysis Tool(分析堆)十分占据内存与CPU,使用较少。
jstack
生成线程快照,定位线程长时间停顿的原因,命令格式为:
jstack [option] vmid
可以通过Thread.getAllStackTraces()方法获取StackTraceElement,来代替jstack方法。
JConsole
JConsole是一种基于JMX的可视化监视、管理工具可进行内存管理、线程管理、查看死锁等。
背景:绩效考核系统,会针对每一个考核员工生成一个各考核点的考核结果,形成一个Excel文档,供用户下载。文档中包含用户提交的考核点信息以及分析信息,Excel文档由用户请求的时候生成,下载并保存在内存服务器一份。64G内存。
问题:经常有用户反映长时间卡顿的问题。
处理思路:
优化SQL(无效) 监控CPU 监控内存发现经常发生Full GC 20-30s
运行时产生大对象(每个教师考核的数据WorkBook),直接放入老年代,MinorGC不会去清理,会导致FullGC,且堆内存分配太大,时间过长。
解决方案:部署多个web容器,每个web容器的堆内存4G,单机TomCat集群。
背景:简单数据抓取系统,抓取网络上的一些数据,分发到其它应用。
问题:不定期内存溢出,把堆内存加大,无济于事,内存监控也正常。
处理方法:NIO使用了堆外内存,堆外内存无法垃圾回收,导致溢出。
优化参考
博客园淡然~~浅笑性能优化 | JVM性能调优篇——来自阿里P7的经验总结
文章引用CSDN作者TJtulong文章深入理解java虚拟机(全章节完整)
文章引用博客园冰河团队【Java】几种典型的内存溢出案例,都在这儿了!
文章引用博客园chenxiangxiangJava虚拟机垃圾回收(三) 7种垃圾收集器
图片引用CSDN岛城小哥常见JVM面试题及答案(综合型)