[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0sOTRovc-1590335886006)(.\imgs\JVM体系架构图.png)]
JVM内存主要分为:heap(堆)、Method Area(方法区)、Java Stack(Java栈)、Native Method Stack(本地方法栈)、Program Counter Register(程序计数器)。
1、heap(堆):一个JVM实例只存在一个堆内存,堆内存的大小是可以调节的。java堆是被所有线程共享的一块内存区域。类加载器读取了文件后,需要把对象、放到堆内存中。保存所有引用类型的真实信息,以方便执行器执行。堆内存分为三部分:Young Generation Space(新生区);Tenure generation space(养老区);Metaspace(元空间)--元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。OutOfMemoryError
2、Method Area(方法区):供各路线程共享的运行时内存区域。它存储了每一类的结构信息,列如运行时常量池(Runtime Constant Pool)、静态变量、字段和方法数据、构造函数和普通方法的字节码内容。
3、Java Stack(Java栈):栈也叫栈内存,主管java程序的运行,是在线程创建时创建,它的生命期是跟随线程的生命期,线程结束栈内存也就释放,对于栈来说,不存在垃圾回收问题,只要线程一结束该栈就Over,生命周期和线程一致,是线程私有的,8种基本类型的变量+对象的引用变量+实例方法都是在函数的栈内存中分配的。(本地变量、栈操作、栈帧数据)StackOverflowError
4、Native Method Stack(本地方法栈):这个几乎用不到。主要是调用一些c\c++的方法。
5、Program Counter Register(程序计数器):每一个线程都有一个程序计数器,是线程私有的,就是一个指针,指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码),由执行引擎读取下一条指令,是一个非常小的内存空间。它是当前线程所执行的字节码的行号指示器,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。如果是一个Native,那么这个计数器为空。---用来完成分支、循环、跳转、异常处理、线程恢复等基础功能、不会发生内存溢出(OutofMemory==OOM)错误。
堆:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rRVv5Jrm-1590335886008)(.\imgs\JVMHeap内存配置.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HV8j0nnT-1590335886009)(.\imgs\堆内存.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MHHLU1qS-1590335886010)(.\imgs\堆的分类.png)]
栈:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YsNdH9he-1590335886011)(.\imgs\栈1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MQJF295r-1590335886013)(.\imgs\栈的运行原理.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BytCcVux-1590335886014)(.\imgs\栈内的元素.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CX1QPMbg-1590335886015)(.\imgs\栈堆方法区的交互.png)]
过程:类加载检查-->分配内存-->初始化零值-->设置对象头-->执行init方法
详细过程:
1、虚拟机遇到一条new指令时,首先去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已被加载、解析和初始化过。如果没有,那么须先执行相应的类加载过程。
2、接下来虚拟机将为新生代对象分配内存。对象所需的内存的大小在类加载完成后便可完全确定。分配方式有“指针碰撞(Bump the Pointer)”和“空闲列表(Free List)”两种方式,具体由所采用的垃圾收集器是否带有压缩整理功能决定。
3、内存分配完成后,虚拟机需要将分配到的内存空间都初始化为零值(不包括对象头),这一步操作保证了对象的实例字段在Java代码中可以不赋初始值就直接使用,程序能访问到这些字段的数据类型所对应的零值。
4、接下来虚拟机要对对象进行必要的设置,例如这个对象是哪个类的实例、如何才能找到类的元数据信息、对象的哈希码、对象的GC分代年龄等信息。这些信息存放在对象的对象头(Object Header)之中。根据虚拟机当前的运行状态的不同,如对否启用偏向锁等,对象头会有不同的设置方式。
5、在上面的工作都完成了之后,从虚拟机的角度看,一个新的对象已经产生了,但是从Java程序的角度看,对象创建才刚刚开始—方法还没有执行,所有的字段都还为零。所以,一般来说,执行new指令后接着执行init方法,把对象按照程序员的意愿进行初始化(应该是将构造函数中的参数赋值给对象的字段),这样一个真正可用的对象才算完全产生出来。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GB15kEPc-1590335886016)(.\imgs\对象头.png)]
1、概念:
内存泄露:是指在程序中动态分配内存给一些临时对象,但是对象生命周期过后,GC不会对它进行回收使它始终占用内存,就容易造成内存泄漏。
内存溢出:是指在新的对象生成的时候,在给它分配内存的时发现没有可以分配的内存空间,就是内存溢出。
2、内存泄漏场景:
长生命周期的对象持有短生命周期对象的引用:
修改hashset中对象的参数值,且参数是计算哈希值的字段:当一个对象被存储进HashSet集合中以后,就不能修改这个对象中的那些参与计算哈希值的字段,否则对象修改后的哈希值与最初存储进HashSet集合中时的哈希值就不同了,在这种情况下,即使在contains方法使用该对象的当前引用作为参数去HashSet集合中检索对象,也将返回找不到对象的结果,这也会导致无法从HashSet集合中删除当前对象,造成内存泄露。
机器的连接数和关闭时间设置:长时间开启非常耗费资源的连接,也会造成内存泄露。
3、内存溢出场景:
堆内存溢出(outOfMemoryError:java heap space):堆用于存储对象实例,只要不断地创建对象,并且保证GC Roots到对象之间有可达路径来避免来及回收机制清除这些对象,那么在对象数量达到最大堆的容量限制后就会产生内存溢出异常。
方法区内存溢出(outOfMemoryError:permgem space):如果程序加载的类过多,或者使用反射、gclib等这种动态代理生成类的技术,就可能导致该区发生内存溢出。
线程栈溢出(java.lang.StackOverflowError):线程栈时线程独有的一块内存结构,所以线程栈发生问题必定是某个线程运行时产生的错误。一般线程栈溢出是由于递归太深或方法调用层级过多导致的。
通过参数 -XX:HeapDumpOnOutOfMemoryError可以让虚拟机在出现内存溢出是Dump当前的内存堆存储快照以便后事分析。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RuQSKEZL-1590335886018)(.\imgs\堆溢出处理1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tBPFhUVH-1590335886019)(.\imgs\堆溢出2.png)]
1、引用计数法
序给对象添加一个引用计数器,每有一个变量引用它时,计数器加1。当引用断开时,计数器减1。当计数器为0时,代表着没有任何变量引用它,该对象就是死亡状态,JVM需要对此类对象进行回收。
缺点:
难以解决对象之间的互相循环引用问题(类似于死锁)。
2、可达性分析法
程序创建一系列的GC Roots作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链,当一个对象与GC Roots没有任何引用链相连的话,即此对象到GC Roots不可达,则证明此对象是不可用的,JVM稍后将会对此类对象进行回收。
即使在可达性分析算法中的不可达的对象,也并非是“非死不可得”。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4rqA2Xef-1590335886019)(.\imgs\对象的生与死.png)]
1、标记清除算法:算法分为“标记”与“清除”两个阶段。首先标记出所有需要回收的对象,之后清除所有被回收的对象。
缺点:产生内存碎片。
效率不高,两次扫描耗时严重。
2、(新生代使用)复制算法:(为了解决效率问题)它将内存按照容量分成2块,每次只使用其中的一块,当这一块用完了,就将存活的对象复制到另一块上,然后把已经使用过的空间一次清理掉。不用考虑碎片问题
缺点:内存缩小了一半。
3、(老年代使用)标记整理算法:标记完之后,堆存活的对象进行整理(所有活的移到一段),然后再清理掉。
4、分代收集算法:没有新的思想,就是根据对象存活周期的同将内容划分为几块(老年代和新生代),各个年代采用不同的算法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-duw9y7y3-1590335886021)(.\imgs\JVMHeap内存配置.png)]
大多数情况下,对象在新生代Eden区中分配,当Eden区没有足够的空间分配时,虚拟机会发起一次MinorGC。
虚拟机提供了-XX:PrintGCDetails这个收集器日志参数。打印日志。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D45aSNjh-1590335886021)(.\imgs\进入老年代1.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fpSOntnU-1590335886022)(.\imgs\进入老年代2.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j55cWGLk-1590335886023)(.\imgs\进入老年代3.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-74yEUudI-1590335886024)(.\imgs\GC类别.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W7LIfeVO-1590335886024)(.\imgs\sunJDK监控工具.png)]
1、jps 可以显示当前系统中所有java进程.
-l: 输出主类的全名
-v:输出虚拟机进程启动时JVM的参数
2、jstack 加进程号,可以输出给定java进程的内部线程状态及堆栈信息.
3、jinfo -flag 名字 pid :查询名字的参数值。
虚拟机的类加载机制:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。
前提:编译把本地机器码转化为字节码。
在java语言里,类型的加载、连接和初始化过程都是在程序运行期间完成的。这种策略虽然会令类加载时稍微增加一些性能开销,但是会为Java应用程序提供高度的灵活性,java里天生可以动态扩展的语言特性就是依赖运行期间的动态加载和动态连接这个特点来实现的。
类的生命周期:加载-->验证-->准备-->解析-->初始化-->使用-->卸载。
1、加载:
通过一个类的全限定名来获取定义此类的二进制字节流。
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
在内存中生成一个代表这个类的Java.lang.Class对象,作为方法区这个类的各种数据的访问入口。
2、验证:验证是连接的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当期虚拟机的要求,并且不会危害到虚拟机自身的安去。
文件格式验证:判断字节流是否符合Class文件格式的规范,UTF-8验证、等一系列文件的验证。
元数据验证:堆字节码描述的信息进行语义分析,(这个类是否有父类、这个类的父类是否继承了不允许继承的类、等)
字节码验证:主要目的是通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。对类的方法体进行校验和分析。
符号引用验证:发生在虚拟机将符号引用转化为直接引用的时候,这个转化动作将在连接的第三阶段--解析阶段中发生。符号引用验证可以看作是对类自身以外的信息进行匹配性校验。(引用是否能找到对于的的实例)
3、准备:正式为类变量分配内存并设置变量初始值的阶段,这些变量(static修饰的)所使用的内存都将在方法区中进行分配。
4、解析:是虚拟机将常量池内的符号引用替换为直接引用的过程。
符号引用:一组符号来描述所引用的目标。
直接引用:直接指向目标的指针、相对偏移量或者一个能间接定位到目标的句柄。
5、初始化:是类加载过程的最后一步,前面的类加载过程中,除了在加载阶段用户应用程序可以通过自定义类加载器参与之外,其余动作完全是由虚拟机主导和控制。到了初始化阶段,才真正开始执行类中定义的Java程序。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VraZp0tl-1590335886025)(.\imgs\类的生命周期.png)]
类初始化步骤:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VSpLxFV5-1590335886026)(.\imgs\初始化顺序.png)]
类的初始化时机:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZQgKsjln-1590335886026)(.\imgs\类的初始化时机.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vaKDxZeJ-1590335886027)(.\imgs\类加载器.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PhiAKK6H-1590335886028)(.\imgs\双亲委派.png)]
HelloWorld的具体执行过程:
1、执行HelloWorld.java文件,生成HelloWorld.class字节码文件;
2、虚拟机执行HelloWorld.class,将这个类加载到内存中(即方法区的类代码区中);
3、虚拟机通过类找到HelloWorld的主方法(程序的入口方法),访问权限为public(公有可用),虚拟机传递String类型参数的地址到主方法的args中去,并在栈区为args开辟内存空间,返回一个void的返回值;
4、定义一个String(标准类库中的)类型的变量(在栈区开辟空间)s,s的值不确定(垃圾值,编译无法通过);
5、s = “Hello World!”,对象“Hello World!”在方法区的常量数据区开辟空间,属性即为:Hello World!,方法即为:toString(),变量s存放对象“Hello World!”的地址;
6、虚拟机找到标准类库中的System.class类并加载到内存中(即方法区的类代码区中),System.out为标准字节输出流对象(),并调用println()方法将变量s的值打印到屏幕上。
PS: 虚拟机调用主方法时会创建三个默认对象:System.out(标准字节输出流对象)、System.in(标准字节输入流对象)和System.error(标准字节出错流对象).
以上共涉及:
1个java文件:HelloWorld.java
4个class类: HelloWorld.class、String[].class、String.class、System.class
5个对象: “Hello World!”、String[]、System.out、System.in、System.error
2个变量:args、s
3个方法:main()、toString()、println()
error(标准字节出错流对象).
以上共涉及:
1个java文件:HelloWorld.java
4个class类: HelloWorld.class、String[].class、String.class、System.class
5个对象: “Hello World!”、String[]、System.out、System.in、System.error
2个变量:args、s
3个方法:main()、toString()、println()
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-x3u26d4j-1590335886029)(.\imgs\HelloWorld内存图.png)]