学习JVM
java虚拟机
流程:helloworld.java----(javac编译)----helloworld.class-------(java运行)——JVM——机器码
JVM功能
*解释和运行
*内存管理
*即时编译(跨平台-慢一点)jit
(反复用到的代码 解释保存再内存里面)
jvm主要组成:
*类加载器,
*运行时数据区(jvm管理的内存),
*执行引擎(即时编译器,解释器,垃圾回收器)
字节码文件组成
工具:notepad++是不可以的 工具用 jclasslib
组成:基本信息,常量池,字段,方法,属性
基本信息
1, magic(魔数):确认是字节码文件(前缀固定0xcofe base)
2,主版本号 : (-44就等于jdk)-可以确定运行jdk和编译的版本是否一致
常量池
避免重复内容的重复定义,浪费空间
*常量池的数据都有一个编号
*字节码指令中提高编号引用常量池的过“符合引用”
方法
JVM:堆,栈,方法区
栈:是线程用的,(后进先出)(执行完就会释放)
*有多个栈帧组成,对应着每次方法调用时所占的内存
*每个线程只能有一个活动栈帧,对应正在执行的方法
递归会导致栈溢出
一个线程一个栈,一个方法一个栈帧
JVM调优
栈和栈帧
程序启动 在jvm的加载机制
1,将代码加载到 方法区(类加载)
2,栈启动mian主线程: 然后给线程的方法分配栈帧
new出来的对象放堆,方法执行完栈帧就会释放
!!!!
1线程启动就会分配栈,和程序计数器
2执行方法就会右栈帧(线程的栈帧是独立的)
3方法执行完栈帧就会释放,线程执行完栈就释放
程序计数器: *记录每个线程的执行到哪里-记录当前线程的状态-线程独有的)多线程切换用的
*由字节码执行引擎修改里面的内容
多线程的时候 操作系统的任务调度器 分配时间片
栈帧存的数据
1局部变量表(int = 1等)
2操作数栈(加减乘除操作时的数据空间,计算完就释放)
3动态链接、(每个方法内存地址,映射 元空间/方法区)
4方法出口(方法执行完 要继续执行main方法的下一个)
堆:new出来的对象---公共
栈(线程):放线程的--私有
栈里面的对象(保证地址)是指向堆的
本地方法栈:(用native修饰的方法-底层是用C++实现的)--私有
Thread类的本地方法 System类的本地方法
方法区(元空间):常理,静态变量,类信息---公共
方法区的对象也是指向堆的
JDK诊断工具
*Java VisualVM是JDK自带的基本调优工具之一
*jdk自带诊断命令
*arthas(阿里巴巴)诊断工具
。可以快速点位cpu高代码
。可以快速点位锁代码
。线上运行代码反编译
JVM堆的组成
1. 新生代(Young Generation):新生代是堆的一部分,用于存储新创建的对象。它又分为Eden区、Survivor区(通常有两个)。
- Eden区:新创建的对象首先分配在Eden区。
- Survivor区:当Eden区满时,存活的对象会被移到Survivor区。Survivor区一般有两个,分别称为From区和To区。存活的对象会在From区和To区之间进行复制,经过多次垃圾回收后(每次年龄+1),仍然存活的对象会被移动到老年代(15岁/6 CMS)。
2. 老年代(Old Generation):老年代用于存储长时间存活的对象。当对象经过多次垃圾回收后仍然存活,它们会被移动到老年代。(full GC老年代也回收)
对象年龄判断机制(如果对象大于Survivor 50%会直接放老年代)
3. 永久代(Permanent Generation):永久代用于存储类的元数据(metadata)和方法信息(method information)。在JDK 8之后,永久代被元空间(Metaspace)所取代。元空间不再位于堆中,而是位于本地内存中。
4. 堆外内存(Off-Heap Memory):堆外内存是指不受JVM堆管理的内存,通常由本地方法直接分配和释放。堆外内存包括直接内存(Direct Memory)和本地内存(Native Memory)。
需要注意的是,JVM的堆大小可以通过启动参数进行配置,例如-Xmx和-Xms参数用于设置堆的最大和初始大小。堆的大小对于应用程序的性能和内存使用有重要影响,需要根据具体应用场景进行合理配置。
堆-对象直接进入老年代
1,动态年龄判断:survivor从小到大累加年龄超过空间的50%(默认) ,后面的会直接晋升老年代
2 ,大对象直接接入 大对象就是需要大量连续内存空间的对象(比如:字符串、数组)。
复制很耗性能,
JVM参数XX:PretenureSizeThreshold 可以设置大 对象的大小,如果对象超过设置大小会直接进入老年代,不会进入年轻代,这个参数只在 Serial 和ParNew两个收集器下 有效。比如设置JVM参数:
-XX:PretenureSizeThreshold=1000000 (单位是字节) -XX:+UseSerialGC ,再执行下上
面的第一 个程序会发现大对象直接进了老年代
为什么要这样呢?(原因)
为了避免为大对象分配内存时的复制操作而降低效率。
(解决——加大Survivor区,并用G1分代收集)
3,老年代空间担保机制
From区和To区的作用如下:
1. 存储存活对象:在Minor GC过程中,存活的对象会被移动到Survivor区的From区。
2. 进行对象复制:在下一次Minor GC之前,存活的对象会从From区复制到To区。
3. 清空From区:复制完成后,From区会被清空,为下一次Minor GC做准备。
4. 交换From区和To区:在下一次Minor GC时,From区和To区会互换角色,即From区变为To区,To区变为From区。
通过交替使用From区和To区,Survivor区可以实现对象的复制和清理,以进行有效的垃圾回收。这种复制算法被称为"标记-复制"(Mark and Copy)算法,它可以有效地处理新生代中的对象,并减少内存碎片化的问题。
需要注意的是,Survivor区的大小可以通过JVM参数进行调整,以适应不同应用程序的需求。一般来说,Survivor区的大小应该合理设置,避免过小导致频繁的对象复制,或过大导致浪费内存空间。
垃圾回收
Minor GC 和 Full GC 垃圾回收会导致 stw(Stop the World")这个系统停顿
为什么要这样设计:不停止用户线程里面对象就会不断变化,实现简单,
JVM的可达性分析
JVM的可达性分析(Reachability Analysis)是垃圾回收的一种核心算法,用于确定哪些对象是可达的(reachable)或不可达的(unreachable),从而确定哪些对象应该被回收。
可达性分析的基本原理是从一组称为"GC Roots"的根对象开始,通过遍历对象引用链,标记所有与根对象直接或间接相连的对象为可达对象。而未被标记的对象则被认为是不可达的,即无法通过任何引用链访问到的对象。这些不可达对象将被垃圾回收器识别并回收,释放其占用的内存空间。
GC Roots包括以下几种类型的对象:
1. 虚拟机栈(VM Stack)中的引用对象。
2. 方法区(Method Area)中类静态属性引用的对象。
3. 方法区中常量引用的对象。
4. 本地方法栈(Native Method Stack)中JNI引用的对象。
通过从GC Roots出发进行可达性分析,JVM可以确定哪些对象是活动的,即仍然被引用和使用的对象,而哪些对象是不再使用的,可以被回收的对象。
可达性分析是现代垃圾回收器中常用的算法,它具有高效、准确的特点,并能够处理复杂的对象引用关系。通过可达性分析,JVM可以自动管理内存,释放不再使用的对象,从而提高系统的性能和资源利用率。
Minor GC
Minor GC(Minor Garbage Collection)是Java虚拟机(JVM)中的一种垃圾回收操作,主要针对新生代进行回收。新生代是Java堆内存中的一部分,用于存储新创建的对象。Minor GC的目标是清理新生代中的无用对象,以释放内存空间。
在Minor GC过程中,垃圾回收器会扫描新生代中的对象,并标记那些仍然存活的对象。然后,它会将存活的对象复制到Survivor区(通常是From区),同时清理掉无用的对象。在复制过程中,存活的对象会被移动到Survivor区的To区。最后,From区会被清空,为下一次垃圾回收做准备。
Minor GC通常发生在新生代中的Eden区(新对象的分配区域)空间不足时。当Eden区满了之后,会触发Minor GC来回收无用的对象,以便为新对象腾出空间。通常情况下,大部分对象在新生代中很快被回收,只有少部分对象会进入老年代(Old Generation)。
相比于Full GC(Full Garbage Collection),Minor GC的开销较小,回收的对象数量也较少。因此,Minor GC的执行时间通常较短,对应用程序的停顿时间影响较小。它是Java堆内存中垃圾回收的常见操作之一,用于保证新生代的内存空间的有效利用。
Full GC
Full GC(Full Garbage Collection)是Java虚拟机(JVM)中垃圾回收的一种操作,它是对整个堆内存进行回收的过程。Full GC会清理整个堆内存中的所有对象,包括年轻代和老年代。
Full GC通常是在进行一次完整的垃圾回收之前执行的,目的是回收所有不再被引用的对象,释放内存空间。Full GC的执行会导致应用程序的停顿,因为在此期间,所有的应用线程都会被暂停,直到垃圾回收完成。
Full GC通常发生在以下情况下:
1. 当堆内存空间不足时(如老年代太多),无法分配新的对象时,会触发Full GC来回收内存。
2. 当执行System.gc()方法或者调用Runtime.getRuntime().gc()方法时,可能会触发Full GC。
3. 当永久代(Permanent Generation)空间不足时(在JDK 8及之前的版本中),会触发Full GC来回收永久代。
Full GC的执行时间通常比部分垃圾回收(如年轻代的Minor GC)更长,并且会导致较长的停顿时间。因此,对于性能敏感的应用程序,需要合理配置堆内存大小,以减少Full GC的频率和影响。
总之,Full GC是Java虚拟机中对整个堆内存进行的垃圾回收操作,它会清理整个堆内存中的所有对象,包括年轻代和老年代,通常会导致较长的停顿时间。
JVM中常见的垃圾回收算法和策略包括以下几种:
1. 标记-清除算法(Mark and Sweep):该算法分为两个阶段,首先标记所有活动对象,然后清除未标记的对象。但是,标记-清除算法会产生内存碎片,可能会导致内存分配效率降低。
2. 复制算法(Copying):该算法将堆内存划分为两个相等大小的区域,每次只使用其中一个区域。当一个区域满时,将存活的对象复制到另一个区域,然后清除当前区域中的所有对象。复制算法消耗的时间较短,但会浪费一部分内存空间。
3. 标记-压缩算法(Mark and Compact):该算法首先标记所有活动对象,然后将活动对象向一端移动,最后清理掉边界以外的内存空间。标记-压缩算法消除了内存碎片,但可能会导致对象移动的开销较大。
4. 分代收集算法(Generational Collection):该算法根据对象的生命周期将堆内存划分为不同的代(Generation),如新生代(Young Generation)和老年代(Old Generation)。新生代中的对象生命周期较短,采用复制算法;而老年代中的对象生命周期较长,采用标记-压缩算法。
5. 并发标记清除算法(Concurrent Mark and Sweep):该算法允许垃圾回收器与应用程序并发执行,减少停顿时间。它通过在标记和清除阶段之间允许应用程序继续运行来提高性能。
6. G1收集器(Garbage-First Collector):G1收集器是一种面向服务端应用的垃圾回收器,它将堆内存划分为多个大小相等的区域(Region),通过并发标记、并发清除和并发整理来实现高效的垃圾回收。
这些垃圾回收算法和策略的选择取决于应用程序的性能需求和内存特点。JVM根据实际情况自动选择适当的垃圾回收器和算法来管理内存。
垃圾收集器
G1(Garbage First)收集器是Java虚拟机(JVM)中的一种垃圾收集器。它在JDK 7u4版本中首次引入,并在JDK 9及以后的版本中成为默认的垃圾收集器。G1收集器采用了分代收集和并发标记整理的方式,旨在提供可预测的停顿时间和高吞吐量的垃圾收集性能。
G1收集器的主要特点和优势包括:
1. 分代收集:G1收集器将堆内存划分为多个大小相等的区域(Region),每个区域可以是Eden区、Survivor区或Old区。这种分代的方式可以更好地适应不同对象的生命周期和内存使用模式。
2. 并发标记:G1收集器使用并发标记算法,在垃圾收集过程中,可以与应用程序线程并发执行标记阶段,减少垃圾收集对应用程序的影响。
3. 空闲区域优先回收:G1收集器的名字“Garbage First”即表示它优先回收垃圾最多的区域。这种策略可以最大程度地回收垃圾,提高垃圾收集的效率。
4. 可预测的停顿时间:G1收集器通过将堆内存划分为多个区域,并使用增量式的并发标记算法,可以控制垃圾收集的停顿时间。这对于要求低延迟的应用程序非常重要。
5. 自适应调节:G1收集器会根据堆内存的使用情况和垃圾收集的效果,动态地调整各个阶段的参数,以达到最优的垃圾收集性能。
G1收集器适用于大内存、多核处理器的应用场景,尤其是需要低延迟和高吞吐量的服务端应用程序。它在处理大堆内存和大量对象时表现出色,并且可以通过调整参数来满足不同应用程序的需求。
相关文章:
学习JVM
java虚拟机 流程:helloworld.java----(javac编译)----helloworld.class-------(java运行)——JVM——机器码JVM功能 *解释和运行 *内存管理 *即时编译(跨平台-慢一点)jit (反复用到的代码 解释保存再内存里面)…...
Oracle MongoDB
听课的时候第一次碰到,可以了解一下吧,就直接开了墨者学院的靶场 #oracle数据库 Oracle数据库注入全方位利用 - 先知社区 这篇写的真的很好 1.判断注入点 当时找了半天没找到 看样子是找到了,测试一下看看 id1 and 11 时没有报错 2.判断字段…...
Linux-RedHat系统-安装 中间件 Tuxedo
安装步聚 一、中间件安装包: tuxedo121300_64_Linux_01_x86 Tuxedo下载地址: Oracle Tuxedo Downloads 二、新建用户: (创建Oracle用户时,需要root权限操作) 创建用户: # useradd oracle …...
PHP中的依赖注入是怎样的?
依赖注入(Dependency Injection,DI)是一种设计模式,它用于解耦组件之间的依赖关系,提高代码的可维护性、可测试性和灵活性。在 PHP 中,依赖注入通常通过构造函数注入、方法注入或属性注入来实现。 以下是依…...
Python求小于m的最大10个素数
为了找到小于m的最大10个素数,我们首先需要确定m的值。然后,我们可以使用一个简单的算法来检查每一个小于m的数字是否是素数。 下面是一个Python代码示例,可以找到小于m的最大10个素数: def is_prime(n): if n < 1: …...
系统的安全性设计
要设计一个安全的系统,除了要了解一些前面讲到的常用的保护手段和技术措施外,还要对系统中可能出现的安全问题或存在的安全隐患有充分的认识,这样才能对系统的安全作有针对性的设计和强化,即“知己知彼,百战百胜”。 下…...
美容店预约小程序搭建指南
随着互联网的发展,越来越多的传统行业开始尝试将业务与互联网相结合,以提供更加便捷、高效的服务。美容行业也不例外。本文将通过使用第三方制作平台,如乔拓云网,指导您如何搭建一个美观实用的美容店预约小程序,帮助您…...
AI:ElasticSearch
ElasticSearch是一款开源的分布式搜索引擎和数据分析引擎,主要用于处理海量数据并提供近实时的搜索和分析功能。它具有全文检索、结构化检索和数据分析等特点,能够满足各种复杂的搜索需求。ElasticSearch使用Java编写,可以运行在多个服务器上…...
如何用 Python 代码打包成一个可执行的 exe 文件?
将Python代码打包成可执行的EXE文件通常需要使用第三方工具,其中PyInstaller是一个流行的选择。PyInstaller可以将Python脚本打包成独立的可执行文件,无需用户安装Python解释器。 打包Python代码成EXE文件的步骤 步骤1:安装PyInstaller 首…...
【Hive】——CLI客户端(bin/beeline,bin/hive)
1 HiveServer、HiveServer2 2 bin/hive 、bin/beeline 区别 3 bin/hive 客户端 hive-site.xml 配置远程 MateStore 地址 XML <?xml version"1.0" encoding"UTF-8" standalone"no"?> <?xml-stylesheet type"text/xsl" hre…...
简约大气视频制作模板PR剪辑素材PR项目工程文件
Premiere Pro模板,简约大气视频剪辑素材PR项目工程文件(包含手机竖屏分辨率),包含24个媒体占位符和9个文本占位符。可以编辑和自定义文本占位符和媒体占位符。用来展示照片视频制作。包含视频教程。 来自PR模板网:http…...
Guarded Suspension(担保挂起)设计模式
当线程访问某个对象时,发现条件不满足,暂时挂起等待条件满足时再次访问。Guarded Suspension模式是一个非常基础的模式,主要关注(临界值)不满足时将操作的线程正确挂起,以防止出现数据不一致或者操作超过临…...
禾匠榜店商城系统 RCE漏洞复现
0x01 产品简介 禾匠榜店商城系统是浙江禾匠信息科技有限公司的一套基于PHP和MySQL的商城系统。 0x02 漏洞概述 禾匠榜店商城系统的api/testOrderSubmit模块下的preview方法存在命令执行漏洞,攻击者可以向服务器写入木马文件,直接获取服务器权限 0x03 漏洞概述 FOFA:bod…...
Python移动未标注的图片数据集
Python移动未标注的图片数据集 前言前提条件相关介绍实验环境Python移动未标注的图片数据集情况一:有图,无标注文件代码实现输出结果 情况二:有图,有标注文件,但标注信息为空代码实现输出结果 情况一与情况二同时都考虑…...
判断css文字发生了截断,增加悬浮提示
示例: 固定显示宽度,溢出显示...,利用了css的属性,想要实现成下面这样: 针对溢出的文字,hover显示全部。 提示很好加,使用tooltip组件就行了,难点是如何判断是否发生了文字溢出。…...
day33-37-SpringBootV12(整合Spring,SpringMVC,Mybatis,日志,api测试等框架)
ssm spring --> applicationContext.xml配置文件 springmvc --> springmvc.xml配置文件 mybatis —> mybatis-config.xml配置文件 —> springboot优化了之前的框架配置,思想是约定大于配置 一、引言 1.1 初始化配置 为了使用SSM框架去开发,准备SSM…...
如何处理好面试中的“压力测试”?
作为一名求职者,在面试时有时遇到的是压力测试,有时则遇到的是一些无良企业单位,究竟如何把握忍耐的限度,才合格当一个能经受压力的员工,才能避免对无良单位的一味隐忍! 压力面试是指有意制造紧张,以了解求…...
大数据----31.hbase安装启动
二.Hbase安装 先前安装: Zookeeper 正常部署 首先保证 Zookeeper 集群的正常部署,并启动之。 三台机器都执行:zkServer.sh startHadoop 正常部署 Hadoop 集群的正常部署并启动。 主节点上进行 :start-all.sh 1.HBase 的获取 一定…...
ChatGPT Plus重新开启订阅
12月14日凌晨,OpenAI首席执行官Sam Altman在社交平台宣布,终于找到了更多的GPU算力,重新开启订阅ChatGPT Plus。 上个月15日,OpenAI就因为算力不足,以及用户激增等原因暂停了ChatGPT Plus订阅。 Sam表示,在…...
C#科学绘图之scottPlot绘制多个图像
文章目录 示例移除图像图例信号图 scott系列:绘图初步 示例 从名字就能看出,ScottPlot的绘图函数AddScatter的作用是为图窗添加数据点,换言之,每调用一次AddScatter,就可以在图窗中添加一组图像。下面添加两个按钮&a…...
二百一十五、Flume——Flume拓扑结构之复制和多路复用的开发案例(亲测,附截图)
一、目的 对于Flume的复制和多路复用拓扑结构,进行一个小的开发测试 二、复制和多路复用拓扑结构 (一)结构含义 Flume 支持将事件流向一个或者多个目的地。 (二)结构特征 这种模式可以将相同数据复制到多个channe…...
Leetcode—2962.统计最大元素出现至少 K 次的子数组【中等】
2023每日刷题(五十六) Leetcode—2962.统计最大元素出现至少 K 次的子数组 滑动窗口算法思想 参考的灵神思路 实现代码 class Solution { public:long long countSubarrays(vector<int>& nums, int k) {int n nums.size();long long ans…...
MapReduce模拟统计每日车流量-解决方案
MapReduce模拟统计每日车流量-解决方案 1.Map阶段:将原始数据分割成若干个小块,每个小块由一个Map任务处理。Map任务将小块中的每个数据项映射成为一个键值对,其中键为时间戳,值为车流量。2.Shuffle阶段:将Map任务输出…...
【深度学习】强化学习(二)马尔可夫决策过程
文章目录 一、强化学习问题1、交互的对象2、强化学习的基本要素3、策略(Policy)4、马尔可夫决策过程1. 基本元素2. 交互过程的表示3. 马尔可夫过程(Markov Process)4. 马尔可夫决策过程(MDP)5. 轨迹的概率计…...
Vue.js 使用基础知识
Vue.js 是一款用于构建用户界面的渐进式框架,它专注于视图层。Vue.js 不同于传统的 JavaScript 框架,它采用了组件化的开发方式,使得开发者可以更加高效和灵活地构建交互式的 Web 应用程序。 目录 什么是 Vue.js安装 Vue.jsVue 实例模板语法插…...
Linux---计划任务
本章主要介绍如何创建计划任务 使用 at 创建计划任务使用 crontab 创建计划任务 有时需要在某个指定的时间执行一个操作,此时就要使用计划任务了。计划任务有两种: 一个是at计划任务,另一个是 crontab计划任务。 下面我们分别来看这两种计划…...
.NET微信网页开发之通过UnionID机制解决多应用用户帐号统一问题
背景 随着公司微信相关业务场景的不断拓展,从最初的一个微信移动应用、然后发展成微信公众号应用、然后又有了微信小程序应用。但是随着应用的拓展,如何保证相同用户的微信用户在不同应用中登录的同一个账号呢?今天的主题就来了.NET微信网页…...
【docker】docker入门与安装
Docker 一、入门 Docker的主要目标是:Build, Ship and Run Any App, Anywhere,也就是通过对应用组件的封装、分发、部署、运行等生命周期的管理,使用户的APP及其运行环境能做到一次镜像,处处运行。 Docker运行速度快的原因 Docker有比虚拟…...
视觉学习笔记12——百度飞浆框架的PaddleOCR 安装、标注、训练以及测试
系列文章目录 虚拟环境部署 参考博客1 参考博客2 参考博客3 参考博客4 文章目录 系列文章目录一、简单介绍1.OCR介绍2.PaddleOCR介绍 二、安装1.anaconda基础环境1)anaconda的基本操作2)搭建飞浆的基础环境 2.安装paddlepaddle-gpu版本1)安装…...
深入分析ClassLocader工作机制
文章目录 一、ClassLoader简介1. 概念2. ClassLoader类结构分析 二、ClassLoader的双亲委派机制三、Class文件的加载流程1. 简介2. 加载字节码到内存3. 验证与解析4. 初始化Class对象 四、常见加载类错误分析1. ClassNotFoundException2. NoClassDefFoundError3. UnsatisfiledL…...
搭建动态网站/百度联盟个人怎么接广告
public class ArrayDemo2 {public static void main(String[] args) {//定义一个数组存放元素int[] arr3 {10, 20, 30, 40, 50, 60, 70, 80, 90};//arr3.length/2 需要互换的次数for (int i 0; i < arr3.length / 2; i) {//进行数组互换int sum arr3[i];arr3[i] arr3[ar…...
建网站和建小程序多少钱/谷歌seo外链平台
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼昂达v975w双系统(win10x86安卓5.1R1)安装教程微软也确实令人失望,在win10下,app还是这样子,所以楼主萌生了在不刷v975i的bios状况刷安卓,而是在win下的bios装上x86安卓玩耍安卓的海量…...
淄博网站建设.com/网站建设是什么工作
http://acm.hdu.edu.cn/showproblem.php?pid4291 题意:... 思路: 首先暴力求出最外层模100000007的循环节MOD2,然后求出模MOD2的循环节MOD1.求出循环节后用类似与斐波那契数列举证优化的方法求解将时间复杂度由O(N)降到O(logN*2^3); ps:注意…...
物流公司做网站/永久免费个人网站申请注册
ajaxSuccess()方法只要AJAX请求成功完成,ajaxSuccess(callback)方法就会附加一个要执行的函数。这是一个Ajax事件。这是此方法使用的所有参数的描述-callback-要执行的功能。事件对象,XMLHttpRequest和用于该请求的设置将作为参数传递给回调。假设我们在…...
wordpress邮件功能/网页设计与制作用什么软件
jsx 语法,直接可以在js中使用html标签。 还可以通过花括号的形式,在html标签中,写js表达式。 <div>{ 1 2 }hello,world! </div> 事件是大写 <button onClick{this.handleBtnClick.bind(this)}>add</button&…...
天锐绿盾如何做网站限制/dw网页制作教程
1,其实有时候一直在找借口不去思考这个问题,总是以赶项目为由,没有很认真的思考这个问题,为什么我们要在项目中使用MVP模式,自己也用MVP也已经做了两个项目,而且在网上也看了不少的文章,但是感觉…...