entityframeworkcore性能优化(代码片段)

JimCarter JimCarter     2023-01-06     444

关键词:

https://docs.microsoft.com/zh-cn/ef/core/performance/

1. 定位性能问题

1.1 通过LogTo方法

定位生成的sql语句和sql的执行时间。

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

    optionsBuilder
        .UseSqlServer(@"Server=(localdb)\\mssqllocaldb;Database=Blogging;Integrated Security=True")
        .LogTo(Console.WriteLine, LogLevel.Information);

info: 06/12/2020 09:12:36.117 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT [b].[Id], [b].[Name]
      FROM [Blogs] AS [b]
      WHERE [b].[Name] = N'foo'

这个sql执行了4ms。

注意:不建议在生产环境中配置LogTo。一方面会影响程序的运行速度,另一方面过多的日志会占用空间。

2.2 将sql与LINQ查询关联起来

上述的方法即使你知道了sql的问题所在,有时候也很难找到对应的linq是哪一个。所以需要跟linq打tag:

var myLocation = new Point(1, 2);
var nearestPeople = (from f in context.People.TagWith("This is my spatial query!")
                     orderby f.Location.Distance(myLocation) descending
                     select f).Take(5).ToList();

生成的sql中就会出现tag:

-- This is my spatial query!

SELECT TOP(@__p_1) [p].[Id], [p].[Location]
FROM [People] AS [p]
ORDER BY [p].[Location].STDistance(@__myLocation_0) DESC

2.解决问题

找到有问题的sql之后,下一步是分析这个sql的执行流程。对于sql server来说,SSMS执行sql时会生成这个sql的执行计划图,可做进一步分析。

3. 进行Benchmark

如果同一个功能可以有多个实现,我们可以借助BenchmarkDotNet库进行基准测试。这里提供了一个基准测试用来比对一个计算平均值功能的4中实现:

  1. 加载所有实体,然后计算平均值
  2. 加载所有实体但是不启用跟踪(AsNoTracking),然后计算平均值
  3. 仅加载要计算的列,然后计算平均值
  4. 让数据库计算平均值

四个方法的实现如下:

//跟踪
[Benchmark]
public double LoadEntities()

    var sum = 0;
    var count = 0;
    using var ctx = new BloggingContext();
    foreach (var blog in ctx.Blogs)
    
        sum += blog.Rating;
        count++;
    
    return (double)sum / count;

//不跟踪
[Benchmark]
public double LoadEntitiesNoTracking()

    var sum = 0;
    var count = 0;
    using var ctx = new BloggingContext();
    foreach (var blog in ctx.Blogs.AsNoTracking())
    
        sum += blog.Rating;
        count++;
    

    return (double)sum / count;

//仅加载Rating列
[Benchmark]
public double ProjectOnlyRanking()

    var sum = 0;
    var count = 0;
    using var ctx = new BloggingContext();
    foreach (var rating in ctx.Blogs.Select(b => b.Rating))
    
        sum += rating;
        count++;
    

    return (double)sum / count;

//数据库进行计算
[Benchmark(Baseline = true)]
public double CalculateInDatabase()

    using var ctx = new BloggingContext();
    return ctx.Blogs.Average(b => b.Rating);

结果如下:

方法平均值错误标准偏差中值比率RatioSD第 0 代第 1 代第 2 代已分配
LoadEntities2,860.4μs54.31μs93.68μs2844.5μs4.550.33210.937570.3125-1309.56 KB
LoadEntitiesNoTracking1353.0μs21.26μs18.85μs1355.6μs2.100.1487.89063.9063-540.09 KB
ProjectOnlyRanking910.9μs20.91μs61.65μs892.9μs1.460.1441.01560.9766-252.08 KB
CalculateInDatabase627.1μs14.58μs42.54μs626.4μs1.000.004.8828--33.27 KB

注意:BenchmarkDotNet只是单线程运行方法

4. 查询优化

4.1 正确使用索引

// Matches on start, so uses an index (on SQL Server)
var posts1 = context.Posts.Where(p => p.Title.StartsWith("A")).ToList();
// Matches on end, so does not use the index
var posts2 = context.Posts.Where(p => p.Title.EndsWith("A")).ToList();

配合数据库检查工具(如之前介绍的SSMS的执行计划),找出索引问题。

4.2 只查需要的属性

如果只需要Id和Url属性那就不要查其他的。

foreach (var blogName in context.Blogs.Select(b =>newId=b.Id,Url=b.Url))

    Console.WriteLine("Blog: " + blogName.Url);

这样生成的sql比较短:

SELECT [b].[Id],[b].[Url]
FROM [Blogs] AS [b]

4.3 使用Take限制返回的条数

var blogs25 = context.Posts
    .Where(p => p.Title.StartsWith("A"))
    .Take(25)
    .ToList();

4.4 使用拆分查询AsSplitQuery避免笛卡尔积爆炸

AsSplitQuery要配合Include使用,而且只对一对多的关系进行查询时才生效,多对一和一对一不会拆分。

4.5 使用Include进行预先加载

可以在一个sql中把关联的数据加载过来,避免生成多次sql查询

using (var context = new BloggingContext())

    var filteredBlogs = context.Blogs
        .Include(
            blog => blog.Posts
                .Where(post => post.BlogId == 1)
                .OrderByDescending(post => post.Title)
                .Take(5))
        .ToList();

如果当前不需要关联实体的数据,可以考虑使用“显示加载”和“懒加载”。

4.6 注意“懒加载”产生的N+1问题

对于以下代码会生成4条sql,分来用来查询blog和查询三个blog对应的post。又被称为N+1问题

foreach (var blog in context.Blogs.ToList())

    foreach (var post in blog.Posts)
    
        Console.WriteLine($"Blog blog.Url, Post: post.Title");
    

可以优化为以下单个sql查询:

foreach (var blog in context.Blogs.Select(b => new  b.Url, b.Posts ).ToList())

    foreach (var post in blog.Posts)
    
        Console.WriteLine($"Blog blog.Url, Post: post.Title");
    

4.7 缓冲和流式处理

缓冲:将所有查询结果加载到内存里

流式:每次将结果集的一部分放到内存里,无论查询1条还是1万条,占用的内存空间都是固定的。当查询大量数据时考虑使用这种方式。

// ToList 和 ToArray 将导致缓冲
var blogsList = context.Posts.Where(p => p.Title.StartsWith("A")).ToList();
var blogsArray = context.Posts.Where(p => p.Title.StartsWith("A")).ToArray();

// 流式:一次只处理一行数据
foreach (var blog in context.Posts.Where(p => p.Title.StartsWith("A")))

    // ...


// AsEnumerable 也属于流式
var doubleFilteredBlogs = context.Posts
    .Where(p => p.Title.StartsWith("A")) // 服务端和数据库执行
    .AsEnumerable()
    .Where(p => SomeDotNetMethod(p)); // 这段代码将在客户端执行

4.8 efcore的内部缓冲

除了ToListToArray会进行缓冲外,当遇到以下两种情况时,efcore内部也会缓冲一些数据。

  1. 当重试策略准备就绪时。这是为了保证重试查询能够返回相同的结果。
  2. 当使用了拆分查询时,会缓冲除最后一个sql外其他的sql查询数据。除非sql server启用了MARS。

注意:如果你的重试策略里使用了ToList,那么就会缓冲两次。

4.9 使用AsNoTracking查询

当不关心查询出来的数据的状态时,就可以使用AsNoTracking提高效率。

4.10 直接执行sql

某些情况下你自己写的sql可能更优。

4.11 使用异步方法

尽量使用诸如SaveChangesAsync之类的异步方法。同步方法会在数据库IO期间阻塞线程,当其他请求进来时可能需要再次开辟新的线程,增加总的线程数和线程上线文切换次数。

4.12 实体上尽量使用[Required]特性

  1. 非null的比较比null的比较要快,所以尽量将实体的属性上填写[Required]特性。
  2. 尽量检查是否相等(==)避免检查不相等(!=)。因为==有时不用区分是否为null。
  3. 查询时显示指定某些属性不能为null,下面第二行代码要快于第一行:
var query1 = context.Entities.Where(e => e.String1 != e.String2 || e.String1.Length == e.String2.Length);
var query2 = context.Entities.Where(
    e => e.String1 != null && e.String2 != null && (e.String1 != e.String2 || e.String1.Length == e.String2.Length));

5. 更新优化

5.1 批量处理(Batching)

var blog = context.Blogs.Single(b => b.Url == "http://someblog.microsoft.com");
blog.Url = "http://someotherblog.microsoft.com";
context.Add(new Blog  Url = "http://newblog1.microsoft.com" );
context.Add(new Blog  Url = "http://newblog2.microsoft.com" );
context.SaveChanges();

以上代码会产生4个sql往返:查询、更新、插入、插入。

info: 2021/5/31 17:49:19.507 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (5ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT TOP(2) [b].[Id], [b].[Name], [b].[Url]
      FROM [Blogs] AS [b]
      WHERE [b].[Name] = N'fish'
info: 2021/5/31 17:49:19.517 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (1ms) [Parameters=[@p1='?' (DbType = Int64), @p0='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
      SET NOCOUNT ON;
      UPDATE [Blogs] SET [Url] = @p0
      WHERE [Id] = @p1;
      SELECT @@ROWCOUNT;
info: 2021/5/31 17:49:19.518 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[@p0='?' (Size = 4000), @p1='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
      SET NOCOUNT ON;
      INSERT INTO [Blogs] ([Name], [Url])
      VALUES (@p0, @p1);
      SELECT [Id]
      FROM [Blogs]
      WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();
info: 2021/5/31 17:49:19.520 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (0ms) [Parameters=[@p0='?' (Size = 4000), @p1='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
      SET NOCOUNT ON;
      INSERT INTO [Blogs] ([Name], [Url])
      VALUES (@p0, @p1);
      SELECT [Id]
      FROM [Blogs]
      WHERE @@ROWCOUNT = 1 AND [Id] = scope_identity();

efcore是否将多个sql放到一个数据库往返里取决于所使用的数据库provider。例如,当涉及到4条以下或40条以上的语句时,对sql server进行批处理效率会比较低。所以efcore再默认情况下单个批处理中最多只会执行42条语句,然后在其他往返中执行其他语句。

当然你可以手动进行配置,当语句在1-100之间时就启用批处理:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

    optionsBuilder.UseSqlServer(
        @"Server=(localdb)\\mssqllocaldb;Database=Blogging;Integrated Security=True",
        o => o
            .MinBatchSize(1)
            .MaxBatchSize(100));

配置完之后针对上述同一个操作,可以看到只有两次往返:

info: 2021/5/31 17:48:37.891 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (4ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT TOP(2) [b].[Id], [b].[Name], [b].[Url]
      FROM [Blogs] AS [b]
      WHERE [b].[Name] = N'fish'
info: 2021/5/31 17:48:37.909 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (7ms) [Parameters=[@p1='?' (DbType = Int64), @p0='?' (Size = 4000), @p2='?' (Size = 4000), @p3='?' (Size = 4000), @p4='?' (Size = 4000), @p5='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
      SET NOCOUNT ON;
      UPDATE [Blogs] SET [Url] = @p0
      WHERE [Id] = @p1;
      SELECT @@ROWCOUNT;

      DECLARE @inserted1 TABLE ([Id] bigint, [_Position] [int]);
      MERGE [Blogs] USING (
      VALUES (@p2, @p3, 0),
      (@p4, @p5, 1)) AS i ([Name], [Url], _Position) ON 1=0
      WHEN NOT MATCHED THEN
      INSERT ([Name], [Url])
      VALUES (i.[Name], i.[Url])
      OUTPUT INSERTED.[Id], i._Position
      INTO @inserted1;

      SELECT [t].[Id] FROM [Blogs] t
      INNER JOIN @inserted1 i ON ([t].[Id] = [i].[Id])
      ORDER BY [i].[_Position];

5.2 大容量更新(Bulk Update)

以下代码

foreach(var p in context.Posts)

    p.Content += "a";

context.SaveChanges();

产生了两次数据库往返,最后一次往返用来update数据:

info: 2021/5/31 17:44:32.068 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT [p].[Id], [p].[BlogId], [p].[Content], [p].[IsDeleted], [p].[Title]
      FROM [Posts] AS [p]
info: 2021/5/31 17:44:32.098 RelationalEventId.CommandExecuted[20101] (Microsoft.EntityFrameworkCore.Database.Command)
      Executed DbCommand (28ms) [Parameters=[@p1='?' (DbType = Int64), @p0='?' (Size = 4000), @p3='?' (DbType = Int64), @p2='?' (Size = 4000), @p5='?' (DbType = Int64), @p4='?' (Size = 4000), @p7='?' (DbType = Int64), @p6='?' (Size = 4000), @p9='?' (DbType = Int64), @p8='?' (Size = 4000), @p11='?' (DbType = Int64), @p10='?' (Size = 4000)], CommandType='Text', CommandTimeout='30']
      SET NOCOUNT ON;
      UPDATE [Posts] SET [Content] = @p0
      WHERE [Id] = @p1;
      SELECT @@ROWCOUNT;

      UPDATE [Posts] SET [Content] = @p2
      WHERE [Id] = @p3;
      SELECT @@ROWCOUNT;

      UPDATE [Posts] SET [Content] = @p4
      WHERE [Id] = @p5;
      SELECT @@ROWCOUNT;

      UPDATE [Posts] SET [Content] = @p6
      WHERE [Id] = @p7;
      SELECT @@ROWCOUNT;

      UPDATE [Posts] SET [Content] = @p8
      WHERE [Id] = @p9;
      SELECT @@ROWCOUNT;

      UPDATE [Posts] SET [Content] = @p10
      WHERE [Id] = @p11;
      SELECT @@ROWCOUNT;

虽然efcore对最后一次往返进行了batching,但是还是不够完美。第一,查询了post的所有字段并进行了跟踪。第二,保存时需要与快照对比确定哪些属性改变了,并将这些改变生成sql。遗憾的是efcore现在还不支持大容量更新(Bulk Update),所以需要我们手动执行sql:

context.Database.ExecuteSqlRaw("UPDATE [Employees] SET [Salary] = [Salary] + 1000");

6. 其他优化性能的方式

6.1 使用DbContext

ASP.NET Core的构造函数注入或Razor Pages的构造函数注入上下文,每次都是一个新实例。通过重复利用上下文,而不是为每个请求都创建上下文的实例,可以提高系统的吞吐量。通过AddDbContextPool启用上下文池:

services.AddDbContextPool<BloggingContext>(
    options => options.UseSqlServer(connectionString));

当请求实例时,efcore先检查池子中有没有可用的实例。请求处理完成后,会重置实例的所有状态,然后放入池中。可以设置AddDbContextPoolpoolSize参数来控制池子中最多可以由多少个实例。当超出poolSize后池子就不再缓存新的实例。

6.2 查询缓存与参数化查询

efcore会先将linq查询树“编译”为对应的sql语句。由于这个过程比较繁重,efcore会按照查询树的形状缓存查询:树结构相同的查询会重用内部缓存的编译结果。

考虑下述代码:

var post1 = context.Posts.FirstOrDefault(p => p.Title == "post1");
var post2 = context.Posts.FirstOrDefault(p => p.Title == "post2");

因为linq查询树包含不同的常量,所以查询树不一样。每一个查询都会被单独编译,然后生成以下细微差别的sql:

SELECT TOP(1) [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] = N'blog1'

SELECT TOP(1) [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] = N'blog2'

因为sql不相同,所以查询计划也不一样。

但是我们可以做以下优化:

var postTitle = "post1";
var post1 = context.Posts.FirstOrDefault(p => p.Title == postTitle);
postTitle = "post2";
var post2 = context.Posts.FirstOrDefault(p => p.Title == postTitle);

现在已经将常量参数化,所以两个查询树一样,并且efcore只需要编译一次。生成的sql也进行了参数化,允许数据库重复使用同一查询计划:

SELECT TOP(1) [b].[Id], [b].[Name]
FROM [Blogs] AS [b]
WHERE [b].[Name] = @__blogName_0

注意:没必要参数化每一个查询,使用常量进行查询也还是ok的。efcore会进行优化,详见下一章节。

EF Core的事件计数器报告查询缓存命中率。 在正常的应用程序中,在程序启动后,此计数器不久就会达到100%,一旦大多数查询执行至少一次。 如果此计数器的值低于100%,则表示应用程序可能正在执行与查询缓存不一致的操作.

数据库如何管理缓存查询计划依赖于数据库。 例如,SQL Server 隐式维护 LRU 查询计划缓存,而 PostgreSQL 不 (但已准备好的语句可能会产生非常类似的最终效果) 。 有关更多详细信息,请参阅数据库文档。

javascript性能优化1——内存管理(js垃圾回收机制引用计数标记清除标记整理v8分代回收performance使用)(代码片段)

目录一、导入二、内存管理1.JavaScript中的内存管理2.JavaScript中的垃圾回收概念可达对象引用样例可达样例3.GC算法介绍GC定义与作用GC里的垃圾是什么GC算法是什么常见GC算法     3.1引用计数      实现原理      引用计数... 查看详情

性能优化之jvm高级特性(代码片段)

1、JVM体系结构线程共享内存可以被所有线程共享的区域,包括堆区、方法区、运行时常量池。1.1堆(Heap)大多数时候,Java堆是Java虚拟机管理的内存里最大的一块,所有的对象实例和数组都要在堆上分配内存空间,Java对象可以... 查看详情

高性能的javascript代码,优化技巧分享

...,已经广泛应用于Web应用开发中。为了提高Web应用的性能,从JavaScript的性能优化方向入手,会是一个很好的选择。本文从加载、上下文、解析、编译、执行和捆绑等多个方面来讲解JavaScript的性能优化技巧,以便... 查看详情

优化 ResultSet 获取性能(Apache Spring、MySQL)

】优化ResultSet获取性能(ApacheSpring、MySQL)【英文标题】:OptimizingResultSetfetchperformance(ApacheSpring,MySQL)【发布时间】:2012-05-0816:41:39【问题描述】:我的问题是:我试图通过来自MySQL的JDBCTemplate在Spring中处理大约150万行数据。有这... 查看详情

efcore性能优化(代码片段)

...ng.Tasks;usingCore.Web.Models;usingMicrosoft.AspNetCore.Mvc;usingMicrosoft.EntityFrameworkCore;namespaceCore.Web.ControllerspublicclassRegionController:ControllerprivatereadonlyDataContext_dataContext;publicRegionController(DataContextdataContext)_dataContext=dataContext;//非跟踪查询publicasync... 查看详情

深入jvm内核---jvm性能优化

持久代用来防止类、类的一些常量操作   1.类和接口的全限定名   2、字段的名称和描述符   3、方法和名称和描述符   两个原则   1.一个是将转移到老年代的对象数量降到最少   因为老年代空间上的GC处理会花费更多... 查看详情

深入解析g1垃圾收集器与性能优化(代码片段)

本文详细介绍G1垃圾收集器的参数配置,如何进行性能调优,以及怎样对GC性能进行分析和评估。文章目录0.G1简介1.垃圾回收阶段简介2.纯年轻代模式的垃圾收集3.混合模式的垃圾收集4.标记周期的各个阶段5.常用参数与默认值 ... 查看详情

jvm年轻代

...情么?其实不分代完全可以,分代的唯一理由就是优化GC性能。你先想想,如果没有分代,那我们所有的对象都在一块,GC的时候我们要找到哪些对象没用,这样就会对堆的所有区域进行扫描。而我们的很多对象都是朝生夕死的... 查看详情

.net性能调优-垃圾回收!!!最全垃圾回收来了(代码片段)

...容来提供内存安全。托管堆代数概述为优化垃圾回收器的性能,将托管堆分为三代:第0代、第1代和第2代。目的是为了单独处理短生存期对象和长生存期对象。垃圾回收器大部分时间都在处理短生存期对象的回收。底层... 查看详情

jvm之年轻代(新生代)老年代永久代以及gc原理详解gc优化

关于JVM,也许你听过这些术语:年轻代(新生代)、老年代、永久代、minorgc(younggc)、majorgc、fullgc不要急,先上图,这是jvm堆内存结构图  仔细的你发现了图中有些分数8/10和1/10,这是默认配置下各个代内存分配比例。举... 查看详情

张高兴的entityframeworkcore即学即用:创建第一个efcore应用(代码片段)

写在前面EntityFrameworkCore(EFCore)是.NET平台流行的对象关系映射(ORM)框架。虽然.NET平台中ORM框架有很多,比如Dapper、NHibernate、PetaPoco等,并且EFCore的性能也不是最优的(这是由于EF的实体跟踪特性,将其禁用后可以大幅提升性能... 查看详情

45.jvm调优策略常见问题:内存泄漏(年老代堆空间被占满持久代被占满堆栈溢出线程堆栈满系统内存被占满)优化方法:优化目标优化gc步骤优化总结;案例分析(公司系统参数网上给的配置参数)(代码片段)

...1.3.堆栈溢出45.1.1.4.线程堆栈满45.1.1.5.系统内存被占满45.2.优化方法45.2.1.优化目标45.2.2.优化GC步骤45.2.3.优化总结45.3.案例分析45.3.1.案例1IntellijIDEA2016优化45.3.2.公司系统参数 查看详情

jvm系统优化实践:分代模型

您好,我是湘王,这是我的51CTO博客,欢迎您来,欢迎您再来~大部分在代码里创建的对象,存活周期都是极短的,只有少数对象是长期存活的,如静态类和静态变量。采用不同方式创建和使用对象,其生存周期也不同。因此,J... 查看详情

#yyds干货盘点#愚公系列2023年02月.net/c#知识点-efcore性能优化之显示编译(代码片段)

...然后通过调用一个委托调用它。1.安装install-packageMicrosoft.EntityFrameworkCore.InMemory2.注入services.AddDbContext<TestDbContext>(options=>options.UseInMemo 查看详情

1代和2代区别很大,那么2代和3代区别大吗

...只指令集,二代由于比一代多出了avx指令集在浮点运算上性能大大提升所以变化较大,还多出了aes在加密运算上也有极大提升。所以一代和二代区别较大,三代只是做了小优化,降低了功耗。最后说cpu里的另一个东西就是核心显... 查看详情

C# 托管代码优化 [关闭]

...消耗大量CPU,但我想知道是否可以优化代码以获得更好的性能。目前解析大约需要一分钟。15页PNG页面。我会降低到大约30-40秒。C++代 查看详情

android性能优化总提纲

Android性能优化(1)–性能优化介绍Android性能优化(2)—启动优化–1(启动优化介绍+启动时间测量)Android性能优化(2)—启动优化—2(方法耗时获取与异步初始化)Android性能优化(3)–内存优化–(内存优化工具、内存管理机制... 查看详情

一个性能较好的jvm参数配置

一个性能较好的web服务器jvm参数配置:-server//服务器模式-Xmx2g//JVM最大允许分配的堆内存,按需分配-Xms2g//JVM初始分配的堆内存,一般和Xmx配置成一样以避免每次gc后JVM重新分配内存。-Xmn256m//年轻代内存大小,整个JVM内存=年轻代+... 查看详情