jsp网站购物车怎么做,网站运营团队各岗位的职责是什么,优秀全屏企业网站,给wordpress创建ftp文章目录 一、JVM类加载的过程1.1类加载的基本流程1.1.1加载1.1.2验证1.1.3准备1.1.4解析1.1.5初始化 1.2双亲委派模型 二、JVM垃圾回收机制2.1找到垃圾2.1.1引用计数(比如Python#xff0c;PHP中用到)2.1.2可达性分析(比如Java中用到) 2.2释放垃圾2.2.1标记清除2.2.2复制算法… 文章目录 一、JVM类加载的过程1.1类加载的基本流程1.1.1加载1.1.2验证1.1.3准备1.1.4解析1.1.5初始化 1.2双亲委派模型 二、JVM垃圾回收机制2.1找到垃圾2.1.1引用计数(比如PythonPHP中用到)2.1.2可达性分析(比如Java中用到) 2.2释放垃圾2.2.1标记清除2.2.2复制算法2.2.3标记整理2.2.4分代回收 一、JVM类加载的过程
1.1类加载的基本流程
Java代码会被编译成.class文件(里面包含了一些字节码)JVM会把.class文件读取到内存中并对其进行解析、构造类对象(这个过程叫类加载)类加载完成之后就会在内存中得到类对象后续要构造这个类的实例都是基于类对象来进行展开的。
1.1.1加载
找到.class文件打开文件读取文件内容。从Java代码中往往会得到某个类的“全限定类名”(比如java.lang.String)JVM会根据这个“全限定类名”在一些指定的目录范围内去查找对应的.class文件找到对应的.class文件就能够把这个.class文件打开并且读取里面的内容。
1.1.2验证
验证.class文件里的内容是否符合要求。 .class文件是二进制格式的文件里面的某个字节都是有某些特定含义的。java标准文档https://docs.oracle.com/javase/specs/index.html里说明了一个.class文件的格式是怎样的.class文件里应该要包含哪些内容。
1.1.3准备
给类对象分配内存空间。这个内存空间的大小是根据上一步的验证的结果来确定的。这里只是分配内存空间还没有初始化内存空间此时这个内存空间上的数值全是0此时如果打印类的static成员就会打印出0。
1.1.4解析
针对类对象中包含的字符串常量进行一些初始化操作。
java代码中用到的字符串常量在编译之后会进入到.class文件中。
比如java代码中有final String a “hello”; 编译之后.class文件的二进制指令中也会有一个a这样的引用被创建出来由于引用本质上保存的是一个变量的地址在.class文件中因为文件不涉及到内存地址所以.class文件中的a就会先被设置成一个“文件偏移量”通过这个“文件偏移量”可以找到hello这个字符串所在的位置当我们把这个类真正加载到内存的时候再把这个“文件偏移量”替换回真正的hello的内存地址。 如上图所示假设在.class文件中文件开头到hello开头的距离是100个字节就称hello这个字符串在.class文件中的“文件偏移量”为100。文件开头到test开头的这100个字节里也会有一条指令这条指令描述了String a 100这里的100表示“文件偏移量”。当.class文件加载到内存中的时候test这时的内存地址为0x12String s 100也会把100这个“文件偏移量”替换成hello这个字符串真实的内存地址这个替换的过程就是“解析”阶段要完成的主要工作。这个替换过程也叫把“符号引用”(“文件偏移量”)替换成“直接引用”(内存地址)。
1.1.5初始化
针对类对象进行初始化即把类对象中的各个属性都设置好。 初始化好static成员。 执行静态代码块。 加载父类。
1.2双亲委派模型
双亲委派模型属于类加载的第一个步骤“加载”过程中的其中一个环节即根据“全限定类名”找到.class文件。
JVM中内置了三个类加载器(程序员也可以手动创建出新的类加载器) ①BootStrap ClassLoader ②Extension ClassLoader ③Application ClassLoader 这三个类加载器彼此之间存在一个父子关系即Application ClassLoader是子、Extension ClassLoader是父、BootStrap ClassLoader是爷这个父子关系不是继承而是这几个类加载器里都有一个parent这样的属性这个parent属性指向一个父“类加载器”。
类加载的第一个步骤“加载”过程中找.class文件的过程 ①给定一个类的全限定类名比如java.lang.String。
②以Application ClassLoader作为入口根据全限定类名开始执行查找对应的.class文件的逻辑。
③Application ClassLoader不会立即扫描自己负责的目录(Application ClassLoader复责的目录是当前项目对应的目录和第三方库对应的目录)而是把查找的任务交给他的父亲Extension ClassLoader。
④Extension ClassLoader也不会立即扫描自己负责的目录(Extension ClassLoader负责的目录是JDK中的一些扩展库对应的目录(JDK厂商会在标准之外做一些扩展))而是把查找的任务交给它的父亲BootStrap ClassLoader。
⑤BootStrap ClassLoader也不会立即扫描自己负责的目录(BootStrap ClassLoader负责的是标准库对应的目录)而是把查找的任务交给它的父亲结果发现没有父亲因此BootStrap ClassLoader只能扫描自己负责的目录如果类是标准库中的类那么在BootStrap ClassLoader这个类加载器中就能找到对应的.class文件此时查找.class文件的过程就结束了。 如果类不是标准库中的类则查找.class文件的任务就会交给孩子Extension ClassLoader去执行。
⑥Extension ClassLoader就会扫描自己负责的目录如果找到对应的.class文件则查找结束就执行后续的类加载操作如果没找到则把任务交给孩子Application ClassLoader执行。
⑦Application ClassLoader就会扫描自己负责的目录如果找到对应的.class文件则查找结束就执行后续的类加载操作如果没找到就会抛出ClassNotFoundException。
双亲委派模型的目的是为了维护类被加载的优先级。
二、JVM垃圾回收机制
Java中new一个对象就是一次“动态内存申请”。 动态表示运行时(程序运行起来才能确定内存大小)静态表示编译时(编译时就能确定内存大小)。 编译时int a[5]a数组占据多少内存在编译过程中就能确定下来一个int是4字节5个int就是20字节。
在C语言中使用malloc申请的内存在使用完之后需要通过free来释放在C中使用new申请的内存需要通过delete来释放。
Java给出了垃圾回收机制(GC)让JVM自动把不再使用的内存回收掉。而不用手动回收内存大大降低了程序员的心智负担。
局部变量的生命周期是跟随栈帧的生命周期走的方法执行结束栈帧销毁局部变量所对应的内存也就释放了。 静态成员变量的生命周期是整个程序的生命周期是类对象中的一部分类加载之后是不会卸载的所以静态成员变量无需释放。 所以GC回收的是堆上的对象。
GC分为两个步骤
2.1找到垃圾
有两种主流方案
2.1.1引用计数(比如PythonPHP中用到)
new出来的对象单独安排一块空间来保存一个计数器这个计数器用来进行引用计数这个计数器描述了这个对象有几个引用在指向它。 比如 { Test t new Test(); Test t2 t; } 出了{}之后t和t2就被销毁了引用计数就归0了。当对象的引用计数为0时此时这个对象就可以视为垃圾了。
但Java没有使用引用计数因为引用计数有两个缺陷 ①比较浪费内存。因为每个new出来的对象都要单独安排一个计数器来保存它的引用计数计数器至少要占据两个字节的内存空间如果对象很少或者对象很大这时影响不大如果对象很小并且很多这时计数器占据的空间就不容忽视了内存就被浪费了很多。 ②循环问题。 比如 class A { public A t; } class Test { public static void main(String[] args) { A a new A(); A b new A(); a.t b; b.t a; a null; b null; } } 此时a和b两个引用已经被销毁了new出来的两个对象已经无法被其它代码访问到但是它们的引用计数不为0这时这两个对象是不能回收的第一个对象引用了第二个对象第二个对象引用了第一个对象。要想拿到第一个对象就要先拿到第二个对象要想拿到第二个对象就要先拿到第一个对象这构成了逻辑上的循环错误。
2.1.2可达性分析(比如Java中用到)
可达性分析本质上是时间换空间。有一个/一组线程周期性地扫描代码中的所有对象从一些特定的对象出发尽可能地进行遍历访问(比如类似于N叉树遍历)把所有能够被访问到的对象都标记成“可达”不能被访问到的未被标记的对象就是垃圾了。 可达性分析开始遍历访问的起点对象有很多比如局部变量中引用的对象、常量池中引用的对象、方法区中类静态属性引用的对象……这些起点对象统称为GCRoots。 可达性分析是周期性进行的因为某个对象是否是垃圾是会随着代码的执行而发生改变的(比如这个对象现在不是垃圾代码执行了一段时间之后就变成垃圾了)。所以可达性分析比较消耗系统资源导致系统时间开销较大相比之下引用计数通过计数器来衡量当前对象是否是垃圾比较精准时间开销比较小。
2.2释放垃圾
有三种基本思路
2.2.1标记清除
把垃圾对象直接释放掉但这个方案非常不好因为这会产生很多的内存碎片。我们释放内存是为了让其它代码能够申请内存而申请内存时我们申请到的都是连续的内存空间。如果使用标记清除使用了一段时间那么内存中出现内存碎片的情况将会非常严重导致内存申请变得十分困难。
2.2.2复制算法
把内存分成两份一次只用其中的一半。通过复制的方式把有效的对象归类到另一半再统一释放原来那一半的所有空间。 复制算法可以有效解决内存碎片问题但这个方案也有缺点 (a)内存要浪费一般内存利用率低。 (b)如果有效的对象非常多那么拷贝的开销就会很大。
2.2.3标记整理
这个方法既能够解决内存碎片的问题又能够解决复制算法中内存利用率低的问题但拷贝的开销和复制算法差不多。 标记整理类似于顺序表删除元素时的搬运操作。在内存空间中把有效的对象一个一个地往内存空间的前面搬运然后把内存空间后面的空间回收掉。
2.2.4分代回收
JVM释放内存的方法是上述三种基本思路的结合体即分代回收。 把堆分成两部分这两部分不是等分的。左边称为新生代右边称为老年代。新生代中有一个幸存区和一个伊甸区幸存区里等分为两部分。 ①刚new出来的新的对象放在伊甸区从对象诞生到可达性分析扫描开始这个过程虽然时间不长(往往是毫秒~秒级别)但在这个时间里大部分对象都会成为垃圾即大部分对象都活不过一轮GC。
②伊甸区中经过一轮GC后仍然可达的对象就会通过复制算法被拷贝到幸存区。然后释放整个伊甸区的内存。由于伊甸区中幸存下来的可达对象并不多复制开销不大所以这里非常适合用复制算法。
③GC扫描线程也会扫描幸存区然后把GC扫描到的可达对象通过复制算法拷贝到幸存区的另一半然后释放掉幸存区原来那一半的内存。对于幸存区之间的拷贝每一轮GC会拷贝多个对象、也会淘汰多个对象。
④当某个对象在幸存区中存活过很多轮GC扫描之后JVM就认为这个对象在短时间内应该是不会成为垃圾的就会把这个对象拷贝到老年代。
⑤进入老年代的对象也会被GC扫描但老年代GC扫描的频率会比新生代GC扫描的频率低很多(这减少了GC扫描的开销)。老年代使用标记整理的方式对内存进行回收。
新生代使用复制算法进行垃圾回收老年代使用标记整理进行垃圾回收。
分代回收是JVM中主要的垃圾回收思想方法。但是在垃圾回收器具体实现的时候可能还会有一些调整和优化。