当前位置: 首页 > news >正文

800元做网站招工 最新招聘信息58同城

800元做网站,招工 最新招聘信息58同城,案例平台 网站,wordpress all import文章目录 垃圾回收常见内存管理方式手动回收#xff1a;C内存管理自动回收(GC)#xff1a;Java内存管理自动、手动回收优缺点 应用场景垃圾回收器需要对哪些部分内存进行回收#xff1f;不需要垃圾回收器回收需要垃圾回收器回收 方法区的回收代码测试手动调用垃圾回收方法Sy… 文章目录 垃圾回收常见内存管理方式手动回收C内存管理自动回收(GC)Java内存管理自动、手动回收优缺点 应用场景垃圾回收器需要对哪些部分内存进行回收不需要垃圾回收器回收需要垃圾回收器回收 方法区的回收代码测试手动调用垃圾回收方法System.gc()代码运行 思考 堆回收如何判断堆的对象是否可以回收案例介绍引用计数法JVM不使用优缺点验证JVM没有使用引用计数法 可达性分析法JVM使用四类GC Root对象查看GC Root对象的工具 常见的五种引用对象软引用软引用的使用方法软引用对象本身怎么回收呢工作场景软引用的缓存案例 弱引用一般不使用虚引用和终结器引用常规开发不使用 垃圾回收算法垃圾回收算法的历史和分类垃圾回收算法的评价标准标记清除算法优缺点 复制算法优缺点 标记整理算法标记压缩算法优缺点 分代垃圾回收算法常用JVM参数设置使用Arthas查看内存分区垃圾回收执行流程分代GC算法内存为什么分年轻代、老年代 垃圾回收器垃圾回收算法实现Arthas查看所使用的垃圾回收器年轻代-Serial垃圾回收器**回收年代和算法****优点****缺点****适用场景**如何使用 老年代-SerialOld垃圾回收器**回收年代和算法****优点****缺点****适用场景**如何使用 年轻代-ParNew垃圾回收器**回收年代和算法****优点****缺点****适用场景**如何使用 老年代-CMS(Concurrent Mark Sweep)垃圾回收器**回收年代和算法****优点****缺点****适用场景**使用CMS执行步骤 年轻代-Parallel Scavenge垃圾回收器**回收年代和算法****优点****缺点****适用场景****常用参数** 老年代-Parallel Old垃圾回收器**回收年代和算法****优点****缺点****适用场景**如何使用测试 G1(Garbage First)垃圾回收器极力推荐G1垃圾回收有两种方式年轻代回收Young GC混合回收Mixed GC 如何使用G1垃圾回收器**回收年代和算法****优点****缺点**测试 垃圾回收器总结几个问题 文章说明 垃圾回收 字节码文件通过类加载器将类的信息加载到运行时数据区的方法区中接下来执行引擎的解释器开始解释执行字节码信息的字节码指令将对象创建出来放到运行时数据区的堆上在堆上的对象后面不再使用之后垃圾回收器会将这些对象进行销毁自动垃圾回收 常见内存管理方式 手动回收C内存管理 在C/C这类没有自动垃圾回收机制的语言中一个对象如果不再使用需要手动释放否则就会出现内存泄漏。内存泄漏不再使用的对象在系统中未被回收内存泄漏的积累可能会导致内存溢出。 【模拟内存溢出】 在这段代码中通过死循环不停创建Test类的对象每一轮循环结束之后这次创建的对象就不再使用了。但是没有手动调用删除对象的方法此时对象就会出现内存泄漏。 这段代码中手动调用delete删除对象就不会出现内存泄漏。 我们称这种释放对象的过程为垃圾回收而需要程序员编写代码进行回收的方式为手动回收。 自动回收(GC)Java内存管理 Java中为了简化对象的释放引入了自动的垃圾回收Garbage Collection简称GC机制。通过垃圾回收器来对不再使用的对象完成自动的回收垃圾回收器主要负责对堆上的内存进行回收。其他很多现代语言比如C#、Python、Go都拥有自己的垃圾回收器。 垃圾回收器如果发现某个对象不再使用就可以回收该对象。 自动、手动回收优缺点 自动垃圾回收自动根据对象是否使用由虚拟机来回收对象 优点降低程序员实现难度、降低对象回收bug的可能性缺点程序员无法控制内存回收的及时性对象不用之后不会及时被清理掉需要等待垃圾回收器工作有一定的滞后 手动垃圾回收由程序员编程实现对象的删除 优点回收及时性高由程序员把控回收的时机缺点编写不当容易出现悬空指针对象内存被释放但是指针没有指向null、重复释放delete返回执行、内存泄漏、程序员粗心忘记释放等问题 应用场景 解决系统僵死的问题大厂的系统出现的许多系统僵死问题即程序在运行但是得不到回应这个现象与频繁的垃圾回收有关JVM忙于垃圾回收把用户请求晾一边性能优化对垃圾回收器进行合理的设置可以有效地提升程序的执行性能高频面试题 常见的垃圾回收器常见的垃圾回收算法四种引用项目中用了哪一种垃圾回收器 垃圾回收器需要对哪些部分内存进行回收 不需要垃圾回收器回收 首先是线程不共享的部分都是伴随着线程的创建而创建线程的销毁而销毁线程不再使用的时候会将线程的内存释放。Java虚拟机栈、本地方法栈中存储了方法的栈帧方法的栈帧在执行完方法之后就会自动弹出栈并释放掉对应的内存。 需要垃圾回收器回收 方法区的回收 方法区中能回收的内容主要就是不再使用的类。判定一个类可以被卸载。需要同时满足下面三个条件 此类所有实例对象都已经被回收在堆中不存在任何该类的实例对象以及子类对象。 这段代码中就将局部变量对堆上实例对象的引用去除了所以对象就可以被回收。 加载该类的类加载器已经被回收。 这段代码让局部变量对类加载器的引用去除类加载器loader就可以被回收。 该类对应的 java.lang.Class 对象没有在任何地方被引用。 代码测试 package chapter04.gc;import java.net.URL; import java.net.URLClassLoader; import java.util.ArrayList;/*** 类的卸载*/ public class ClassUnload {public static void main(String[] args) throws InterruptedException {try {ArrayListClass? classes new ArrayList();ArrayListURLClassLoader loaders new ArrayList();ArrayListObject objs new ArrayList();while (true) {URLClassLoader loader new URLClassLoader(new URL[]{new URL(file:D:\\lib\\)});Class? clazz loader.loadClass(com.itheima.my.A);Object o clazz.newInstance();// objs.add(o); // classes.add(clazz); // loaders.add(loader);System.gc();}} catch (Exception e) {e.printStackTrace();}} }添加这两个虚拟机参数进行测试 -XX:TraceClassLoading -XX:TraceClassUnloading-XX:TraceClassLoading程序运行过程中打印出类的加载 -XX:TraceClassUnloading类被卸载的时候会打印出日志 如果注释掉代码中三句add调用就可以同时满足3个条件。 手动调用垃圾回收方法System.gc() 注意调用System.gc()方法并不一定会立即回收垃圾仅仅是向Java虚拟机发送一个垃圾回收的请求具体是否需要执行垃圾回收Java虚拟机会自行判断。 代码运行 执行之后日志中就会打印出类卸载的内容 Unloading类被卸载三个条件都存在加载之后会被回收 【第一个条件不满足】 运行之后发现没有卸载 【还可以模拟其他条件】 当其他条件没有满足的时候一样不触发类的卸载 思考 我们所写的类是由应用程序加载器加载的这个加载器是不会被回收的所以一直不会满足条件二那么类卸载主要用在什么场景下呢 开发中此类场景一般很少出现主要在如 OSGi、JSP 的热部署等应用场景中。每个jsp文件对应一个唯一的类加载器当一个jsp文件修改了就直接卸载这个jsp类加载器。重新创建类加载器重新加载jsp文件。 **注**方法区的回收我们很难用到了解一下即可 堆回收 堆是java程序中最大的一部分内存里面有很多对象 如何判断堆的对象是否可以回收 垃圾回收器要回收对象的第一步就是判断哪些对象可以回收。Java中的对象是否能被回收是根据对象是否被引用来决定的。如果对象被引用了说明该对象还在使用不允许被回收。 案例介绍 【简单案例】 第一行代码执行之后堆上创建了Demo类的实例对象同时栈上保存局部变量引用堆上的对象。 第二行代码执行之后局部变量对堆上的对象引用去掉引用关系失效那么堆上的对象就可以被回收了。 【复杂案例】 这个案例中如果要让对象a和b回收必须将局部变量到堆上的引用去除。 直接找A的引用需要去除即a1 null通过B找A的引用也需要去除即b1.a null 如果a1 nullb1 null这样可以回收A和B对象吗 答可以虽然a1.b b1; 但是a1都为null了就找不到b1了。A和B互相之间的引用需要去除吗答案是不需要因为局部变量都没引用这两个对象了在代码中已经无法访问这两个对象即便他们之间互相有引用关系也不影响对象的回收。 判断对象是否可以回收主要有两种方式 引用计数法可达性分析法。 引用计数法JVM不使用 引用计数法会为每个对象维护一个引用计数器初始值为0当对象被引用时加1取消引用时减1。如下图中对象A的计数器初始为0局部变量a1对它引用之后计数器就变成了1。同样A对B产生了引用B的计数器也是1。 如果把A对B的引用去除则变为下图 即如果一个对象的引用计数器是0说明这个对象没有引用就可以被垃圾回收了。 优缺点 优点 实现简单C中的智能指针就采用了引用计数法 缺点 每次引用和取消引用都需要维护计数器对系统性能会有一定的影响存在循环引用问题所谓循环引用就是当A引用BB同时引用A时会出现对象无法回收的问题导致内存泄漏。 这张图上由于A和B之间存在互相引用所以计数器都为1两个对象都不能被回收。但是由于没有局部变量对这两个代码产生引用代码中已经无法访问到这两个对象理应可以被回收。 验证JVM没有使用引用计数法 可以做一个实验验证下Java中循环引用不会导致内存泄漏如果不会泄露说明JVM没有使用引用计数法。 可以通过垃圾回收日志去看一个对象有没有被回收如果想查看垃圾回收的信息可以添加虚拟机参数-verbose:gc。 【代码】 public static void main(String[] args) throws IOException {while (true){A a1 new A();B b1 new B();a1.b b1;|b1.a a1;a1 null;b1 null;System.gc();} }【运行】 加上这个参数之后执行代码发现对象确实被回收了因为内存大小始终差不多一直维持在1000K说明每轮循环创建的两个对象在垃圾回收之后都被回收了。如果没有回收的话随着循环的进行每次new A和new B占用的内存会越来越大。说明JVM没有使用引用计数法 可达性分析法JVM使用 Java使用的是可达性分析算法来判断对象是否可以被回收。可达性分析将对象分为两类 垃圾回收的根对象GC Root普通对象 根对象和普通对象之间会存在引用关系。下图中A到B再到C和D形成了一个引用链。可达性分析算法指的是如果从某个到GC Root对象是可达的对象就不可被回收。 通过对象A可以找到对象B。通过对象B又可以找到对象C、对象D。所以对象A、B、C、D都不可以被回收。如果断掉对象A和对象B的联系则对象B、C、D都可以回收。 四类GC Root对象 GC Root对象是不可以被回收的**哪些对象被称之为GC Root对象呢**满足如下四大类即是GC Root对象否则是普通对象 第一类线程Thread对象它会引用线程栈帧中的方法参数、局部变量等。 第二类系统类加载器加载的java.lang.Class对象引用类中的静态变量。(GC Root可以关联到静态变量所以A的实例也不能被回收) 第三类监视器对象用来保存同步锁synchronized关键字持有的对象。(如下图的ReferenceCouting对象不可以被回收) 第四类本地方法调用时使用的全局对象。虚拟机控制调用Java程序员可以不用太关注 查看GC Root对象的工具 通过Arthas和eclipse Memory Analyzer (MAT) 工具可以查看GC RootMAT工具是eclipse推出的Java堆内存检测工具。具体操作步骤如下 步骤一使用arthas的heapdump命令将堆内存快照保存到本地磁盘中。 步骤二使用MAT工具打开堆内存快照文件选择GC Roots功能查看所有的GC Root。 【演示步骤详解】 1、代码如下 package com.itheima.jvm.chapter04;import java.io.IOException;public class ReferenceCounting {public static A a2 null;public static void main(String[] args) throws IOException { // while (true){A a1 new A();B b1 new B();a1.b b1;b1.a a1;a2 a1;// 程序阻塞System.in.read(); // a1 null; // b1 null; // }} }class A {B b; // byte[] t new byte[1024 * 1024 * 10]; }class B {A a; // byte[] t new byte[1024 * 1024 * 10]; }2、使用arthas连接到程序输入如下命令: heapdump 内存快照文件输出目录/test2.hprof文件名.hprof这样就生成了一个堆内存快照后面介绍简单来说就是包含了所有堆中的对象信息。 3、下载MAT工具。 下载路径https://download.csdn.net/download/laodanqiu/89495359 如果出现如下错误请将环境变量中的JDK版本升级到17以上 4、选择菜单中的打开堆内存快照功能并选择刚才生成的文件。 5、选择内存泄漏检测报告并确定。 6、通过菜单找到GC Roots。 7、MAT对4类GC Root对象做了分类。 8、找到局部变量。 9、找到静态变量。 System Class下面的类太多了可以通过复制A的内存地址来找 拿到内存地址之后通过地址反向寻找关联它的RC Root 查询出来的结果 常见的五种引用对象 强引用可达性算法中描述的对象引用一般指的是强引用即是GCRoot对象对普通对象有引用关系只要这层关系存在普通对象就不会被回收。除了强引用之外Java中还设计了几种其他引用方式软引用弱引用虚引用终结器引用 软引用 软引用相对于强引用是一种比较弱的引用关系如果一个对象只有软引用关联到它当程序内存不足时就会将软引用中的数据进行回收强引用的对象还是会保留。软引用常用于缓存中不能用来存储重要数据不然有被回收的风险如何实现软引用在JDK 1.2版之后提供了SoftReference类来实现软引用。 如下图中对象A被GC Root对象强引用了同时我们创建了一个软引用SoftReference对象它本身也是一个对象软引用对象中引用了对象A。 接下来强引用被去掉之后例如给静态变量赋值一个null对象A暂时还是处于不可回收状态因为有软引用存在并且内存还够用。 如果内存出现不够用的情况对象A就处于可回收状态可以被垃圾回收器回收。 这样做有什么好处如果对象A是一个缓存平时会保存在内存中如果想访问数据可以快速访问。但是如果内存不够用了我们就可以将这部分缓存清理掉释放内存。即便缓存没了也可以从数据库等地方获取数据不会影响到业务正常运行这样可以减少内存溢出产生的可能性。 特别注意 软引用对象本身需要被强引用真实环境中一定存在这个关系否则软引用对象也会被回收掉。 软引用的使用方法 软引用的执行过程如下 将对象使用软引用包装起来new SoftReference对象类型(对象)。内存不足时虚拟机尝试进行垃圾回收。如果垃圾回收仍不能解决内存不足的问题回收软引用中的对象。如果依然内存不足抛出OutOfMemory异常。 代码 /*** 软引用案例2 - 基本使用*/ public class SoftReferenceDemo2 {public static void main(String[] args) throws IOException {// 强引用byte[] bytes new byte[1024 * 1024 * 100];// 建立软引用关系SoftReferencebyte[] softReference new SoftReferencebyte[](bytes);// 去除强引用bytes null;// 打印软引用中的真实数据System.out.println(softReference.get());// 再创建一个100M的数组内存不够用软引用的对象要被释放掉byte[] bytes2 new byte[1024 * 1024 * 100];// 这里打印出来的软引用的数据应该是空System.out.println(softReference.get()); // // byte[] bytes3 new byte[1024 * 1024 * 100]; // softReference null; // System.gc(); // // System.in.read();} }添加虚拟机参数限制最大堆内存大小为200m因为堆内存中还需要放一些其他对象所以堆内存实际上只有100多M可以使用理论上是放不下两个100M的数组的 执行后发现第二个100m对象创建之后需软引用中包含的对象已经被回收了。 软引用对象本身怎么回收呢 如果软引用对象里边包含的数据已经被回收了那么软引用对象本身其实也可以被回收了。 SoftReference提供了一套队列机制 1、软引用创建时通过构造器传入引用队列 2、在软引用中包含的对象被回收时该软引用对象会被放入引用队列 3、通过代码遍历引用队列将SoftReference的强引用删除 代码 /*** 软引用案例3 - 引用队列使用*/ public class SoftReferenceDemo3 {public static void main(String[] args) throws IOException {ArrayListSoftReference softReferences new ArrayList();// 引用队列ReferenceQueuebyte[] queues new ReferenceQueuebyte[]();for (int i 0; i 10; i) {byte[] bytes new byte[1024 * 1024 * 100];SoftReference studentRef new SoftReferencebyte[](bytes,queues);softReferences.add(studentRef);}SoftReferencebyte[] ref null;// 被回收的软引用对象数量int count 0;while ((ref (SoftReferencebyte[]) queues.poll()) ! null) {count;}System.out.println(count);} }最终展示的结果是 这9个软引用对象中包含的数据已经被回收掉所以可以手动从ArrayList中去掉这样就可以释放这9个对象。 工作场景软引用的缓存案例 使用软引用实现学生信息的缓存内存不足时会清理Map里面的值存储的Student对象 注意value回收了key也要同步回收 代码 import lombok.Data;import java.lang.ref.ReferenceQueue; import java.lang.ref.SoftReference; import java.util.HashMap; import java.util.Map;/*** 软引用案例4 - 学生信息的缓存*/ public class StudentCache {private static StudentCache cache new StudentCache();/*** 测试限制堆内存死循环堆内存也不会溢出因为会及时回收** param args*/public static void main(String[] args) {for (int i 0; ; i) {StudentCache.getInstance().cacheStudent(new Student(i, String.valueOf(i)));}}/*** 用于Cache内容的存储*/private MapInteger, StudentRef StudentRefs;/*** 被回收的Reference的队列*/private ReferenceQueueStudent q;/*** 继承SoftReference使得每一个实例都具有可识别的标识。* 并且该标识与其在HashMap内的key相同。*/private class StudentRef extends SoftReferenceStudent {/*** 根据软引用获取key这样才能在map中删除相应的key*/private Integer _key null;public StudentRef(Student em, ReferenceQueueStudent q) {super(em, q);_key em.getId();}}/*** 构建一个缓存器实例*/private StudentCache() {StudentRefs new HashMapInteger, StudentRef();q new ReferenceQueueStudent();}/*** 取得缓存器实例** return*/public static StudentCache getInstance() {return cache;}/*** 以软引用的方式对一个Student对象的实例进行引用并保存该引用** param em*/private void cacheStudent(Student em) {// 清除垃圾引用删除map的key和valuecleanCache();StudentRef ref new StudentRef(em, q);StudentRefs.put(em.getId(), ref);System.out.println(StudentRefs.size());}/*** 依据所指定的ID号获取相应Student对象的实例** param id* return*/public Student getStudent(Integer id) {Student em null;// 缓存中是否有该Student实例的软引用如果有从软引用中取得。if (StudentRefs.containsKey(id)) {StudentRef ref StudentRefs.get(id);// 获取软引用的数据em ref.get();}// 如果没有软引用或者从软引用中得到的实例是null重新构建一个实例// 并保存对这个新建实例的软引用if (em null) {em new Student(id, String.valueOf(id));System.out.println(Retrieve From StudentInfoCenter. ID id);this.cacheStudent(em);}return em;}/*** 清除那些所软引用的Student对象已经被回收的StudentRef对象*/private void cleanCache() {StudentRef ref null;// q里面存储的是已经被回收的软引用不停将软引用弹出来然后删除相应的keywhile ((ref (StudentRef) q.poll()) ! null) {StudentRefs.remove(ref._key);}} }Data class Student {int id;String name;public Student(int id, String name) {this.id id;this.name name;} }弱引用一般不使用 弱引用的整体机制和软引用基本一致区别在于弱引用包含的对象在垃圾回收时不管内存够不够都会直接被回收。在JDK 1.2版之后提供了WeakReference类来实现弱引用弱引用主要在ThreadLocal中使用。 弱引用对象本身也可以使用引用队列进行回收。 package chapter04.weak;import java.io.IOException; import java.lang.ref.WeakReference;/*** 弱引用案例 - 基本使用*/ public class WeakReferenceDemo2 {public static void main(String[] args) throws IOException {byte[] bytes new byte[1024 * 1024 * 100];WeakReferencebyte[] weakReference new WeakReferencebyte[](bytes);bytes null;System.out.println(weakReference.get());System.gc();System.out.println(weakReference.get());} }执行之后发现gc执行之后对象已经被回收了。 虚引用和终结器引用常规开发不使用 这两种引用在常规开发中是不会使用的。 虚引用也叫幽灵引用/幻影引用不能通过虚引用对象获取到包含的对象。虚引用唯一的用途是当对象被垃圾回收器回收时可以接收到对应的通知。Java中使用PhantomReference实现了虚引用直接内存中为了及时知道直接内存对象不再使用从而回收内存使用了虚引用来实现。 终结器引用指的是在对象需要被回收时终结器引用会关联对象并放置在Finalizer类中的引用队列中在稍后由一条由FinalizerThread线程从队列中获取对象然后执行对象的finalize方法在对象第二次被回收时该对象才真正的被回收。在这个过程中可以在finalize方法中再将自身对象使用强引用关联上但是不建议这样做。 下面的代码仅仅是为了面试工作中不会写出这种代码 package chapter04.finalreference;/*** 终结器引用案例*/ public class FinalizeReferenceDemo {public static FinalizeReferenceDemo reference null;public void alive() {System.out.println(当前对象还存活);}Overrideprotected void finalize() throws Throwable {try{System.out.println(finalize()执行了...);// 调用finalize的时候设置强引用自救让对象不被回收reference this;}finally {super.finalize();}}public static void main(String[] args) throws Throwable {// 设置强引用reference new FinalizeReferenceDemo();test();test();}private static void test() throws InterruptedException {// 去除强引用reference null;// 回收对象回收的时候finalize方法会被终结器线程调用一次System.gc();// 执行finalize方法的优先级比较低休眠500ms等待一下Thread.sleep(500);if (reference ! null) {reference.alive();} else {System.out.println(对象已被回收);}} }【运行】 垃圾回收算法 Java是如何实现垃圾回收的呢简单来说垃圾回收算法要做的有两件事 找到内存中存活的对象释放不再存活对象的内存使得程序能再次利用这部分空间 垃圾回收算法的历史和分类 1960年John McCarthy发布了第一个GC算法标记-清除算法。1963年Marvin L. Minsky 发布了复制算法。 本质上后续所有的垃圾回收算法都是在上述两种算法的基础上优化而来。 垃圾回收算法的评价标准 Java垃圾回收过程会通过单独的GC线程来完成但是不管使用哪一种GC算法都会有部分阶段需要停止所有的用户线程。这个过程被称之为Stop The World简称STW如果STW时间过长系统假死则会影响用户的使用。 如下图用户代码执行和垃圾回收执行让用户线程停止执行STW是交替执行的。 交替执行过程可以通过如下代码验证 package chapter04.gc;import lombok.SneakyThrows;import java.util.LinkedList; import java.util.List;/*** STW测试*/ public class StopWorldTest {public static void main(String[] args) {new PrintThread().start();new ObjectThread().start();} }/*** 打印线程*/ class PrintThread extends Thread{SneakyThrowsOverridepublic void run() {//记录开始时间long last System.currentTimeMillis();while(true){long now System.currentTimeMillis();// 如果不是垃圾回收影响这里每次都应该是输出100System.out.println(now - last);last now;Thread.sleep(100);}} }/*** 创建对象线程*/ class ObjectThread extends Thread{SneakyThrowsOverridepublic void run() {Listbyte[] bytes new LinkedList();while(true){// 最多存放8g然后删除强引用垃圾回收时释放8gif(bytes.size() 80){// 清空集合强引用去除垃圾回收器就会去回收对象bytes.clear();}bytes.add(new byte[1024 * 1024 * 100]);Thread.sleep(10);}} }代码运行之前设置如下JVM参数 所以判断GC算法是否优秀可以从三个方面来考虑 吞吐量 吞吐量指的是 CPU 用于执行用户代码的时间与 CPU 总执行时间的比值即吞吐量 执行用户代码时间 /执行用户代码时间 GC时间。吞吐量数值越高垃圾回收的效率就越高。 最大暂停时间 最大暂停时间指的是所有在垃圾回收过程中的STW时间最大值。比如如下的图中黄色部分的STW就是最大暂停时间显而易见上面的图比下面的图拥有更少的最大暂停时间。最大暂停时间越短用户使用系统时受到的影响就越短。 堆使用效率 不同垃圾回收算法对堆内存的使用方式是不同的。比如标记清除算法可以使用完整的堆内存。而复制算法会将堆内存一分为二每次只能使用一半内存。从堆使用效率上来说标记清除算法要优于复制算法。 上述三种评价标准堆使用效率、吞吐量以及最大暂停时间不可兼得。 一般来说堆内存越大回收对象就越多最大暂停时间就越长。想要减少最大暂停时间就要减少堆内存少量多次因为每次清理有一些准备工作因此垃圾回收总时间会上升吞吐量会降低。 没有一个垃圾回收算法能兼顾上述三点评价标准所以不同的垃圾回收算法它的侧重点是不同的适用于不同的应用场景即垃圾回收算法没有好与坏只有是否适合 秒杀场景购买只有很少的时间最大暂停时间越短越好有的场景程序就在后台处理数据暂停时间长一点无所谓目标是吞吐量高一点 标记清除算法 标记清除算法的核心思想分为两个阶段 标记阶段将所有存活的对象进行标记。Java中使用可达性分析算法从GC Root开始通过引用链遍历出所有存活对象。清除阶段从内存中删除没有被标记也就是非存活对象。 第一个阶段从GC Root对象开始扫描将对象A、B、C在引用链上的对象标记出来 第二个阶段将没有标记的对象清理掉所以对象D就被清理掉了。 优缺点 优点实现简单只需要在第一阶段给每个对象维护标志位在引用链上标记为1第二阶段删除标记值为0的对象即可。 缺点 碎片化问题由于内存是连续的所以在对象被删除之后内存中会出现很多细小的可用内存单元。如果我们需要的是一个比较大的空间很有可能这些内存单元的大小过小无法进行分配。如下图红色部分已经被清理掉了总共回收了9个字节但是每个都是一个小碎片无法为5个字节的对象分配空间。 分配速度慢。由于内存碎片的存在需要维护一个空闲链表极有可能发生每次需要遍历到链表的最后才能获得合适的内存空间。我们需要用一个链表来维护哪些空间可以分配对象很有可能需要遍历这个链表到最后才能发现这块空间足够我们去创建一个对象。如下图遍历到最后才发现有足够的空间分配3个字节的对象了。如果链表很长遍历也会花费较长的时间。 复制算法 复制算法的核心思想是 准备两块空间From空间和To空间每次在对象分配阶段只能使用其中一块空间From空间。 对象A首先分配在From空间 在垃圾回收GC阶段将From中的存活对象复制到To空间。 在垃圾回收阶段如果对象A存活就将其复制到To空间。然后将From空间直接清空。 将两块空间的From和To名字互换下次依然在From空间上创建对象。 完整的复制算法的例子 1、将堆内存分割成两块From空间 To空间对象分配阶段创建对象。 2、GC阶段开始将GC Root搬运到To空间 3、将GC Root关联的对象搬运到To空间 4、清理From空间并把名称互换 优缺点 优点 吞吐量高复制算法只需要遍历一次存活对象复制到To空间即可比标记-整理算法少了一次遍历的过程因而性能较好但是性能不如标记-清除算法因为标记清除算法不需要进行对象的移动不会发生碎片化复制算法在复制之后就会将对象按顺序放入To空间中所以对象以外的区域都是可用空间不存在碎片化内存空间。 缺点 内存使用效率低每次只能让一半的内存空间来给创建对象使用。 标记整理算法标记压缩算法 标记整理算法是对标记清理算法中容易产生内存碎片问题的一种解决方案。 核心思想分为两个阶段 标记阶段将所有存活的对象进行标记。Java中使用可达性分析算法从GC Root开始通过引用链遍历出所有存活对象。整理阶段将存活对象移动到堆的一端。清理掉存活对象的内存空间。 优缺点 优点 内存使用效率高整个堆内存都可以使用不像复制算法只能使用半个堆内存不会发生碎片化在整理阶段可以将对象往内存的一侧进行移动剩下的空间都是可以分配对象的有效空间 缺点 整理阶段的效率不高需要遍历多次对象还需要移动对象。整理算法有很多种比如Lisp2整理算法需要对整个堆中的对象搜索3次整体性能不佳。可以通过Two-Finger、表格算法、ImmixGC等高效的整理算法优化此阶段的性能。 分代垃圾回收算法常用 现代优秀的垃圾回收算法会将上述描述的垃圾回收算法组合进行使用其中应用最广的就是分代垃圾回收算法(Generational GC)。分代垃圾回收将整个内存区域划分为两块大区年轻代、老年代 Eden区对象刚被创建出来的时候放到的地方幸存者区-S0、幸存者区-S1用来实现复制算法 可以通过arthas来验证下内存划分的情况 在JDK8中添加-XX:UseSerialGC参数使用分代回收的垃圾回收器运行程序。在arthas中使用memory命令查看内存显示出三个区域的内存情况。 Eden survivor 这两块区域组成了年轻代。tenured_gen指的是晋升区域其实就是老年代。 JVM参数设置 可以设置的虚拟机参数如下 参数名参数含义示例-Xms设置堆的最小和初始大小必须是1024倍数且大于1MB比如初始大小6MB的写法 -Xms6291456 -Xms6144k -Xms6m-Xmx设置最大堆的大小必须是1024倍数且大于2MB比如最大堆80 MB的写法 -Xmx83886080 -Xmx81920k -Xmx80m-Xmn新生代的大小新生代256 MB的写法 -Xmn256m -Xmn262144k -Xmn268435456-XX:SurvivorRatio伊甸园区和幸存区的比例默认为8如新生代有1g内存则伊甸园区800MBS0和S1各100MB比例调整为4的写法-XX:SurvivorRatio4-XX:PrintGCDetailsverbose:gc打印GC日志无 老年代大小不需要设置因为新生代设置完之后老年代的大小就确定了总的堆内存-新生代内存 注如果使用其他版本的JDK或者使用其他回收器上面的部分参数可能就不会生效 使用Arthas查看内存分区 代码 package chapter04.gc;import java.io.IOException; import java.util.ArrayList; import java.util.List;/*** 垃圾回收器案例1*/ //-XX:UseSerialGC -Xms60m -Xmn20m -Xmx60m -XX:SurvivorRatio3 -XX:PrintGCDetails public class GcDemo0 {public static void main(String[] args) throws IOException {ListObject list new ArrayList();int count 0;while (true){System.in.read();System.out.println(count);//每次添加1m的数据list.add(new byte[1024 * 1024 * 1]);}} }使用arthas的memory展示出来的效果 heap展示的是可用堆。 垃圾回收执行流程 1、分代回收时创建出来的对象首先会被放入Eden伊甸园区。 2、随着对象在Eden区越来越多如果Eden区满新创建的对象已经无法放入就会触发年轻代的GC称为Minor GC或者Young GC。Minor GC会把需要eden中和From需要回收的对象回收把没有回收的对象放入To区算法使用的是复制算法。Minor GC结束之后****Eden区会被清空后面创建的对象又可以放到Eden区。 3、接下来S0会变成To区S1变成From区。当eden区满时再往里放入对象依然会发生Minor GC。 此时会回收eden区和S1(from)中的对象并把eden和from区中存活的对象放入S0。 注意每次Minor GC中都会为对象记录他的年龄初始值为0每次GC完加1。 4、如果Minor GC后对象的年龄达到阈值最大15默认值和垃圾回收器有关对象就会被晋升至老年代。 5、当老年代中空间不足无法放入新的对象时先尝试minor gc为啥**因为young满了之后部分对象年龄没有到15也被放在了老年区**minor gc可以清理young区来放新对象。如果空间还是不足就会触发Full GC停顿时间较长Full GC会对整个堆进行垃圾回收。如果Full GC依然无法回收掉老年代的对象那么当对象继续放入老年代时就会抛出Out Of Memory异常。 下图中的程序为什么会出现OutOfMemory 从上图可以看到Full GC无法回收掉老年代的对象那么当对象继续放入老年代时就会抛出Out Of Memory异常。 【测试代码】 //-XX:UseSerialGC -Xms60m -Xmn20m -Xmx60m -XX:SurvivorRatio3 -XX:PrintGCDetails public class GcDemo0 {public static void main(String[] args) throws IOException {ListObject list new ArrayList();int count 0;while (true){System.in.read();System.out.println(count);//每次添加1m的数据list.add(new byte[1024 * 1024 * 1]);}} }结果如下 老年代已经满了而且垃圾回收无法回收掉对象如果还想往里面放就发生了OutOfMemoryError。 分代GC算法内存为什么分年轻代、老年代 为什么分代GC算法要把堆分成年轻代和老年代首先我们要知道堆内存中对象的特性 系统中的大部分对象都是创建出来之后很快就不再使用可以被回收比如用户获取订单数据订单数据返回给用户之后就可以释放了。老年代中会存放长期存活的对象比如Spring的大部分bean对象在程序启动之后就不会被回收了。在虚拟机的默认设置中新生代大小要远小于老年代的大小。 分代GC算法将堆分成年轻代和老年代主要原因有 可以通过调整年轻代和老年代的比例来适应不同类型的应用程序提高内存的利用率和性能。新生代和老年代使用不同的垃圾回收算法新生代一般选择复制算法老年代可以选择标记-清除和标记-整理算法由程序员来选择灵活度较高。分代的设计中允许只回收新生代minor gc如果能满足对象分配的要求就不需要对整个堆进行回收(full gc)STW时间就会减少。尽可能做minor gc少做full gc尽量降低垃圾回收对程序运行的影响 垃圾回收器垃圾回收算法实现 垃圾回收器是垃圾回收算法的具体实现。由于垃圾回收器分为年轻代和老年代除了G1既能管控新生代也可以管控老年代之外新生代、老年代的垃圾回收器必须按照hotspot的要求成对组合进行使用需要根据JDK的版本来选择相应的组合 具体的组合关系如下 JVM底层源码中在某些特殊情况CMS回收器会调用Serial Old回收器 Arthas查看所使用的垃圾回收器 不能直接看到使用了什么垃圾回收器只能根据算法来推断 年轻代-Serial垃圾回收器 Serial是一种单线程串行回收年轻代的垃圾回收器。 回收年代和算法 年轻代复制算法 优点 单CPU处理器下吞吐量非常出色 缺点 多CPU下吞吐量不如其他垃圾回收器单线程只使用了一个CPU堆如果偏大会让用户线程处于长时间的等待 适用场景 Java编写的客户端程序或者硬件配置有限CPU核数不多的场景 如何使用 -XX:UseSerialGC新生代、老年代都使用串行回收器。 老年代-SerialOld垃圾回收器 SerialOld是Serial垃圾回收器的老年代版本采用单线程串行回收 回收年代和算法 老年代标记-整理算法 优点 单CPU处理器下吞吐量非常出色 缺点 多CPU下吞吐量不如其他垃圾回收器堆如果偏大会让用户线程处于长时间的等待 适用场景 与Serial垃圾回收器搭配使用或者在CMS特殊情况下使用 如何使用 -XX:UseSerialGC新生代、老年代都使用串行回收器。 年轻代-ParNew垃圾回收器 ParNew垃圾回收器本质上是对Serial在多CPU下的优化使用多线程进行垃圾回收 回收年代和算法 年轻代复制算法 优点 多CPU处理器下停顿时间较短 缺点 吞吐量和停顿时间不如G1所以在JDK9之后不建议使用 适用场景 JDK8及之前的版本中与CMS老年代垃圾回收器搭配使用 如何使用 -XX:UseParNewGC新生代使用ParNew回收器 老年代使用串行回收器 老年代-CMS(Concurrent Mark Sweep)垃圾回收器 CMS垃圾回收器关注的是系统的暂停时间尽量减少STW优化用户体验允许用户线程和垃圾回收线程在某些步骤中同时执行减少了用户线程的等待时间。 回收年代和算法 老年代标记清除算法 优点 系统由于垃圾回收出现的停顿时间较短用户体验好 缺点 1、内存碎片问题 2、退化问题在某些特定情况会退化为SerialOld这种单线程回收器 3、浮动垃圾问题回收过程有的垃圾回收不掉 适用场景 大型的互联网系统中用户请求数据量大、频率高的场景比如订单接口、商品接口等 使用 XX:UseConcMarkSweepGC可以分别设置年轻代和老年代的回收器 CMS执行步骤 初始标记用极短的时间标记出GC Roots能直接关联到的对象。并发标记, 标记所有的对象用户线程不需要暂停。虽然并发标记是和用户线程一起执行但是如果并发标记占用的资源较高也会影响用户线程重新标记并发由于并发标记阶段有些对象会发生了变化存在错标用的对象本来是存活的标记之后用户线程把它变得不存活了导致错标、漏标因为是并发的可能有的对象是用户线程刚刚创建出来的就会导致漏标等情况需要重新标记。并发清理清理死亡的对象用户线程不需要暂停。 注只有初始标记、重新标记阶段会出现STW 缺点 1、CMS使用了标记-清除算法在垃圾收集结束之后会出现大量的内存碎片为了不影响对象的分配CMS会在Full GC时进行碎片的整理。这样会导致用户线程暂停可以使用-XX:CMSFullGCsBeforeCompactionN 参数默认0调整N次Full GC之后再整理。 2.、无法处理在并发清理过程中产生的“浮动垃圾”不能做到完全的垃圾回收在本次清理的过程中用户线程并发创建了一些对象但是很快没有再使用这些对象在这次清理中没有得到回收需要等到下一次清理所以称这些为浮动垃圾。 3、如果老年代内存不足无法分配对象CMS就会退化成Serial Old单线程回收老年代。 并发线程数 在CMS中并发阶段运行时的线程数可以通过-XX:ConcGCThreads参数设置由系统计算得出计算公式为(-XX:ParallelGCThreads定义的线程数 3) / 4 ParallelGCThreads是STW停顿之后的并行线程数 ParallelGCThreads是由处理器核数决定的 1、当cpu核数小于8时ParallelGCThreads CPU核数 2、否则 ParallelGCThreads 8 (CPU核数 – 8 )*5/8 我的电脑上逻辑处理器有12个所以ParallelGCThreads 8 12 - 8* 5/8 10ConcGCThreads (-XX:ParallelGCThreads定义的线程数 3) / 4 10 3 / 4 3 最终可以得到这张图 并发标记和并发清理阶段会使用3个线程并行处理。重新标记阶段会使用10个线程处理。由于CPU的核心数有限并发阶段会影响用户线程执行的性能。 年轻代-Parallel Scavenge垃圾回收器 Parallel Scavenge是JDK8默认的年轻代垃圾回收器多线程并行回收关注的是系统的吞吐量。为了拉高吞吐量PS会自动调整堆内存大小调整新生代、老年代内存大小、晋升的阈值。 回收年代和算法 年轻代复制算法 优点 吞吐量高而且支持手动设置参数控制吞吐量。为了提高吞吐量虚拟机会动态调整堆的参数用户只需要设置吞吐量不需要设置内存大小等其他参数 缺点 不能保证单次的停顿时间但是支持设置STW时间 适用场景 后台任务不需要与用户交互并且容易产生大量的对象。比如大数据的处理大文件导出 常用参数 Parallel Scavenge允许手动设置最大暂停时间和吞吐量。Oracle官方建议在使用这个组合时不要设置堆内存的最大值垃圾回收器会根据最大暂停时间和吞吐量自动调整内存大小。 最大暂停时间-XX:MaxGCPauseMillisn 设置每次垃圾回收时的最大停顿毫秒数吞吐量-XX:GCTimeRation 设置吞吐量为n用户线程执行时间 n/(n 1)自动调整内存大小, -XX:UseAdaptiveSizePolicy设置可以让垃圾回收器根据吞吐量和最大停顿的毫秒数自动调整内存大小这个参数默认是开启的Oracle建议使用PS组合的时候不要设置堆内存的最大值让垃圾回收器自动调整 注最大暂停时间和吞吐量这两个指标是冲突的垃圾回收器会尽量满足最大暂停时间有时候设置得太小是没办法满足的会超出所设置得最大暂停时间牺牲吞吐量。如果要同时设置最大暂停时间和吞吐量要多做测试让它们比较协调 老年代-Parallel Old垃圾回收器 Parallel Old是为Parallel Scavenge收集器设计的老年代版本利用多线程并发收集。 回收年代和算法 老年代标记-整理算法其实是标记清除整理 优点 并发收集在多核CPU下效率较高 缺点 暂停时间会比较长 适用场景 与Parallel Scavenge配套使用 如何使用 JDK8设置参数就是默认使用该回收器 参数-XX:UseParallelGC 或 -XX:UseParallelOldGC可以使用Parallel Scavenge Parallel Old这种组合。 测试 -XX:PrintFlagsFinal可以在程序启动的时候打印所有配置项的最终值可以看看自动调整功能有没有开启 import java.io.IOException; import java.util.ArrayList; import java.util.List;/*** 垃圾回收器案例3*/ //-XX:UseSerialGC -Xmn1g -Xmx16g -XX:SurvivorRatio8 -XX:PrintGCDetails -verbose:gc -XX:PrintFlagsFinal //-XX:UseParNewGC -Xmn1g -Xmx16g -XX:SurvivorRatio8 -XX:PrintGCDetails -verbose:gc //-XX:UseConcMarkSweepGC //-XX:UseG1GC -Xmn8g -Xmx16g -XX:SurvivorRatio8 -XX:PrintGCDetails -verbose:gc MaxGCPauseMillis //-XX:PrintFlagsFinal -XX:GCTimeRatio 19 -XX:MaxGCPauseMillis10 -XX:UseAdaptiveSizePolicy public class GcDemo2 {public static void main(String[] args) throws IOException {int count 0;ListObject list new ArrayList();while (true){//System.out.println(count);if(count % 10240 0){list.clear();} // byte[] bytes new byte[1024 * 1024 * 1];list.add(new byte[1024 * 1024 * 1 / 2]); // System.gc();}} }【测试最大暂停时间10】 内存大小如下 【测试最大暂停时间1】 结论堆内存越小垃圾回收暂停时间越短 G1(Garbage First)垃圾回收器极力推荐 JDK9之后默认的垃圾回收器是G1Garbage First垃圾回收器。 Parallel Scavenge关注吞吐量允许用户设置最大暂停时间 但是会减少年轻代可用空间的大小。CMS关注暂停时间但是吞吐量方面会下降。而G1设计目标就是将上述两种垃圾回收器的优点融合 JDK9之后强烈建议使用G1垃圾回收器。在JDK7、8上G1可能不是很成熟要慎重考虑 G1出现之前的垃圾回收器年轻代和老年代一般是连续的如下图 G1内存结构发生了变化G1的整个堆会被划分成多个大小相等的区域称之为区Region区域不要求是连续的。分为Eden、Survivor、Old区。Region的大小通过堆空间大小/2048计算得到也可以通过参数-XX:G1HeapRegionSize32m指定(其中32m指定region大小为32M)Region size必须是2的指数幂取值范围从1M到32M。 G1垃圾回收有两种方式 1、年轻代回收Young GC 2、混合回收Mixed GC 年轻代回收Young GC 只回收年轻代回收Eden区和Survivor区中不用的对象。会导致STWG1中可以通过参数 -XX:MaxGCPauseMillisn默认200 设置每次垃圾回收时的最大暂停时间毫秒数G1垃圾回收器会尽可能地保证暂停时间。 【Young GC步骤】 1、新创建的对象会存放在Eden区。当G1判断年轻代区不足max默认60%如果年轻代的内存占了总堆的60%以上就要Young GC无法分配对象时需要回收时会执行Young GC。 2、标记出Eden和Survivor区域中的存活对象 3、根据配置的**最大暂停时间选择某些区域和其他回收器的区别其他回收器会回收负责的所有区域**将存活对象复制到一个新的Survivor区中年龄1使用复制算法不会产生内存碎片清空这些区域。 G1在进行Young GC的过程中会去记录每次垃圾回收时每个Eden区和Survivor区的平均耗时以作为下次回收时的参考依据。这样就可以根据配置的最大暂停时间计算出本次回收时最多能回收多少个Region区域了。比如-XX:MaxGCPauseMillisn默认200每个Region回收耗时40ms那么这次回收最多只能回收4个Region。 4、后续Young GC时与之前相同只不过Survivor区中存活对象会被搬运到另一个Survivor区。 5、当某个存活对象的年龄到达阈值默认15将被放入老年代。原来的Survivor中年龄没有到达15的对象还是会迁移到新的Survivor中。 6、部分对象如果大小超过Region的一半会直接放入老年代这类老年代被称为Humongous翻译巨大的区。比如堆内存是4G每个Region是2M只要一个大对象超过了1M就被放入Humongous区如果对象过大会横跨多个Region一个Region放不完。 7、多次回收之后会出现很多Old老年代区此时总堆占有率达到阈值时 -XX:InitiatingHeapOccupancyPercent默认45%会触发混合回收Mixed GC回收所有年轻代和部分老年代的对象以及大对象区。采用复制算法来完成。 混合回收Mixed GC 混合回收分为如下步骤看起来和CMS差不多但是步骤里面都是有区别的 初始标记initial mark并发标记concurrent mark最终标记remark或者Finalize Marking只管漏标不管新创建、不再关联的对象。这里使用的算法远比CMS的快并发清理cleanupG1对老年代的清理会选择存活度最低的区域来进行回收A区100个对象只存活1个B区100个对象存活99个G1选择回收A区这样可以保证回收效率最高这也是G1Garbage first名称的由来。最后清理阶段使用复制算法不会产生内存碎片。 注意**如果清理过程中发现没有足够的空Region存放转移的对象会出现Full GC**单线程执行标记-整理算法此时会导致用户线程的暂停影响用户的正常使用。所以尽量保证应该用的堆内存有一定多余的空间如果堆内存的占用比较高就要考虑优化。 如何使用G1垃圾回收器 参数1 -XX:UseG1GC 打开G1的开关JDK9之后默认不需要打开 参数2-XX:MaxGCPauseMillis毫秒值 最大暂停的时 回收年代和算法 年轻代老年代复制算法 优点 支持巨大的堆空间回收并有较高的吞吐量。对比较大的堆延迟可控如超过6G的堆回收时不会产生内存碎片并发标记的SATB算法效率高比CMS的算法效率高支持多CPU并行垃圾回收允许用户设置最大暂停时间 缺点 JDK8的早期版本还不够成熟 适用场景 JDK8最新版本、JDK9之后建议默认使用 测试 使用以下代码测试g1垃圾回收器打印出每个阶段的时间: package chapter04.gc;import java.io.IOException; import java.util.ArrayList; import java.util.List;/*** 垃圾回收器案例3*/ //-XX:UseG1GC -Xmn8g -Xmx16g -XX:SurvivorRatio8 -XX:PrintGCDetails -verbose:gc public class GcDemo2 {public static void main(String[] args) throws IOException {int count 0;ListObject list new ArrayList();while (true){//System.out.println(count);if(count % 10240 0){list.clear();} // byte[] bytes new byte[1024 * 1024 * 1];list.add(new byte[1024 * 1024 * 1 / 2]); // System.gc();}} }每个region大小为2m一共有84个young区26个幸存者区。 初始标记花了0.0478秒初始标记是伴随着年轻代回收的0.0478秒是两个操作的总时间 并发标记总共耗时10ms不会产生STW。 最终标记效率较高 清理阶段 垃圾回收器总结 垃圾回收器的组合关系虽然很多但是针对几个特定的版本比较好的组合选择如下 JDK8及之前 ParNew CMS关注暂停时间Parallel Scavenge Parallel Old (关注吞吐量)G1JDK8之前不建议较大堆并且关注暂停时间 JDK9之后G1默认。从JDK9之后由于G1日趋成熟JDK默认的垃圾回收器已经修改为G1所以强烈建议在生产环境上使用G1。G1的实现原理将在《原理篇》中介绍更多前沿技术ZGC最新的垃圾回收器、GraalVM将在《高级篇》中介绍。 几个问题 Java中有哪几块内存需要垃圾回收 方法区回收Java程序员少关注 有哪几种常见的引用类型 强引用最常见的引用方式由可达性分析算法来判断软引用对象在没有强引用情况下内存不定时会回收弱引用对象在没有强引用情况下会直接回收虚引用通过虚引用知道对象被回收了终结器引用对象回收时可以自救不建议使用 有哪几种常见的垃圾回收算法 常见的垃圾回收器有哪些 SerialSerial Old单线程回收适用于单核CPU场景ParNewCMS暂停时间较短适用于大型互联网应用中与用户交互的部分Paraller ScavengeParallel Old吞吐量高适用于后台进行大量数据操作G1适用于较大堆具有可控暂停时间 文章说明 该文章是本人学习 黑马程序员 的学习笔记文章中大部分内容来源于 黑马程序员 的视频黑马程序员JVM虚拟机入门到实战全套视频教程java大厂面试必会的jvm一套搞定丰富的实战案例及最热面试题也有部分内容来自于自己的思考发布文章是想帮助其他学习的人更方便地整理自己的笔记或者直接通过文章学习相关知识如有侵权请联系删除最后对 黑马程序员 的优质课程表示感谢。
文章转载自:
http://www.morning.fhkr.cn.gov.cn.fhkr.cn
http://www.morning.bpmfr.cn.gov.cn.bpmfr.cn
http://www.morning.ctsjq.cn.gov.cn.ctsjq.cn
http://www.morning.nqypf.cn.gov.cn.nqypf.cn
http://www.morning.gcqkb.cn.gov.cn.gcqkb.cn
http://www.morning.mslhq.cn.gov.cn.mslhq.cn
http://www.morning.rxsgk.cn.gov.cn.rxsgk.cn
http://www.morning.xppj.cn.gov.cn.xppj.cn
http://www.morning.bhdyr.cn.gov.cn.bhdyr.cn
http://www.morning.tsflw.cn.gov.cn.tsflw.cn
http://www.morning.splcc.cn.gov.cn.splcc.cn
http://www.morning.ktrdc.cn.gov.cn.ktrdc.cn
http://www.morning.wdxr.cn.gov.cn.wdxr.cn
http://www.morning.ntlxg.cn.gov.cn.ntlxg.cn
http://www.morning.trfrl.cn.gov.cn.trfrl.cn
http://www.morning.rpth.cn.gov.cn.rpth.cn
http://www.morning.tngdn.cn.gov.cn.tngdn.cn
http://www.morning.pmlgr.cn.gov.cn.pmlgr.cn
http://www.morning.snmsq.cn.gov.cn.snmsq.cn
http://www.morning.clkjn.cn.gov.cn.clkjn.cn
http://www.morning.muniubangcaishui.cn.gov.cn.muniubangcaishui.cn
http://www.morning.qlznd.cn.gov.cn.qlznd.cn
http://www.morning.dpdr.cn.gov.cn.dpdr.cn
http://www.morning.guofenmai.cn.gov.cn.guofenmai.cn
http://www.morning.mdmxf.cn.gov.cn.mdmxf.cn
http://www.morning.npmx.cn.gov.cn.npmx.cn
http://www.morning.pqktp.cn.gov.cn.pqktp.cn
http://www.morning.rjrnx.cn.gov.cn.rjrnx.cn
http://www.morning.cnfxr.cn.gov.cn.cnfxr.cn
http://www.morning.yltyr.cn.gov.cn.yltyr.cn
http://www.morning.mdwlg.cn.gov.cn.mdwlg.cn
http://www.morning.rpkl.cn.gov.cn.rpkl.cn
http://www.morning.mhlsx.cn.gov.cn.mhlsx.cn
http://www.morning.mjglk.cn.gov.cn.mjglk.cn
http://www.morning.sgqw.cn.gov.cn.sgqw.cn
http://www.morning.bzcjx.cn.gov.cn.bzcjx.cn
http://www.morning.gbfzy.cn.gov.cn.gbfzy.cn
http://www.morning.nxfwf.cn.gov.cn.nxfwf.cn
http://www.morning.kjdxh.cn.gov.cn.kjdxh.cn
http://www.morning.zqybs.cn.gov.cn.zqybs.cn
http://www.morning.spxsm.cn.gov.cn.spxsm.cn
http://www.morning.ldhbs.cn.gov.cn.ldhbs.cn
http://www.morning.bkgfp.cn.gov.cn.bkgfp.cn
http://www.morning.ytnn.cn.gov.cn.ytnn.cn
http://www.morning.lxthr.cn.gov.cn.lxthr.cn
http://www.morning.zyndj.cn.gov.cn.zyndj.cn
http://www.morning.yhwmg.cn.gov.cn.yhwmg.cn
http://www.morning.tqwcm.cn.gov.cn.tqwcm.cn
http://www.morning.xkhxl.cn.gov.cn.xkhxl.cn
http://www.morning.hjlwt.cn.gov.cn.hjlwt.cn
http://www.morning.cpctr.cn.gov.cn.cpctr.cn
http://www.morning.kzbpx.cn.gov.cn.kzbpx.cn
http://www.morning.lhyhx.cn.gov.cn.lhyhx.cn
http://www.morning.wdhhz.cn.gov.cn.wdhhz.cn
http://www.morning.fkflc.cn.gov.cn.fkflc.cn
http://www.morning.kongpie.com.gov.cn.kongpie.com
http://www.morning.ktlfb.cn.gov.cn.ktlfb.cn
http://www.morning.dwwlg.cn.gov.cn.dwwlg.cn
http://www.morning.kzqpn.cn.gov.cn.kzqpn.cn
http://www.morning.fjlsfs.com.gov.cn.fjlsfs.com
http://www.morning.tdzxy.cn.gov.cn.tdzxy.cn
http://www.morning.rwtlj.cn.gov.cn.rwtlj.cn
http://www.morning.tnrdz.cn.gov.cn.tnrdz.cn
http://www.morning.tyhfz.cn.gov.cn.tyhfz.cn
http://www.morning.nlryq.cn.gov.cn.nlryq.cn
http://www.morning.ftlgy.cn.gov.cn.ftlgy.cn
http://www.morning.errnull.com.gov.cn.errnull.com
http://www.morning.lizimc.com.gov.cn.lizimc.com
http://www.morning.xpzrx.cn.gov.cn.xpzrx.cn
http://www.morning.kydrb.cn.gov.cn.kydrb.cn
http://www.morning.nbpqx.cn.gov.cn.nbpqx.cn
http://www.morning.khtjn.cn.gov.cn.khtjn.cn
http://www.morning.mnwb.cn.gov.cn.mnwb.cn
http://www.morning.dyght.cn.gov.cn.dyght.cn
http://www.morning.rgkd.cn.gov.cn.rgkd.cn
http://www.morning.bdypl.cn.gov.cn.bdypl.cn
http://www.morning.lpzqd.cn.gov.cn.lpzqd.cn
http://www.morning.zdhxm.com.gov.cn.zdhxm.com
http://www.morning.4q9h.cn.gov.cn.4q9h.cn
http://www.morning.frnjm.cn.gov.cn.frnjm.cn
http://www.tj-hxxt.cn/news/256761.html

相关文章:

  • 如何加强网站建设网站建设动态代码
  • 网站开发公司需要那些硬件设备中山网站网站建设
  • 做网站用的笔记本配置房子设计图
  • 企业官方网站建设费用郑州公司做网站汉狮
  • 技术支持 东莞网站建设机械加工莱芜论坛网
  • 深圳出行最新通告wordpress优化数据
  • 四川建设监理协会网站长沙网站优化厂家
  • 济南做公司网站新公司名称取名
  • 安庆网站建设专业常用的网站开发
  • 网站付款链接怎么做的网站的收费标准
  • 山东平台网站建设制作建筑安全类网站
  • 同城便民网站开发各大免费推广网站
  • 企业微网站怎么建设广西网站建设方案
  • 上海做网站的小公司网页模板免费下载平台
  • 百度给做网站收费多少钱石狮市
  • 甘州区建设局网站深圳软件开发工程师
  • 典型的营销型企业网站个人或主题网站建设实验报告
  • 网站开发如何入账微信公众号微网站怎么做
  • 公司网站推广的方法简易购物网站html代码
  • 网站推广费用ihanshi寻找哈尔滨网站建设
  • 水墨风格网站源码华创网站建设
  • 英文营销网站域名申请网站
  • 酷炫网站房地产信息网查询系统
  • 佛山网站建设seo优化建一个网站需要哪些知识
  • 宣传网站设计salient wordpress
  • 网站开发内部工单路桥做网站
  • 移动网站不备案吗陕西省两学一做网站
  • 建站大师计算机培训班有哪些
  • 如何阿里巴巴网站做推广方案企业管理软件erp
  • 上海备案证查询网站查询网站查询重庆营销型网站随做的好