JVM实战—2.JVM内存设置与对象分配流转
大纲
1.JVM内存划分的原理细节
2.对象在JVM内存中如何分配如何流转
3.部署线上系统时如何设置JVM内存大小
4.如何设置JVM堆内存大小
5.如何设置JVM栈内存与永久代大小
6.问题汇总
1.JVM内存划分的原理细节
(1)背景引入
(2)大部分对象的存活周期都是极短的
(3)少数对象是长期存活的
(4)JVM分代模型:新生代和老年代
(5)为什么要分成新生代和老年代
(6)什么是永久代
(1)背景引入
接下来介绍JVM内存的分代模型:新生代、老年代、永久代。现在已知代码里创建的对象,都会进入到Java堆内存中。如下所示,main()方法会周期性执行loadReplicasFromDisk()方法来加载副本数据。
public class Kafka {public static void main(String[] args) {while (true) {loadReplicasFromDisk();Thread.sleep(1000);}}private static void loadReplicasFromDisk() {ReplicaManager replicaManager = new ReplicaManager();replicaManager.load();}
}
一.首先执行main()方法,就会把其栈帧压入main线程的Java虚拟机栈,如下图示:
二.然后main线程每次在while循环里调用loadReplicasFromDisk()方法,就会把loadReplicasFromDisk()方法的栈帧压入自己的Java虚拟机栈,如下图示:
三.接着在执行loadReplicasFromDisk()方法时,就会在Java堆内存里创建一个ReplicaManager对象实例。然后loadReplicasFromDisk()方法的栈帧会有一个replicaManager局部变量,replicaManager局部变量会引用Java堆内存的ReplicaManager对象实例,如下图示:
四.接着就会执行ReplicaManager对象实例的load()方法。
(2)大部分对象的存活周期都是极短的
上面代码中的ReplicaManager对象,就是一个短暂存活的对象。在loadReplicasFromDisk()方法中创建这个ReplicaManager对象,然后执行ReplicaManager对象的load()方法。执行完毕后,loadReplicasFromDisk()方法就会结束。一旦方法执行结束,那么loadReplicasFromDisk()方法的栈帧就会出栈。如下图示:
然后一旦这个ReplicaManager对象没被引用了,就会被JVM的垃圾回收线程给回收掉,释放内存空间。如下图示:
继续回到main()方法的while循环里。下次循环执行loadReplicasFromDisk()方法时,又重复一遍上面的过程,把loadReplicasFromDisk()方法的栈帧压入Java虚拟机栈,然后构造一个ReplicaManager实例对象放在Java堆里。一旦执行完ReplicaManager对象的load()方法后,loadReplicasFromDisk()方法又会结束,再次出栈。然后垃圾回收释放掉Java堆内存里的ReplicaManager对象。
所以上面代码的ReplicaManager对象,就是一个存活周期很短的对象。每次执行loadReplicasFromDisk()方法时,该对象就会被创建出来。然后执行对象的load()方法,接着可能1毫秒后,就要被垃圾回收掉。
所以从这段代码就可以明显看出来:代码里大部分创建的对象,其实存活周期都是很短的。
(3)少数对象是长期存活的
接下来看下面代码,用另外的方式来实现同样的功能,也就是给Kafka这个类定义一个静态变量replicaManager。
public class Kafka {private static ReplicaManager replicaManager = new ReplicaManager();public static void main(String[] args) {while (true) {loadReplicasFromDisk();Thread.sleep(1000);}}private static void loadReplicasFromDisk() {replicaManager.load();}
}
这个Kafka类位于JVM的方法区,它有一个静态变量replicaManager,replicaManager静态变量会引用一个在Java堆内存创建的ReplicaManager对象。如下图示:
main()方法会通过while循环不停调用ReplicaManager对象的load()方法,这时这个ReplicaManager实例对象是会一直被Kafka的静态变量引用的。然后它会一直驻留在Java堆内存里,不会被垃圾回收掉。因为这个实例对象它需要长期被使用,周期性的被调用load()方法,所以这个ReplicaManager实例对象就成为了一个长时间存在的对象。
类似这种被类的静态变量长期引用的对象,就会长期留在Java堆内存里。这种对象就是生命周期很长的对象,它不会轻易被垃圾回收。
(4)JVM分代模型:新生代和老年代
可见,采用不同的方式来创建和使用对象,对象的生命周期是不同的。所以JVM将Java堆内存划分为两个区域:一个是新生代,一个是老年代。其中新生代,就是把创建和使用完之后,马上就要回收的对象放在里面。然后老年代,就是把创建后需要一直长期存在的对象放在里面,如下图示:
下面来看如下代码:
public class Kafka {private static ReplicaManager fetcher = new ReplicaFetcher();public static void main(String[] args) {loadReplicasFromDisk();while(true) {fetchReplicasFromRemote();Thread.sleep(1000);}}private static void loadReplicasFromDisk() {ReplicaManager replicaManager = new ReplicaManager();replicaManager.load();}private static void fetchReplicasFromRemote() {fetcher.fetch(); }
}
这段代码的意思是:
一.类的静态变量fetcher引用了ReplicaFetcher对象,要长期驻留内存
所以ReplicaFetcher对象会在新生代停留一会儿,但最终会进入老年代,如下图示:
二.进入main()方法后,会先调用loadReplicasFromDisk()方法
该方法意思是系统启动就从磁盘加载一次数据,这个方法的栈帧会入栈。然后在该方法里会创建一个ReplicaManager对象,这个对象用完就回收。所以ReplicaManager对象会放在新生代里,由栈帧里的局部变量来引用。如下图示:
三.一旦loadReplicasFromDisk()方法执行完毕,其栈帧就会出栈
对应的新生代里的ReplicaManager对象也会被回收掉,如下图示:
四.接着会执行一段while循环代码
即周期性调用ReplicaFetcher的fetch()方法,从远程加载副本数据。由于ReplicaFetcher这个对象被Kafka类的静态变量fetcher给引用了,所以它会长期存在于老年代里的,持续被使用。
(5)为什么要分成新生代和老年代
之所示需要这么区分,是因为这和垃圾回收有关。新生代里的对象,创建后很快就会被回收,所以需要一种垃圾回收算法。老年代里的对象,需要长期存在,所以需要另一种垃圾回收算法。因此才需要分成两个区域来放不同的对象。
(6)什么是永久代
JVM里的永久代其实就是方法区,方法区就是所谓的永久代,可以认为永久代就是放一些类的信息。
(7)问题
每个线程都有Java虚拟机栈,里面也有方法的局部变量等数据,那么这个Java虚拟机栈里的局部变量需要进行垃圾回收吗?
JVM垃圾回收针对的是新生代、老年代、方法区,不针对方法的栈帧。方法一旦执行完毕,栈帧出栈,里面的局部变量就从内存里清理掉了。
2.对象在JVM内存中如何分配如何流转
(1)对象分配的基础知识总结
(2)大部分对象都优先在新生代分配内存
(3)什么情况下会触发新生代的垃圾回收
(4)长期存活的对象会躲过多次垃圾回收
(5)老年代会垃圾回收吗
(6)关于新生代和老年代的对象分配总结
(1)对象分配的基础知识总结
Java代码里创建出来的对象,一般就是两种:第一种是短期存活的,迅速使用完后就会被垃圾回收;第二种是长期存活的,一直存在Java堆内存里。
第一种短期存活的对象,会在Java堆内存的新生代里。第二种长期存活的对象,会在Java堆内存的老年代里。
那么对象什么时候进入新生代?
什么情况下会进入老年代?
(2)大部分对象都优先在新生代分配内存
大部分对象,都会优先在新生代分配内存。
public class Kafka {private static ReplicaManager fetcher = new ReplicaFetcher();public static void main(String[] args) {loadReplicasFromDisk();while(true) {fetchReplicasFromRemote();Thread.sleep(1000);}}private static void loadReplicasFromDisk() {ReplicaManager replicaManager = new ReplicaManager();replicaManager.load();}private static void fetchReplicasFromRemote() {fetcher.fetch(); }
}
上述代码中:类的静态变量fetcher引用的ReplicaFetcher对象,会长期存活在内存里。但是该对象刚开始由new ReplicaFetcher()实例化时,是在新生代里的。loadReplicasFromDisk()中创建的ReplicaManager对象,也在新生代中。如下:
(3)什么情况下会触发新生代的垃圾回收
一旦loadReplicasFromDisk()方法执行完毕后,这个方法的栈帧出栈,这个时候便没有任何局部变量引用ReplicaManager实例对象了。如下图示:
此时是否会对没被使用的ReplicaManager实例对象进行垃圾回收?此时是不会马上对失去引用的Java堆实例对象进行垃圾回收的,因为垃圾回收也有触发条件。
其中一个比较常见的触发场景是:假设代码创建了很多对象,然后导致Java堆内存里堆积了大量对象。然后这些对象之前都会被各方法中的局部变量引用,但现在没被引用了。如下图示:
这时如果新生代预先分配的内存空间,几乎都被全部对象给占满了,而代码还在继续运行。那么准备在新生代里分配一个对象时,发现新生代里内存空间不够,就会触发一次新生代内存空间的垃圾回收。新生代内存空间的垃圾回收,也称为Minor GC,有时也叫Young GC。Young GC会尝试把新生代里那些没被引用的垃圾对象,都给回收掉。
比如上图的ReplicaManager实例对象,就是没有被引用的垃圾对象。即会把ReplicaManager对象回收,然后存放一个新的对象到新生代。包括上图中的大量实例对象,其实也没被引用。在这个新生代垃圾回收的过程中,就会把这些垃圾对象也都回收掉。
平时代码中创建的大部分对象,都是这种使用后马上就可回收的对象。当已经在新生代里分配了大量对象,且这些对象使用完后也没被引用了。而新生代又差不多满了,要继续分配新对象时发现新生代内存空间不足,就会触发一次垃圾回收,把所有垃圾对象给回收掉,腾出大量内存空间。如下图示:
(4)长期存活的对象会躲过多次垃圾回收
上图中的ReplicaFetcher实例对象,是一个被Kafka类的静态变量fetcher引用的、长期存活的对象。
所以虽然新生代可能随着系统的运行,不停地创建对象,然后让新生代变满,接着进行垃圾回收,大量对象又会被回收掉。但是这个ReplicaFetcher对象,它会一直存活在新生代里。因为它一直被Kafka类的静态变量引用着,所以它不会被回收。
因此JVM有规定:如果一个实例对象在新生代中成功躲过15次垃圾回收,就进入老年代。对象的年龄就是:每进行一次垃圾回收而没被回收掉,对象年龄就加1。
所以如果ReplicaFetcher对象在新生代中成功躲过15多次垃圾回收,那么ReplicaFetcher对象就会被认为会长期存活在内存里,然后就会被转移到老年代中。老年代会存放一些年龄很大的对象,如下图示:
(5)老年代会垃圾回收吗
老年代里的那些对象会被垃圾回收吗?答案是肯定的。因为老年代的对象也有可能随着代码的运行不再被引用,也要垃圾回收。当越来越多对象进入老年代,一旦老年代满了,也要对老年代垃圾回收。
(6)关于新生代和老年代的对象分配总结
目前已介绍如下机制:
一.对象优先分配在新生代
二.新生代对象满了就会触发垃圾回收把没有被引用的垃圾对象清理掉
三.如果对象躲过了十五次垃圾回收就会进入老年代
四.如果老年代满了也会触发垃圾回收把没有被引用的垃圾对象清理掉
当然还有其他机制,比如:
一.新生代垃圾回收后因为存活对象太多导致大量对象直接进入老年代
二.大对象不经过新生代直接进入老年代
三.对象动态年龄判断机制
四.空间分配担保机制
3.部署线上系统时如何设置JVM内存大小
(1)对象在JVM内存中的分配流转总结
(2)与JVM内存相关的几个核心参数图解
(3)如何在启动系统的时候设置JVM参数
(4)每日百万交易的支付系统JVM参数优化案例
(1)对象在JVM内存中的分配流转总结
代码里创建的对象,都是优先在新生代分配的。然后随着一些方法执行完毕,大部分对象就没有被引用而成为垃圾对象。如下图示:
随着代码持续运行,新生代里的对象会越来越多。而且新生代里面大部分对象都是生命周期短的对象,很快就不会被引用。因此可以认为新生代里的大部分对象都会是一些垃圾对象。
然后代码继续运行,肯定会创建新的对象,需要分配在新生代里。所以一旦新生代里内存不够了,就会触发一次Young GC。此时会把新生代里没被引用的垃圾对象都给回收掉,腾出内存空间。如下图示:
对于那种长周期存活的对象,它会在新生代里持续躲过多次垃圾回收。每躲过一次垃圾回收,年龄会增长1岁。然后当它成为15岁的"老年对象"时,就会被转移到老年代里。如下图示:
所以核心的问题就是:短生存周期的对象和长生存周期的对象分别是什么,它们是如何在新生代里分配的,新生代什么时候触发YGC,长生存周期的对象如何转移到老年代里。
(2)与JVM内存相关的几个核心参数图解
接下来介绍JVM的参数如何设置。在JVM内存分配中,有几个参数是比较核心的,如下所示:
一.-Xms:Java堆内存的大小
二.-Xmx:Java堆内存的最大大小
三.-Xmn:Java堆内存中的新生代大小
四.-XX:PermSize:永久代大小
五.-XX:MaxPermSize:永久代最大大小
六.-Xss:每个线程的栈内存大小
下面对上述参数进行一一说明。
-Xms和-Xmx:用于设置Java堆内存刚开始大小,以及允许的最大大小。对于这对参数,通常都会设置为完全一样的大小。这两个参数是用来限定Java堆内存的总大小的,如下图示:
-Xmn:这个参数用来设置Java堆内存中的新生代的大小,扣除新生代大小之后的剩余内存就是老年代的内存大小,如下图示:
-XX:PermSize和-XX:MaxPermSize:设置永久代大小和最大永久代大小。JDK1.8后被替换为-XX:MetaspaceSize和-XX:MaxMetaspaceSize,如下图示:
-Xss:这个参数限定每个线程的栈内存大小。每个线程都有一个自己的虚拟机栈,然后每次执行一个方法,就会将方法的栈帧压入线程的栈里。方法执行完毕,那么栈帧就会从线程的栈里出栈。如下图示:
(3)如何在启动系统的时候设置JVM参数
比如以"java -jar"方式启动一个jar包里的系统时,可采用下面格式:
$ java -Xms512M -Xmx512M -Xmn256M -Xss1M
-XX:PermSize=128M -XX:MaxPermSize=128M -jar App.jar
(4)每日百万交易的支付系统JVM优化案例
接下来分析一个支付系统的核心业务流程,然后结合JVM相关知识,来一步步探究JVM内存相关的核心参数,在上线一个生产系统时,应如何针对预估的并发压力,给出一个未经调优的比较合理的初始值。
另外分析各种参数在设置时有哪些考虑的点,Java堆内存到底要多大?新生代和老年代的内存分别要多大?永久代和虚拟机栈分别要多大?
其实JVM参数到底该如何设置,一定是根据不同业务场景来调整的。不会有一个通用的配置和模板,一切都要从案例出发,结合场景来分析。
(5)问题
Tomcat、Spring Boot部署启动系统时,JVM参数如何设置?
答:Spring Boot是在启动时可以加上JVM参数的,Tomcat则是在bin目录下的catalina.sh中加入JVM参数的。
4.如何设置JVM堆内存大小
(1)支付系统核心业务流程
(2)每日百万交易的支付系统的压力在哪里
(3)支付系统每秒钟需要处理多少笔支付订单
(4)每个支付订单处理要耗时多久
(5)每个支付订单大概需要多大的内存空间
(6)每秒发起的支付请求对内存的占用
(7)让支付系统运行起来进行分析
(8)对完整的支付系统内存占用需要进行预估
(9)支付系统的JVM堆内存应该怎么设置
(10)总结
(1)支付系统核心业务流程
支付系统的核心业务流程如下图示:
首先用户在商城系统提交一个支付订单的请求,接着商城系统把这个请求提交给支付系统。支付系统就会生成一个支付订单,此时订单状态可能是"待支付"的状态。然后支付系统指引用户跳转到付款页面,选择一个付款方式。然后用户进行支付,支付系统把实际支付请求转交给第三方支付渠道。第三方支付渠道可能是微信或支付宝,由它们处理支付请求转移资金。如果微信或者支付宝处理完支付后,就会返回支付结果给支付系统,支付系统可以更新自己本地的支付订单的状态变成"已完成"。
当然,其实一个完整的支付系统还包含很多内容。比如还要负责对账以及跟合作商户之间的资金清算,支付系统要包含渠道管理、支付交易、对账管理、结算管理等各种功能,但是这里只关注最核心的支付流程即可。
(2)每日百万交易的支付系统的压力在哪里
一个每日百万交易的支付系统的压力到底集中在哪里?比如上面的那个核心支付流程,假设每日要发生百万次交易。
一般达到百万交易,要不然是国内最大的互联网公司,要不就是一个通用型第三方支付平台,对接各种APP的支付交易。
其实通过上图都能明显看到,上述业务流程中,最核心的环节就是在用户发起支付请求时,会生成一个支付订单。这个支付订单需要记录清楚:是谁发起支付、对哪个商品支付等信息。如果每日百万交易,那么在JVM的角度看:就是每天会在JVM中创建百万个支付订单对象。如下图示:
所以这个每日百万交易的支付系统,它的压力有很多方面:如高并发访问、高性能处理、大量的支付订单数据需要存储等技术难点。但抛开这些系统架构层面的东西,单单在JVM层面支付系统最大的压力:就是每天JVM内存里会频繁创建和销毁100万个支付订单对象。
所以这里就牵扯到一些核心问题:
一.支付系统需要部署多少台机器?
二.每台机器需要多大的内存空间?
三.每台机器启动的JVM需要分配多大堆内存空间?
四.设置JVM多大内存才能创建这么多对象而不会导致内存不够而崩溃?
(3)支付系统每秒钟需要处理多少笔支付订单
要解决线上系统最核心的一个参数,也就是合理设置JVM堆内存大小。首先第一个要计算的,就是每秒钟订单系统要处理多少笔支付订单。
假设每天100万个支付订单。那么一般用户交易行为都会发生在每天的高峰期,比如中午或者晚上。假设每天高峰期大概是3个小时,将100万平均分配到3个小时里。那么大概每秒100笔订单,所以就以每秒100笔订单来进行计算。假设支付系统部署3台机器,则每台机器实际上每秒大概处理30笔订单。如下图示,这个图可以反映出支付系统每秒钟的订单处理压力。
(4)每个支付订单处理要耗时多久
下一个问题,必须要弄清楚的是,每个支付订单大概要处理多长时间?
如果用户发起一次支付请求:那么支付需要在JVM中创建一个支付订单对象,填充进数据。然后把这个支付订单写入数据库,以及可能处理一些其他事情。
假设一次支付请求的处理包含一个支付订单的创建,大概需要1秒时间,那么每台机器一秒钟会接收到30笔支付订单的请求。然后会在JVM新生代里创建30个支付订单的对象,进行写库等处理。接着1秒后这30个支付订单就处理完毕,此时栈帧中对这些支付订单对象的引用就被回收了。然后这些订单对象在JVM的新生代里就是没被引用的垃圾对象了。接着下一秒会继续来处理30个支付订单,重复这个步骤。
(5)每个支付订单大概需要多大的内存空间
接下来计算一下,每个支付订单对象大概需要多大的内存空间?
可以直接根据支付订单类中的实例变量的类型来计算。比如支付订单类如下所示:一个Integer类型的变量数据4字节,一个Long类型的变量数据是8字节,还有别的类型的变量数据占据多少字节等,这样就可以计算出每个支付订单对象大致占多少字节了。
public class PayOrder {private Integer userId;private Long orderTime;private Integer orderId;
}
一般像支付订单这种核心类,可以按20个实例变量来计算。然后大概一个订单对象也就一两百字节,可以算它大一点。比如一个支付订单对象占据500字节的内存空间,也不到1K。
(6)每秒发起的支付请求对内存的占用
假设有3台机器,每秒钟处理30笔支付订单的请求。那么在这1秒内,肯定有方法栈帧里的局部变量在引用这些支付订单对象。那么30个支付订单,大概占据的内存空间是30 * 500字节 = 15000字节。大概15K左右,其实是非常小的,如下图示:
(7)让支付系统运行起来进行分析
现在已经把整个系统运行的关键环节的数据都分析清楚了:每秒30个支付请求,每秒创建30个支付对象,每秒占15K的内存空间。接着1秒过后,这30个对象就没有被引用了,成为新生代里的垃圾。下一秒请求过来,系统继续创建30个支付对象放入新生代里,然后新生代里的对象就会持续累积增加。
直到有一刻,发现可能新生代里都有几十万个对象。此时占据了几百M的内存空间,可能新生代空间就快满了。然后就会触发Young GC,把新生代里的垃圾对象都给回收掉。从而腾出内存空间,可以继续在内存里分配新的对象。
这就是该支付系统在创建订单环节的JVM运行模型。
(8)对完整的支付系统内存占用需要进行预估
前面的分析都是基于一个核心业务流程中的一个支付订单对象来分析的,但那其实那只是整个支付系统的一个小部分而已。
真实的支付系统在线上运行时,肯定会每秒创建大量其他对象。所以可以结合这个访问压力以及核心对象的内存占据,大致估算一下整个支付系统每秒钟大致会占据多少内存空间。
如果要估算的话,其实可以把上述的计算结果扩大10到20倍。即每秒除了在内存里创建支付订单对象,还会创建其他数十种对象。
假设一台机器每秒创建100个500字节的支付订单对象,扩大20倍后,那么每秒创建出的被栈内存的局部变量引用的对象,大概占1M内存空间。然后下一秒对新请求,继续创建1M对象放入新生代,一秒后又变成垃圾。循环多次后,新生代里垃圾太多,就会触发Young GC回收掉这些垃圾。这就是一个完整的支付系统在JVM层面的内存使用模型。
(9)支付系统的JVM堆内存应该怎么设置
一般来说这种线上业务系统的机器配置是2核4G或者是4核8G。
一.2核4G的机器来部署则还是有点紧凑的
虽然机器有4G内存,但机器本身也用一些内存,最后JVM最多2G内存。然后这2G还得分配给方法区、栈内存、堆内存几块区域,那么堆内存可能最多就是有1G多的内存空间。然后堆内存还分为新生代和老年代,老年代需要放置系统的一些长期存活的对象,也要占几百M的内存空间,那么这样下来新生代可能只剩下几百M的内存了。
但上述仅仅是针对一个支付订单对象来分析的,实际上如果扩大20倍来对完整支付系统的预估后:一台机器每秒处理100个订单,每秒就会占据1M左右的内存空间。那么此时如果新生代就几百M的内存空间:就会导致运行几百秒后,新生代内存空间就满了,此时就会触发YGC。如果频繁触发YGC,还是会影响线上系统的性能稳定性的。
二.可以考虑采用4核8G的机器来部署支付系统
此时JVM进程至少可以给4G以上内存,新生代至少可分配2G内存空间。这样就可以做到即便新生代每秒消耗1M左右的内存,也要将近半小时到1小时才会让新生代触发YGC,大大降低了GC频率。
举个例子:
机器采用4核8G,-Xms和-Xmx设置为3G,给整个堆内存3G内存空间。-Xmn设置为2G,给新生代2G内存空间。而且假设业务量如果更大,则可以考虑不只部署3台机器,可以考虑横向扩展部署5台机器或者10台机器,这样每台机器处理的请求更少对JVM的压力更小。
(10)总结
从一个日百万交易的支付系统出发,部署3台机器的场景下。每秒钟每台机器需要处理多少笔订单,每笔订单要耗时多久处理。每笔订单的核心对象每秒钟会对JVM占据多大内存空间,根据单个核心对象横向扩展预估整个系统每秒需要占据多大内存空间。接着根据上述数据模型推算出:在不同的机器配置之下,新生代大致会有多大的内存空间。然后在不同的新生代大小下,多久会触发一次Young GC。
为了避免频繁的GC,那么应该选用什么样的机器配置。部署多少台机器,设置JVM堆内存、新生代分别多大的内存空间。
根据这套配置,就可以推算出来整个系统的运行模型了。每秒钟创建多少对象在新生代,然后1秒之后成为垃圾。大概系统运行多久,新生代会触发一次GC,频率有多高。
5.如何设置JVM栈内存与永久代大小
(1)如何设置JVM堆内存总结
(2)不合理设置内存的反面示例
(3)大促期间瞬时访问量增加十倍
(4)少数请求要几十秒处理导致老年代内存占用变大
(5)老年代对象越来越多导致频繁垃圾回收
(6)不合理设置内存的反面示例总结
(7)如何合理设置永久代大小
(8)如何合理设置栈内存大小
(1)如何设置JVM堆内存总结
如果准备上线一个新系统,如何根据这个系统预估的业务量和访问量,去推算系统每秒的并发量。然后推算每秒的请求对内存空间的占用,从而推算出整个系统运行期间的JVM内存运转模型。然后基于推算出的JVM内存运转模型,在上线前选择合理的机器配置,需要多大内存的机器才能让JVM堆内存空间拥有一个合理的大小。
这是一项非常基础的技能,因为对于某些业务新系统,可能上线就会面临很大的访问压力。所以要合理预估内存压力,选择合适的机器配置,设置合理的内存大小。
每个合格的工程师,都应该在上线系统时,对系统压力做出预估。然后对JVM内存、磁盘空间大小、网络带宽、数据库压力做出预估,最后在各方面都给出合理的配置。
(2)不合理设置内存的反面示例
下面介绍一个不合理设置内存大小导致问题的反面案例。假设支付系统因为没有经过合理的内存预估,所以选用了1台2核4G的虚拟机来部署线上系统,而且只用一台机器。然后线上JVM给的堆内存大小仅仅只有1G,扣除老年代后,新生代只有几百M的内存空间。如下图示:
接着业务压力还是每天100万交易,高峰期每秒大概100笔支付交易。那么对应的每秒就有100个支付订单对象有创建出来,每个支付订单对象占据500字节左右,总共是50K。然后假设处理一笔交易总共需要1秒,那么这100个对象在新生代中存在1秒的期间会被栈帧引用,无法被回收。此外进行全局预估时,会从支付订单对象横向扩展到系统其他对象。所以起码要把内存占用扩大10到20倍,比如扩大20倍。
因此只用一台2核4G机器来处理每秒100个创建支付订单的请求,在1秒内总共会创建出大概1M对象,这些对象在这1秒内是无法被回收的。
(3)大促期间瞬时访问量增加十倍
其实按照估算出的内存压力,在系统正常情况下,还不算什么大问题。因为每秒新增1M对象,几百秒过后新生代快满了。自然就会触发Young GC,回收掉里面99%的垃圾对象。如果新生代内存有500M,最多会发现系统每隔几分钟略微卡顿一下。因为这个时候在进行垃圾回收,会影响系统性能。
但是现在假设电商系统搞大促活动,很可能会导致压力瞬间增大10倍。此时可能会发现,支付系统每秒要处理的不是100笔,而是上千笔订单。
这时系统压力本身就会很大,不光是JVM堆内存,尤其是线程资源、CPU资源,都会几乎打满,JVM堆内存就更是岌岌可危了。
(4)少数请求要几十秒处理导致老年代内存占用变大
现在假设一台机器每秒需要处理1000笔交易,那么支付系统每秒对内存的占用就增加到10M以上。甚至再大胆点,预估支付系统每秒对内存占用达到几十M,甚至上百M。因为毕竟大促时流量激增,就一切围绕这来预估。而且最可怕的是,可能每秒过来的1000笔交易,不再是1秒就能处理完。因压力骤增导致性能下降,可能出现处理完一个请求要几秒甚至几十秒。此时如下图示,假设新生代里已经积压了很多的数据,都快满了。
此时内存里有几十M的对象都被引用着,因为少数请求突然处理特别慢。为什么会处理特别慢?因为压力太大,导致系统性能太差了。如下图示:
这时如果要在新生代里分配对象,那么就会导致一次YGC去回收新生代。但可能回收大量对象后,那少数几十M对象还在,因为少数请求特别慢。然后很快新生代继续被填满,再次触发YGC,然后少数几十M对象还在。此时多次YGC之后,这少数几十M对象就会被转移到老年代去。如下图示:
(5)老年代对象越来越多导致频繁垃圾回收
上述流程如果反复来多次,时不时有少数请求特别慢,这些特别慢的请求创建的对象在新生代多次没法回收就会被移到老年代。然后后续处理完,老年代里的对象就没被引用了,成为了垃圾对象。
经常重复这个流程,老年代里的垃圾对象,就会越来越多。一旦老年代的垃圾对象越来越多,那么老年代迟早会满,触发老年代的垃圾回收。而且这个老年代被占满的频率还很快,就会频繁触发老年代的垃圾回收。而老年代垃圾回收是很慢的,老年代频繁垃圾回收会极大影响系统性能。
所以如果设置内存不合理,就会导致新生代内存不充足。在遇到大促等流量暴增时,就会导致偶尔卡顿。偶尔卡顿又会让很多本在新生代的对象不停迁移到老年代,最后导致老年代要不停地进行垃圾回收。
(6)不合理设置内存的反面示例总结
如果内存设置过小,那么当遇到突发巨大流量压力、突发性能抖动时:可能会导致请求卡顿,引发很多新生代对象长期被栈引用,无法被回收。最后本应留在新生代的对象就会持续进入老年代,从而导致老年代内存被频繁占满,频繁触发老年代的垃圾回收。
可见不合理预估业务系统压力、不合理设置内存大小,会导致很大问题。
(7)如何合理设置永久代大小
永久代大小的设置没太多可以参考的规范。一般刚开始上线一个系统时可设置永久代为几百M,基本上都是够用的。因为永久代里主要存放的是类的信息,当然永久代也可能发生内存溢出。
(8)如何合理设置栈内存大小
栈内存大小设置,一般无需特别预估和设置,默认的512K到1M都够了。栈内存大小其实就是指每个线程的栈内存空间大小,一般用来存放线程执行方法期间的各种局部变量,当然栈内存也会发生内存溢出。
6.问题汇总
问题一:
既然栈帧存放了方法对应的局部变量数据,也包括方法执行的其它信息。那为何不把程序计数器记录执行的情况,也放在各个方法自己的栈帧里,而是单独列一个程序计数器去存储呢?
答:这就涉及JVM设计者的设计思想了。程序计数器针对的是代码指令的执行,Java虚拟栈针对的是方法的执行。一个是指令,一个是数据,分开设计。
问题二:
方法区的类什么时候会被回收?为什么?
答:在以下几种情况下,方法区里的类会被回收:
一.该类的所有实例对象都已从堆内存里回收
二.加载该类的ClassLoader已被回收
三.对该类的Class对象没有任何引用
满足上面三个条件就可以回收该类了。
问题三:
方法执行完后,栈帧马上被出栈,那该栈帧中的变量等数据是马上就被回收掉吗?还是需要等垃圾回收线程扫描到再回收?
答:出栈就没了。
问题四:
双亲委派模型的设计出发点是什么?
答:双亲委派模型设计的出发点很重要:对于任意一个类,都需要由加载它的类加载器和这个类本身,来一同确立其在Java虚拟机中的唯一性。每一个类加载器,都拥有一个独立的类名称空间。也就是说,判断两个类是否相等,只有在这2个类是由同一个类加载器加载的前提下才有意义。否则即使这两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载它们的类加载器不同,这两个类必定不相等。
基于双亲委派模型设计:
那么Java中基础的类,Object类重复多个的问题就不会存在了。因为经过层层传递,加载请求最终都会被启动类加载器所响应,所以加载的Object类最后也会只有一个。否则如果用户自己编写一个java.lang.Object类,并放到ClassPath中,那么就会出现很多Object类,这样应用程序将一片混乱。
问题五:
Tomcat需要破坏双亲委派模型的原因是什么?
答:原因如下:
(1)Tomcat中需要支持不同Web应用依赖同一个第三方类库的不同版本,所以Tomcat中的jar类库需要保证相互隔离。
(2)同一个第三方类库的相同版本在不同的Web应用中可以共享。
(3)Tomcat依赖的类库需要与应用依赖的类库隔离。
(4)JSP需要支持修改后不用重启Tomcat即可生效,Tomcat为了类加载隔离和类更新不用重启,定制开发了各种类加载器。
问题六:
引用Class对象的会是什么?
答:比如用反射可以获取一个对象对应的类的Class对象实例,比如Class clazz = replicaManager.getClass(),可通过replicaManager引用的对象获取ReplicaManager类的Class对象。那个clazz变量,就可以引用这个Class对象。
问题七:
Spring的对象和自定义的POJO对象会分配在哪里?
答:托管给Spring管理的对象(配置了@Configration)会长期存在老年代。自定义那些POJO对象,如果不是类对象,那么就会朝生夕灭、会被分配在新生代。Spring容器的对象,默认采用单例方式加载,这些对象会存在老年代中。但在方法内new出来的对象不会存活太长,方法结束后会在下次垃圾回收的时候被回收。
问题八:
如下代码的变量和实例对象何时会被销毁回收?
public void load() {A a = new A();
}
答:a这个变量是存放在虚拟机栈的,load()方法执行完后就会被销毁,new A()这个对象是需要等待垃圾回收线程扫描后才回收销毁。
问题九:
软引用和弱引用的回收时机?
答:内存不够才会回收软引用对象,内存足够不会回收软引用对象。弱引用不管内存空间够不够,只能撑到下次垃圾回收之前,就会被回收。
问题十:
类初始化时,类变量引用的是new出来的对象,此时变量引用的对象会被实例化到堆内存吗?
答:会实例化放到堆内存
问题十一:
是不是应该尽量设大新生代,让系统在高峰期不产生GC?
答:是的,尽量是这样。
问题十二:
类初始化的时机都有哪些?
答:类的"加载->验证->准备->解析->初始化"并不是一个连续的动作。也就是说,类即便加载了,也不一定立即会进行初始化。
类初始化的时机如下:
一.当创建某个类的新实例时(如通过new或者反射、克隆、反序列化等)
二.当调用某个类的静态方法时
三.当使用某个类或接口的静态字段时
四.调用Java API中的某些反射方法时,如类Class中的方法、java.lang.reflect中的方法
五.当初始化某个子类时
六.当虚拟机启动某个被标明为启动类的类(即包含main方法的那个类)
相关文章:
JVM实战—2.JVM内存设置与对象分配流转
大纲 1.JVM内存划分的原理细节 2.对象在JVM内存中如何分配如何流转 3.部署线上系统时如何设置JVM内存大小 4.如何设置JVM堆内存大小 5.如何设置JVM栈内存与永久代大小 6.问题汇总 1.JVM内存划分的原理细节 (1)背景引入 (2)大部分对象的存活周期都是极短的 (3)少数对象…...
无问社区-无问AI模型
无问AI模型是无问社区新上线的一款AI功能,支持文本图像的输入,在文本理解能力、推理能力、视觉能力上相较于“社区助手”有了很大的提升。 我们在预训练模型的技术上增加1.7亿token的训练数据进行强化训练使其具备更好的效果。 更好的消息是我们准备了…...
【记录】列表自动滚动轮播功能实现
目录 效果展示代码 效果展示 代码 <!-- 首页 --> <template><div class"page_body_item_body" mouseenter"stopScroll" mouseleave"scroll(false)"><ele-tableclass"eleTable":table-options"options"…...
前缀树介绍
数风流人物,还看今朝! 前缀树 Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补全和拼写检查。 前…...
Solon v3.0.5 发布!(Spring 可以退休了吗?)
Solon 框架! 新一代,面向全场景的 Java 应用开发框架。从零开始构建(非 java-ee 架构),有灵活的接口规范与开放生态。 追求: 更快、更小、更简单提倡: 克制、高效、开放、生态 有什么特点&am…...
基础组件:
基础组件: RichText 富文本组件,解析并显示HTML格式文本。 适用场景: RichText组件适用于加载与显示一段HTML字符串,且不需要对显示效果进行较多自定义的应用场景。RichText组件仅支持有限的通用属性和事件。具体见属性与事件…...
【LeetCode 面试经典150题】详细题解之滑动窗口篇
【LeetCode 面试经典150题】详细题解之滑动窗口篇 1 滑动窗口理论基础1.1 算法思想1.2 使用场景1.3 使用思路 2 209.长度最小的子数组2.1 题目分析2.2 算法步骤2.3 代码实现2.4 时间复杂度 3 3.无重复字符的最长字串3.1 题目分析3.2 算法步骤3.3 代码实现3.4 复杂度分析 4 30.串…...
【 CSS 】sass 扩展语言的安装
一、全局安装node-sass Sass世界上最成熟、稳定和强大的CSS扩展语言 | Sass中文网 https://www.npmjs.com/package/node-sass NPM镜像_NPM下载地址_NPM安装教程-阿里巴巴开源镜像站 注意:nodejs版本14以上,否则node-sass安装不成功 npm install -g mi…...
【Linux】Linux中用户信息相关的配置文件:/etc/passwd、/etc/group、/etc/shadow、/etc/sudoers
1 用户信息 1.1 /etc/passwd linux上用户的信息保存在/etc/passwd中,看文件名会以为这里保存的是用户密码,但实际上用户密码保存在另一个文件中。 /etc/passwd文件中每行保存一个用户的信息,例如: root:x:0:0:root:/root:/bin…...
electron-vite_18 设置系统音量loudness报错
loudness是一款控制系统音量输出的一款 Node.js 库;但是在electron-vite中直接使用编译的时候会报错;这个时候需要单独处理; 错误分析 error Error: spawn E:\xxx\out\main\adjust_get_current_system_volume_vista_plus.exe 查看编译后项目…...
springboot测试类里注入不成功且运行报错
目录 出错信息 原因 出错信息 写测试类的时候,一直说我注入不成功 而且我运行的时候报错了 java.lang.IllegalStateException: Unable to find a SpringBootConfiguration, you need to use ContextConfiguration or SpringBootTest(classes...) with your te…...
Harmony 网络请求
Http数据请求 axios第三方网络请求工具 1.下载ohpm 2.安装axios 3.使用axios...
Webpack在Vue CLI中的应用
webpack 作为目前最流行的项目打包工具,被广泛使用于项目的构建和开发过程中,其实说它是打包工具有点大材小用了,我个人认为它是一个集前端自动化、模块化、组件化于一体的可拓展系统,你可以根据自己的需要来进行一系列的配置和安…...
docker-componse集群部署Tdengine3.3.2.0
一、centos7.5集群部署三台机器使用docker-componse进行部署 1、三台服务器分别配置host vim /etc/hosts 192.168.13.244 td.master 192.168.13.245 td.slave1 192.168.12.70 td.slave2 2、...
4.3 数据库HAVING语句
having子句要和group by子句联合起来才能使用,不能单独去使用,接下来咱们看一下为什么要引入having子句语法呢?引入having子句也是出于无奈,因为有些条件查询,用group by子句并不能满足要求,比如说查询部门…...
如何使用React,透传各类组件能力/属性?
在23年的时候,我主要使用的框架还是Vue,当时写了一篇“如何二次封装一个Vue3组件库?”的文章,里面涉及了一些如何使用Vue透传组件能力的方法。在我24年接触React之后,我发现这种扩展组件能力的方式有一个专门的术语&am…...
C# 6.0版本的WebAPI接口部署到Linux服务器
将 C# 6.0 的 Web API 部署到 Linux 服务器涉及多个步骤,包括准备环境、构建和发布应用程序、配置 Web 服务器以及确保应用程序正常运行。以下是详细的部署指南: 1. 准备开发环境 安装 .NET SDK 确保你已经在本地开发环境中安装了 .NET SDK(…...
ArkTs组件(2)
一.下拉列表组件:Select 1.接口 Select(options: Array<SelectOption>) 参数名类型必填说明optionsArray<SelectOption>是设置下拉选项。 SelectOption对象说明 名称类型必填说明valueResourceStr是 下拉选项内容。 iconResourceStr否 下拉选项图片…...
Bash 脚本教程
注:本文为 “Bash 脚本编写” 相关文章合辑。 BASH 脚本编写教程 as good as well于 2017-08-04 22:04:28 发布 这里有个老 American 写的 BASH 脚本编写教程,非常不错,至少没接触过 BASH 的也能看懂! 建立一个脚本 Linux 中有…...
SQL创建和操纵表
本文介绍创建、更改和删除表的基本知识。 1. 创建表 SQL 不仅用于表数据操纵,而且还用来执行数据库和表的所有操作,包括表本身的创建和处理。一般有两种创建表的方法: 多数DBMS 都具有交互式创建和管理数据库表的工具;表也可以…...
1.微服务灰度发布(方案设计)
前言 微服务架构中的灰度发布(也称为金丝雀发布或渐进式发布)是一种在不影响现有用户的情况下,逐步将新版本的服务部署到生产环境的策略。通过灰度发布,你可以先将新版本的服务暴露给一小部分用户或特定的流量,观察其…...
QT笔记- QTreeView + QFileSystemModel 当前位置的保存与恢复 #选中 #保存当前索引
保存当前位置 QString currentPath model->filePath(view->currentIndex()); // 获得当前位置路径 恢复位置 view->setCurrentIndex(model->index(currentPath)); // 设置此路径所在位置为当前位置...
LeetCode - Google 校招100题 第6天 回溯法(Backtracking) (8题)
欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/144743505 LeetCode 合计最常见的 112 题: 校招100题 第1天 链表(List) (19题)校招100题 第2天 树(Tree) (21题)校招100题 第3天 动态规划(DP) (20题)...
k8s,service如何找到容器
Kubernetes之所以需要Service,一方面是因为Pod的IP不是固定的,另一方面则是因为一组Pod实例之间总会有负载均衡的需求 被selector选中的Pod,就称为Service的Endpoints,查看方式: kubectl get endpoints hostnames需要…...
计算机的错误计算(一百九十二)
摘要 用两个大模型计算 csc(0.999), 其中,0.999是以弧度为单位的角度,结果保留5位有效数字。两个大模型均给出了 Python代码与答案。但是,答案是错误的。 例1. 计算 csc(0.999), 其中,0.999是以弧度为单位的角度,结…...
金仓数据库安装-Kingbase v9-centos
在很多年前有个项目用的金仓数据库,上线稳定后就没在这个项目了,只有公司的开发环境还在维护,已经好多年没有安装过了,重温一下金仓数据库安装,体验一下最新版本,也做一个新版本的试验环境; 一、…...
深入解析 Java 中的 getDeclaredMethods() 方法:使用与原理全攻略
在 Java 的反射机制中,getDeclaredMethods() 是一个非常重要的方法,它允许我们获取类中声明的所有方法(包括公共、私有、保护以及默认访问权限的方法)。通过这个方法,我们可以动态地分析和操作类的行为,这在…...
Dockerfile的用法
Dockerfile的用法 示例 `Dockerfile`使用 `Dockerfile` 创建 Docker 镜像`Dockerfile` 指令详解其他常用指令总结Dockerfile 是一个文本文件,包含了用于创建 Docker 镜像的一系列指令。这些指令描述了镜像的基础、所安装的软件、文件的复制、环境变量的设置以及其他配置。下面…...
Gmsh有限元网格剖分(Python)---点、直线、平面的移动
Gmsh有限元网格剖分(Python)—点、直线、平面的移动和旋转 最近在学习有限元的网格剖分算法,主要还是要参考老外的开源Gmsh库进行,写一些博客记录下学习过程,方便以后回忆嘞。 Gmsh的官方英文文档可以参考:gmsh.pdf 但咋就说&a…...
AI的进阶之路:从机器学习到深度学习的演变(三)
(承接上集:AI的进阶之路:从机器学习到深度学习的演变(二)) 四、深度学习(DL):机器学习的革命性突破 深度学习(DL)作为机器学习的一个重要分支&am…...
如何通过 360 驱动大师检查自己电脑上的显卡信息
在深入探讨如何查看显卡信息之前,首先需要了解显卡的基本概念。显卡(Graphics Processing Unit, GPU),是计算机中负责处理图形输出到显示器的重要硬件。根据其集成度和性能,显卡通常被分为两类: 集成显卡&…...
数据仓库工具箱—读书笔记02(Kimball维度建模技术概述04、使用一致性维度集成)
Kimball维度建模技术概述 记录一下读《数据仓库工具箱》时的思考,摘录一些书中关于维度建模比较重要的思想与大家分享🤣🤣🤣 第二章前言部分作者提到:技术的介绍应该通过涵盖各种行业的熟悉的用例展开(赞同…...
.net framework wpf 打包免安装exe文件
1、打开所在项目csproj文件添加以下内容: <Target Name"AfterResolveReferences"><ItemGroup><EmbeddedResource Include"(ReferenceCopyLocalPaths)" Condition"%(ReferenceCopyLocalPaths.Extension) .dll"><…...
瑞吉外卖项目学习笔记(八)修改菜品信息、批量启售/停售菜品
瑞吉外卖项目学习笔记(一)准备工作、员工登录功能实现 瑞吉外卖项目学习笔记(二)Swagger、logback、表单校验和参数打印功能的实现 瑞吉外卖项目学习笔记(三)过滤器实现登录校验、添加员工、分页查询员工信息 瑞吉外卖项目学习笔记(四)TableField(fill FieldFill.INSERT)公共字…...
Oracle 日常巡检
1. 检查服务器状态 1.1. CPU使用情况 1.1.1. top top 命令是 Linux 和 Unix 系统中用于显示实时系统状态的工具,特别是对于监控 CPU 和内存的使用非常有用。 在命令行中输入 top,top 会显示一个实时更新的界面,其中包含系统的关键指标&am…...
CS 144 check7: putting it all together
Exercises 经验:两边的TCP连接建立得尽快,如果服务器端启动了,客户端没有启动就连不上。。 服务器端: 客户端: 文件收发测试: 参考: CS 144CS144 | Winter 2024, Lab 0~7 记录࿰…...
springboot数据校验报错
目录 报错信息 原因 解决 报错信息 springboot在进行数据校验的时候ConfigurationProperties注解形式下Email报错 Caused by: javax.validation.UnexpectedTypeException: HV000030: No validator could be found for constraint javax.validation.constraints.Email valid…...
mapbox基础,加载天地图矢量底图
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:mapbox 从入门到精通 文章目录 一、🍀前言1.1 ☘️mapboxgl.Map 地图对象…...
SLAM/数字图象处理基础
概念 视差:相同特征的不同深度估计的偏差 BoW,DBoW,DBoW2的区别是什么 Bag of Words (BoW)、DBoW(Dynamic Bag of Words)和DBoW2是用于图像处理和计算机视觉中的不同特征表示和匹配方法。它们之间的主要区别如下&am…...
修改vue-element-admin,如何连接我们的后端
改哪几个文件就可以连接我们后端 主要就这四个 main.js,屏蔽这个或者删除 vue-config 最后两个文件改下端口即可 这样基本就能发了,但是还要改下 改成api 然后还要修改request.js 这里改成我们返回的状态码 我讲一个东西很容易就懂了&…...
基于PLC的采摘机械手系统(论文+源码)
1系统方案设计 本次设计围绕基于PLC的采摘机械手系统进行设计, PLC即可编程控制器其是一种常见的微处理器,本次拟采用西门子是S7-200 PLC,一方面对整个设计从器件选型到I/O分配,图纸绘制等进行设计,另一方面还通过组态…...
使用ArcGIS/ArcGIS pro绘制六边形/三角形/菱形渔网图
在做一些尺度分析时,经常会涉及到对研究区构建不同尺度的渔网进行分析,渔网的形状通常为规则四边形。构建渔网的方法也很简单,使用ArcGIS/ArcGIS Pro工具箱中的【创建渔网/CreateFishnet】工具来构建。但如果想构建其他形状渔网进行相关分析&…...
【5/6 面向应用相关的优化 网卡实现及评估 】
5 面向应用相关的优化 面对数据中心多样化的部署需求和应用场景,如分布式存储、多租户性能隔离等,构建高性能的RDMA系统需要仔细选择传输模式和通信原语以充分发挥其性能优势,并调整软硬件结构进行适配.整体手段:在软件层面结合R…...
如何识别钓鱼邮件和诈骗网站?(附网络安全意识培训PPT资料)
识别钓鱼邮件和诈骗网站是网络安全中的一个重要环节。以下是一些识别钓鱼邮件和诈骗网站的方法: 识别钓鱼邮件: 检查发件人地址: 仔细查看发件人的电子邮件地址,看是否与官方域名一致。 检查邮件内容: 留意邮件中是否…...
TOGAF之架构标准规范-业务架构
TOGAF标准规范中,业务架构阶段的主要工作是开发支持架构愿景的业务架构。 如上所示,业务架构(Business Architecture)在TOGAF标准规范中处于B阶段,该阶段的主要内容包括阶段目标、阶段输入、流程步骤、架构方法。 阶段…...
嵌入式轻量级开源操作系统:HeliOS的使用
嵌入式轻量级开源操作系统:HeliOS的使用 📍项目地址:https://github.com/heliosproj/HeliOS HeliOS项目是一个社区交付的开源项目,用于构建和维护HeliOS嵌入式操作系统(OS)。HeliOS是一个功能齐全的操作系统࿰…...
mysql 数据库迁移到达梦数据库
1.windows安装达梦数据库,去官网下载 dm8 进行安装,安装后,可以使用管理工具管理数据 使用迁移工具对数据进行迁移; 2.使用php 或者 thinkphp连接达梦数据库 2.1、先PHP开启DM扩展 从达梦数据库安装目录下drivers/php_pdo 复制对…...
Java中的异常处理机制
今天想和大家探讨Java中的异常处理机制。异常处理是任何编程语言中不可或缺的一部分,它帮助我们处理程序运行时可能出现的错误,确保程序的健壮性和稳定性。Java作为一种强类型、面向对象的编程语言,提供了一套完整的异常处理框架。 1. 异常的…...
iOS 苹果开发者账号: 查看和添加设备UUID 及设备数量
参考链接:苹果开发者账号下添加新设备UUID - 简书 如果要添加新设备到 Profiles 证书里: 1.登录开发者中心 Sign In - Apple 2.找到证书设置: Certificate,Identifiers&Profiles > Profiles > 选择对应证书 edit &g…...
数据仓库工具箱—读书笔记02(Kimball维度建模技术概述03、维度表技术基础)
Kimball维度建模技术概述 记录一下读《数据仓库工具箱》时的思考,摘录一些书中关于维度建模比较重要的思想与大家分享🤣🤣🤣 第二章前言部分作者提到:技术的介绍应该通过涵盖各种行业的熟悉的用例展开(赞同…...