在面试的时候经常会问到JVM调优问题
1、线上环境,如果内存飙升了,应该怎么排查呢?
2、线上环境,如果CPU飙升了,应该怎么排查呢?
内存飙升首先要考虑是不是类有很多,并且没有被释放;使用jmap可以检查出哪个类很多CPU飙升,可以使用Jstact来找出COU飙升的原因。
下面就来研究Jmap,Jstact的用法。
目标:
1、Jmap、Jstack、Jinfo详解
2、JvisualVM调优工具实战
3、JVM内存或CPU飙高如何定位
4、JSta令预估JVM运行情况
5、系统频繁FULL GC导致系统卡顿实战调优
6、内存泄露到底是怎么回事?
这个命令是用来查看系统内存使用情况的,实例个数,以及占用内存。
命令:
jmap -histo 4528
运行结果:
num #instances #bytes class name
----------------------------------------------
1: 581 1816136 [I
2: 872 1115120 [B
3: 5235 726912 [C
4: 3682 88368 java.lang.String
5: 607 69336 java.lang.Class
6: 663 38496 [Ljava.lang.Object;
7: 841 33640 java.util.TreeMap$Entry
8: 120 8640 java.lang.reflect.Field
9: 215 8520 [Ljava.lang.String;
10: 326 7824 java.lang.StringBuilder
11: 106 6784 java.net.URL
12: 188 6016 java.io.File
13: 157 5024 java.util.Hashtable$Entry
14: 148 4736 java.util.concurrent.ConcurrentHashMap$Node
这里显示的是,byte类型的数组,有多少个实例,占用多大内存。
查看堆信息
Jmap -heap 进程号
执行结果
Attaching to process ID 2139, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 11.0.2+9
using thread-local object allocation.
Garbage-First (G1) GC with 8 thread(s)
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 4294967296 (4096.0MB)
NewSize = 1363144 (1.2999954223632812MB)
MaxNewSize = 2576351232 (2457.0MB)
OldSize = 5452592 (5.1999969482421875MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 1048576 (1.0MB)
Heap Usage:
G1 Heap:
regions = 4096
capacity = 4294967296 (4096.0MB)
used = 21654560 (20.651397705078125MB)
free = 4273312736 (4075.348602294922MB)
0.5041845142841339% used
G1 Young Generation:
Eden Space:
regions = 15
capacity = 52428800 (50.0MB)
used = 15728640 (15.0MB)
free = 36700160 (35.0MB)
30.0% used
Survivor Space:
regions = 5
capacity = 5242880 (5.0MB)
used = 5242880 (5.0MB)
free = 0 (0.0MB)
100.0% used
G1 Old Generation:
regions = 1
capacity = 210763776 (201.0MB)
used = 0 (0.0MB)
free = 210763776 (201.0MB)
0.0% used
通过上述结果分析,我们查询的内容如下:
这个命令是导出堆信息,当我们线上有内存溢出的情况的时候,可以使用Jmap -dump导出堆内存信息。然后再导入可视化工具用jvisualvm进行分析。
导出命令
jmap -dump:file=a.dump 进程号
我们还可以设置内存溢出自动导出dump文件(内存很大的时候,可能会导不出来)
1. -XX:+HeapDumpOnOutOfMemoryError
2. -XX:HeapDumpPath=./ (路径)
下面有案例说明如何使用。
上面我们用导出dump堆信息到文件中,可以使用jvisualvm工具导入dump堆信息,进行分析。
打开jvisualvm工具命令:
jvisualvm
打开工具界面如下:

点击文件->装入,可以导入文件,查看系统的运行情况了。
下面通过工具来分析内存溢出的原因。
public static void main(String[] args) {
List<Object> list = new ArrayList<>();
int i = 0;
int j = 0;
while (true) {
list.add(new User(i++, UUID.randomUUID().toString()));
new User(j--, UUID.randomUUID().toString());
}
}
public class User {
private int id;
private String name;
public User(int num,String uuid){
this.id = num;
this.name = uuid;
}
}
为了方便看到效果,所以我们会设置两组参数。
第一组:设置堆空间大小,将堆空间设置的小一些,可以更快查看内存溢出的效果
‐Xms10M ‐Xmx10M ‐XX:+PrintGCDetails
设置的堆空间时10M,并且打印GC
第二组:设置内存溢出自动导出dump文件(内存很大的时候,可能会导不出来)
1. -XX:+HeapDumpOnOutOfMemoryError
2. -XX:HeapDumpPath=./ (路径)
将这两组参数添加到项目启动配置中。
运行的过程中打印堆空间信息到文件中:
后面我们可以使用工具导入堆文件进行分析(下面有说到)。
我们还可以设置内存溢出自动导出dump文件(内存很大的时候,可能会导不出来)

-Xms10M -Xmx10M -XX:+PrintGCDetails
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/zhangsan/Downloads
-XX:+HeapDumpOnOutOfMemoryError 表示的是内存溢出的时候输出文件
-XX:HeapDumpPath=/Users/zhangsan/Downloads 表示的是内存溢出的时候输出文件的路径
这里需要注意的是堆目录要写结对路径,不能写相对路径。
我们看到,运行没有多长时间就内存溢出了。

查看导出的文件目录:

第四步:导入堆内存文件到jvisualvm工具
文件->装入->选择刚刚导出的文件

我们主要看【类】这个模块。

通过上图我们可以明确看出,有三个类实例数特别多,分别是:byte[],java.lang.String,com.jvm.User。前面两个我们不容易看出是哪里的问题,但是第三个类com.jvm.User我们就看出来了,问题出在哪里。接下来就重点排查调佣了这个类的地方,有没有出现内存没有释放的情况。
这个程序很简单,那么byte[]和java.lang.String到底是什么呢?我们的User对象结构中字段类型是String。
public class User {
private int id;
private String name;
}
既然有很多User,自然String也少不了。
那么byte[]是怎么回事呢?其实String类中有byte[]成员变量。所以也会有很多byte[]对象。

Jstack可以用来查看堆栈使用情况,还可以查看进程死锁情况。
Jstack 进程号
public class DeadLockTest {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock1) {
try {
System.out.println("thread1 begin");
Thread.sleep(5000);
} catch (InterruptedException e) {
}
synchronized (lock2) {
System.out.println("thread1 end");
}
}
}).start();
new Thread(() -> {
synchronized (lock2) {
try {
System.out.println("thread2 begin");
Thread.sleep(5000);
} catch (InterruptedException e) {
}
synchronized (lock1) {
System.out.println("thread2 end");
}
}
}).start();
}
}
下面来分析一下这段代码:
运行程序,通过Jstack命令来看看是否能检测到当前有死锁。

从这里面个异常可以看出,
通过上面的信息,我们判断出两个线程的状态都是BLOCKED,可能有点问题,然后继续往下看

我们从最后的一段可以看到这句话:Found one Java-level deadlock; 意思是找到一个死锁。死锁的线程号是Thread-0,Thread-1。
Thread-0:正在等待0x000000070fc3ad98对象的锁,这个对象现在被Thread-1持有。
Thread-1:正在等待0x000000070fc3ad88对象的锁,这个对象现在正在被Thread-0持有。
最下面展示的是死锁的堆栈信息。死锁可能发生在DeadLockTest的第17行和第31行。通过这个提示,我们就可以找出死锁在哪里了。
在程序代码启动的过程中,打开jvisualvm工具。

找到当前运行的类,查看线程,就会看到最头上的一排红字:检测到死锁。然后点击“线程Dump”按钮,查看相信的线程死锁的信息。

这里可以找到线程死锁的详细信息,具体内容和上面使用Jstack命令查询的结果一样,这里实用工具更加方便。
我们使用案例来说明如何查询cpu线程飙高的问题。
代码:
package com.lxl.jvm;
public class Math {
public static int initData = 666;
public static User user = new User();
public User user1;
public int compute() {
int a = 1;
int b = 2;
int c = (a + b) * 10;
return c;
}
public static void main(String[] args) {
Math math = new Math();
while(true){
math.compute();
}
}
}
这是一段死循环代码,会占满cpu。下面就运行这段代码,来看看如何排查cpu飙高的问题。
top

我们看到cpu严重飙高,一般cpu达到80%就会报警了
使用【top -p 进程号】 查看进程id的cpu占用情况

需要注意的是,这里的H是大写的H。

我们可以看出线程0和线程1线程号飙高。
通过上图我们可以看到占用cpu资源最高的线程由两个,线程分别是4018362,4018363.我们以第一个为例说明,如何查询这个线程是哪个线程,以及这个线程的什么地方出现问题,导致cpu飙高。
4018362该线程为十进制,转为十六进制为3d50ba。具体转换可以网上查询工具。
接下来查询飙高线程的堆栈信息
jstack 4013440|grep -A 10 3d50ba
通过这个方式可以查询到这个线程对应的堆栈信息

从这里我们可以看出有问题的线程id是0x4cd0, 哪一句代码有问题呢,Math类的22行。
上述方法定位问题已经很精确了,接下来就是区代码里排查为什么会有问题了。
Jinfo命令主要用来查看jvm参数
执行结果:

从结果可以看出,我们使用的是CMS+Parallel垃圾收集器
jinfo -sysprops 进程id
执行结果:
Java System Properties:
#Thu Nov 11 17:28:19 CST 2021
java.runtime.name=OpenJDK Runtime Environment
java.protocol.handler.pkgs=org.springframework.boot.loader
sun.boot.library.path=/data/java/jdk8/jre/lib/amd64
java.vm.version=25.40-b25
java.vm.vendor=Oracle Corporation
java.vendor.url=http\://java.oracle.com/
path.separator=\:
java.vm.name=OpenJDK 64-Bit Server VM
file.encoding.pkg=sun.io
user.country=CN
sun.java.launcher=SUN_STANDARD
sun.os.patch.level=unknown
java.vm.specification.name=Java Virtual Machine Specification
user.dir=/data/temp
java.runtime.version=1.8.0_41-b04
java.awt.graphicsenv=sun.awt.X11GraphicsEnvironment
java.endorsed.dirs=/data/java/jdk8/jre/lib/endorsed
os.arch=amd64
java.io.tmpdir=/tmp
line.separator=\n
java.vm.specification.vendor=Oracle Corporation
os.name=Linux
sun.jnu.encoding=UTF-8
java.library.path=/usr/java/packages/lib/amd64\:/usr/lib64\:/lib64\:/lib\:/usr/lib
java.specification.name=Java Platform API Specification
java.class.version=52.0
sun.management.compiler=HotSpot 64-Bit Tiered Compilers
os.version=5.10.23-5.al8.x86_64
user.home=/root
user.timezone=Asia/Shanghai
java.awt.printerjob=sun.print.PSPrinterJob
file.encoding=UTF-8
java.specification.version=1.8
user.name=root
java.class.path=chapter1-jvm-0.0.1-SNAPSHOT.jar
java.vm.specification.version=1.8
sun.java.command=chapter1-jvm-0.0.1-SNAPSHOT.jar
java.home=/data/java/jdk8/jre
sun.arch.data.model=64
user.language=zh
java.specification.vendor=Oracle Corporation
awt.toolkit=sun.awt.X11.XToolkit
java.vm.info=mixed mode
java.version=1.8.0_41
java.ext.dirs=/data/java/jdk8/jre/lib/ext\:/usr/java/packages/lib/ext
sun.boot.class.path=/data/java/jdk8/jre/lib/resources.jar\:/data/java/jdk8/jre/lib/rt.jar\:/data/java/jdk8/jre/lib/sunrsasign.jar\:/data/java/jdk8/jre/lib/jsse.jar\:/data/java/jdk8/jre/lib/jce.jar\:/data/java/jdk8/jre/lib/charsets.jar\:/data/java/jdk8/jre/lib/jfr.jar\:/data/java/jdk8/jre/classes
java.vendor=Oracle Corporation
file.separator=/
java.vendor.url.bug=http\://bugreport.sun.com/bugreport/
sun.io.unicode.encoding=UnicodeLittle
sun.cpu.endian=little
sun.cpu.isalist=
Jstat命令是jvm调优非常重要,且非常有效的命令。我们来看看她的用法:
jstat -gc 进程id
这个命令非常常用,在线上有问题的时候,可以通过这个命令来分析问题。
下面我们来测试一下,启动一个项目,然后在终端驶入jstat -gc 进程id,得到如下结果
下面我们来测试一下,启动一个项目,然后在终端驶入jstat -gc 进程id,得到如下结果:

上面的参数分别是什么意思呢?先识别参数的含义,然后根据参数进行分析
连续观察GC变化的命令
jstat -gc 进程ID 间隔时间 打印次数
举个例子:我要打印10次gc信息,每次间隔1秒
jstat -gc 进程ID 1000 10

这样就连续打印了10次gc的变化,每次隔一秒。
这个命令是对整体垃圾回收情况的统计,下面将会差分处理。
命令:
jstat -gcnew 进程ID [ 间隔时间 打印次数]
这个指的是当前某一次GC的内存情况

jstat -gcnewcapacity 进程ID

参数含义:
命令:
jstat -gcold 进程ID

参数含义:
命令:
jstat -gcoldcapacity 进程ID

参数含义:
命令
jstat -gcmetacapacity 进程ID

命令:
jstat -gcutil 进程ID

现在有一个线上的异常情况。具体详情如下:
如何能够知道系统运行期间发生了多少次young gc和多少次Full gc,并且他们的耗时是多少呢?使用如下命令:
jstat -gcutil 进程ID
然后就可以看到程序运行的结果了;

这几个参数的具体含义是什么呢?
JVM优化的目标其实主要是Full GC。只要不发生Full GC,基本就不会出现OOM。所以如何优化Full GC就是我们的目标。往前推,老年代的对象是怎么来的呢?从新生代来的,那么我们就要避免朝生夕死的对象进入老年代。
1)分析GC 数据:
期间发生的Full GC次数和耗时:500多次,用时200多秒。平均7*2*3600s/500 = 20分钟发生一次Full GC,每次full gc耗时:200秒/500=400毫秒;
期间发生的Young GC次数和耗时:1万多次,用时500多秒,那么平均7*24*3600秒/10000 = 60秒也就是每一分钟发生一次Young GC,每次Young GC耗时:500/10000=50毫秒;
其实从full gc和young gc的时间来看,还好,不太长。主要是发生的频次,full gc发生的频次太高了,20分钟一次,通常我们的full gc怎么也要好几个小时触发一次,甚至一天才触发一次。而Young gc触发频次也过于频繁,1分钟触发一次。
根据上述信息,我们可以画一个内存模型出来。
先来看看原系统的JVM参数配置信息
-Xms1536M -Xmx1536M -Xmn512M -Xss256K -XX:SurvivorRatio=6 -XX:MetaspaceSize=256M
-XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:UseConcMarkSweepGC
-XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccUpancyOnly
根据参数我们梳理/如下内存模型。堆内存空间都分配好了,那么上面说了每过60s就会触发一次Young GC,那么就是说,平均每秒会产生384/60=6.4M的垃圾。而老年代,每过20分钟就会触发一次GC,而老年代可用的内存空间时0.75G,就是750多M。

现在的问题,为什么每过20分钟,就会有750M的对象挪到老年代呢?解决了这个问题,我们就可以阻止对象挪到老年代
结合对象挪动到老年代的规则分析这个模型可能会有那哪些问题:
1.大对象
2.顽固的对象
3.动态年龄判断机制
4.老年代空间担保机制
既然这种情况有可能,那我们就来分析一下:
线程每秒中产生6M多的垃圾,如果并发量比较大的时候,处理速度比较慢,可能1S处理不玩,假设处理完数据要四五秒,就按五秒来算,那么一秒就可能产生30M的垃圾,这时候触发Eden区回收的是,就30M的垃圾要进入到S1区,而S1区很可能本身就有一部分对象了,再加上这30M就大于S1区的一半了,直接进入老年代。
这只是一种可能。
综上所述,现在最有可能频繁触发GC的可能的原因是动态年龄判断机制。我们之前在做优化的时候,遇到过。可以将Survivor区域放大一点,就可以了。
我们用下面这个案例来模拟分析上述情况。分析找到问题。
这时一个springboot的web程序,内容很简单。创建项目的时候注意选择web就可以了
然后里面定义了一个User对象。这个User对象比较特别,里面有个参数a分配0.1M的内存空间。
public class User {
private int id;
private String name;
byte[] a = new byte[1024*100];
public User(){}
public User(int id, String name) {
super();
this.id = id;
this.name = name;
}
......
}
package com.jvm;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
@RestController
public class IndexController {
@RequestMapping("/user/process")
public String processUserData() throws InterruptedException {
ArrayList<User> users = queryUsers();
for (User user: users) {
//TODO 业务处理
System.out.println("user:" + user.toString());
}
return "end";
}
/**
* 模拟批量查询用户场景
* @return
*/
private ArrayList<User> queryUsers() {
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 5000; i++) {
users.add(new User(i,"zhuge"));
}
return users;
}
}
接口类很简单,每次调用接口,先创建5000个用户,然后让这个5000个用户执行各自的业务逻辑。需要注意的是5000个用户占用内存空间约500M。也就是说每次调用接口,都会产生500M的对象。
package com.example.seckill;
import com.jvm.Application;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.web.client.RestTemplate;
@RunWith(SpringRunner.class)
@SpringBootTest(classes={Application.class})// 指定启动类
public class ApplicationTests {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Autowired
private RestTemplate restTemplate;
@Test
public void test() throws Exception {
for (int i = 0; i < 10000; i++) {
String result = restTemplate.getForObject("http://localhost:8080/user/process", String.class);
Thread.sleep(1000);
}
}
}
测试类很简单,就是手动调用上面的接口。循环调用10000次。
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
我们要模拟线上的情况,所以参数也设置和线上一样的情况。
-Xms1536M -Xmx1536M -Xmn512M -Xss256K -XX:SurvivorRatio=6
-XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=75
-XX:+UseCMSInitiatingOccupancyOnly
程序启动起来以后,可以使用jps命令查看进程,输入命令查看gc触发情况
jstat -gc 进程ID [间隔时间 触发次数]
jstat -gc 8620 1000 10000
表示观察8620这个进程,每隔1s打印一次gc情况,连续打印10000次
执行jstat -gc前先执行jinfo -flags 线程id查询jvm参数是否配置成功。

然后再执行jstat -gc 线程id 1000 10000
我们可以看到,程序启动以后都是触发了1次young gc,1次Full GC,程序启动触发gc都是ok的,后面基本没有什么垃圾产生了。
这一步没啥说的,直接启动程序就可以了
jstat -gc 8620 1000 10000

程序启动以后,我们发现频繁的触发了gc,新生代gc触发很频繁,老年代也很频繁。老年代gc触发那么频繁,那就是有问题了。根据上面的分析,最有可能的情况是动态年龄分配机制。可能产生的对象在Survivor区放不下,直接进入老年代。处理这个问题的方法是,扩大年轻代空间。
-Xms1536M -Xmx1536M -Xmn1024M -Xss256K -XX:SurvivorRatio=6
-XX:MetaspaceSize=256M -XX:MaxMetaspaceSize=256M -XX:+UseParNewGC
-XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=92
-XX:+UseCMSInitiatingOccupancyOnly
内存空间变化后:

再次启动项目,看运行结果

基本没有gc触发。说明我们的优化是有效的。然后在启动测试程序:

这次我们发现,触发gc的次数相对来说少了,gc的速度相对于上一次小了些,但是又有新的问题发生:老年代gc比年轻代还要频繁。这是怎么回事呢?有什么情况会让老年代触发gc频繁大于年轻代呢?
这可能会有几种情况
云数据空间比较好看,我们之间看输出的参数

红框圈的就是云数据空间和元数据已用空间。通过观察,我们发现元数据大小基本上是不变的。所以,元数据空间不太会增加导致触发full gc。
这个情况一般在线上都会进制成代码触发full gc。需要通过-XX:+DisableExplicitGC参数禁用,如果加上了这个jvm启动参数,那么代码中调用System.gc()没有任何效果。
结合之前学习的理论,我们知道,老年代空间担保机制。有可能在触发一次minor gc的时候触发了两次full gc。
来复习一下:

1)、年轻代每次minor gc之前JVM都会计算下老年代剩余可用空间。如果这个可用空间小于年轻代里现有的所有对象大小之和(包括垃圾对象),就会看一个“-XX:-HandlePromotionFailure”(jdk1.8默认就设置了)的参数是否设置了,如果有这个参数,就会看看老年代的可用内存大小,是否大于之前每一次minor gc后进入老年代的对象的平均大小。
2)、如果上一步结果是小于或者之前说的参数没有设置,那么就会直接触发一次Full gc,然后在触发Minor gc,如果回收完还是没有足够的空间存放新的对象就会发生“OOM”
3)、如果minor gc之后剩余存活的需要挪动到老年代的对象大小还是大于老年代可用空间,那么也会触发full gc,full gc完之后如果还是没有空间放minor gc之后的存活对象,则会发生“OOM”。
在梳理一下这块逻辑,为什么叫担保机制。在触发Minor gc的时候,进行了一个调条件判断,预估老年代空间是否能够放得下新生代的对象,如果能够放得下,那么就直接触发Minor GC,如果放不下,那么就会先触发Full GC。在触发Full GC的时候如果设置了担保机制参数会增加异步判断,而不是直接触发Full GC。判断老年代剩余可用空间 是否小于 历史每次Minor GC后进入老年代对象的平均值。这样的判断可以减少Full gc的次数,因为新生代在触发Full gc以后是会回收一部分内存的,剩余部分再放入老年代,可能就能放下了。
通过回顾,我们看到老年代担保机制中,当触发了一次Minor GC的时候,有可能会触发两次Full gc。这样就导致Full gc的次数大于Minor gc。
由此可见,我们这次优化是失败的,还引入了新的问题。这里还有可能是大对象导致的,不一定是非常大的一个对象。也可能是多个对象在一个时刻产生的大对象。
我们查找是否有大对象,或者某一个时间是否有大对象占用较大的内存空间,可以使用命令或者终端查看
jmap -histo 进程ID

前面都是系统对象,往下找我们看到一个自定义对象User,这个实例有10000个,占用内存空间240M
或者使用jvisualvm

点击内存,就可以实时看到系统进程的内存占用情况。点击内存其实就是对【jmap -histo 进程id】命令的包装
内存占用最多的是byte[]数组,占用了内存的95%。是什么情况让byte数组占用这么多的内存呢?这个通常都是用户自定义对象造成的。往下看,我们看到User对象,user对象占用了12W字节数据,有5000个实例。
假如这个代码不是我们写的,是别人写的,我们不熟悉。这时候可以通过一下方法定位问题
/**
* 模拟批量查询用户场景
* @return
*/
private ArrayList<User> queryUsers() {
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 5000; i++) {
users.add(new User(i,"zhuge"));
}
return users;
}
我们发现在这里创建了5000个对象。不过5000个对象应该也不多。通常一个对象也就几k,我们进到User里看看
public class User {
private int id;
private String name;
byte[] a = new byte[1024*100];
}
意外发现,User里定义了一个byte数组,一个byte数组占用100k的空间。那么问题就是在这里了。
系统中有这么多对象,说明什么问题呢?new User()在反复执行,这样的话,cpu占用率应该不低。如果这边不好找,我们可以看看cpu占用情况。

这里的cpu其实就是对命令jstack命令的封装【jstack 进程ID|grep -A 10 线程ID】
通过分析我们看出第一个tack()方法占用cpu最高,达到98%,但是这个是什么东西呢,我们不太熟悉,看看第二个,第二个是queryUser(),这个是我们自己的方法,可以看看这个方法的具体内容:
/**
* 模拟批量查询用户场景
* @return
*/
private ArrayList<User> queryUsers() {
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 5000; i++) {
users.add(new User(i,"zhuge"));
}
return users;
}
public class User {
private int id;
private String name;
byte[] a = new byte[1024*100];
}
刚好就定位到这段代码,我们发现他一下查询了5000个对象,并且每个对象里定义了一个大对象。这样我们就定位到了问题。
所以在查询数据的时候,要注意是否有大对象,如果有大对象的话,需要预估一下内存消耗。剩下就是代码优化的问题了。
我们这里降低查询用户数从一次5000到一次500,然后重启代码试一下:
private ArrayList<User> queryUsers() {
ArrayList<User> users = new ArrayList<>();
for (int i = 0; i < 500; i++) {
users.add(new User(i,"zhuge"));
}
return users;
}
来看看运行效果

触发young gc的频率降低了,而且基本不会触发full gc了。说明这次优化是有效的。
无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD
C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.
本文主要介绍在使用Selenium进行自动化测试或者任务时,对于使用了iframe的页面,如何定位iframe中的元素文章目录场景描述解决方案具体代码场景描述当我们在使用Selenium进行自动化测试的时候,可能会遇到一些界面或者窗体是使用HTML的iframe标签进行承载的。对于iframe中的标签,如果直接查找是无法找到的,会抛出没有找到元素的异常。比如近在咫尺的例子就是,CSDN的登录窗体就是使用的iframe,大家可以尝试通过F12开发者模式查看到的tag_name,class_name,id或者xpath来定位中的页面元素,会抛出NoSuchElementException异常。解决
1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,
我最喜欢的Google文档功能之一是它会在我工作时不断自动保存我的文档版本。这意味着即使我在进行关键更改之前忘记在某个点进行保存,也很有可能会自动创建一个保存点。至少,我可以将文档恢复到错误更改之前的状态,并从该点继续工作。对于在MacOS(或UNIX)上运行的Ruby编码器,是否有具有等效功能的工具?例如,一个工具会每隔几分钟自动将Gitcheckin我的本地存储库以获取我正在处理的文件。也许我有点偏执,但这点小保险可以让我在日常工作中安心。 最佳答案 虚拟机有些人可能讨厌我对此的回应,但我在编码时经常使用VIM,它具有自动保存功
我正在尝试上传文件。一个简单的hello.txt。我正在关注文档,但无法将其上传到我的存储桶。#STARTAWSCLIENTs3=Aws::S3::Resource.newbucket=s3.bucket(BUCKET_NAME)begins3.buckets[BUCKET_NAME].objects[KEY].write(:file=>FILE_NAME)puts"Uploadingfile#{FILE_NAME}tobucket#{BUCKET_NAME}."bucket.objects.eachdo|obj|puts"#{obj.key}=>#{obj.etag}"endresc
我有一个Highstock图表(带有标记和阴影的线条),并且想以编程方式显示一个highstock工具提示,例如,当我选择某个表上的一行(包含图表数据)我想显示相应的highstock工具提示。这可能吗? 最佳答案 股票图表thissolution不起作用:在thisexample你必须更换这个:chart.tooltip.refresh(chart.series[0].data[i]);为此:chart.tooltip.refresh([chart.series[0].points[i]]);解决方案可用here.
目录0专栏介绍1平面2R机器人概述2运动学建模2.1正运动学模型2.2逆运动学模型2.3机器人运动学仿真3动力学建模3.1计算动能3.2势能计算与动力学方程3.3动力学仿真0专栏介绍?附C++/Python/Matlab全套代码?课程设计、毕业设计、创新竞赛必备!详细介绍全局规划(图搜索、采样法、智能算法等);局部规划(DWA、APF等);曲线优化(贝塞尔曲线、B样条曲线等)。?详情:图解自动驾驶中的运动规划(MotionPlanning),附几十种规划算法1平面2R机器人概述如图1所示为本文的研究本体——平面2R机器人。对参数进行如下定义:机器人广义坐标
网站的日志分析,是seo优化不可忽视的一门功课,但网站越大,每天产生的日志就越大,大站一天都可以产生几个G的网站日志,如果光靠肉眼去分析,那可能看到猴年马月都看不完,因此借助网站日志分析工具去分析网站日志,那将会使网站日志分析工作变得更简单。下面推荐两款网站日志分析软件。第一款:逆火网站日志分析器逆火网站日志分析器是一款功能全面的网站服务器日志分析软件。通过分析网站的日志文件,不仅能够精准的知道网站的访问量、网站的访问来源,网站的广告点击,访客的地区统计,搜索引擎关键字查询等,还能够一次性分析多个网站的日志文件,让你轻松管理网站。逆火网站日志分析器下载地址:https://pan.baidu.
一、机器人介绍 此处是基于MATLABRVC工具箱,对ABB-IRB-1200型号的微型机械臂进行正逆向运动学分析,并利Simulink工具实现对机械臂进行具有动力学参数的末端轨迹规划仿真,最后根据机械模型设计Simulink-Adams联合仿真。 图1.ABBIRB 1200尺寸参数示意图ABBIRB 1200提供的两种型号广泛适用于各作业,且两者间零部件通用,两种型号的工作范围分别为700 mm 和 900 mm,大有效负载分别为 7 kg 和5 kg。 IRB 1200 能够在狭小空间内能发挥其工作范围与性能优势,具有全新的设计、小型化的体积、高效的性能、易于集成、便捷的接