Developers using Aspose.PDF in Azure App Service encounter OutOfMemoryException even when Azure reports only 40% memory usage. The same code works locally on development machines with ample RAM but crashes in Azure's constrained environments. With over 24,000 views on Stack Overflow, this is a common production issue. This article documents the behavior and examines alternatives that handle Azure's memory constraints more gracefully.
The Problem
Aspose.PDF can throw OutOfMemoryException in Azure App Service while the App Service plan shows significant available memory. This counterintuitive behavior occurs because:
- Azure App Service has per-process memory limits that differ from total plan memory
- Aspose.PDF's memory allocation patterns can fragment the managed heap
- .NET's garbage collector behavior differs between development and production environments
- Azure's memory metrics don't reflect native memory usage accurately
The result is production failures that cannot be reproduced locally.
Error Messages and Symptoms
System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.
at Aspose.Pdf.Document..ctor(Stream input)
at YourApp.Controllers.PdfController.Generate()
From developer reports:
OutOfMemoryException even though Azure Service Plan reached only 40% of memory used.
Symptoms include:
- Application works with 4GB RAM locally but fails with 4GB in Azure
- Failures are intermittent and hard to reproduce
- Scaling up the App Service plan may not resolve the issue
- Memory appears available but allocations fail
Who Is Affected
This issue impacts Azure deployments using Aspose.PDF:
Platforms: Azure App Service (all tiers), Azure Functions, Azure Container Instances.
Framework Versions: .NET Framework 4.x and .NET Core/.NET 5+ deployments.
Use Cases: PDF generation, PDF merging, PDF-to-image conversion, any operation that allocates significant memory.
Evidence from the Developer Community
Timeline
| Date | Event | Source |
|---|---|---|
| 2019-12-13 | Azure OutOfMemoryException reported on Stack Overflow | Stack Overflow |
| 2020-03-15 | Multiple answers confirm similar experiences | Stack Overflow |
| 2022-10-24 | Issue continues with 24K+ views | Stack Overflow |
Community Reports
"I hit the OutOfMemoryException on the using statement even though Azure Service Plan reached only 40% of memory used."
— Developer, Stack Overflow, December 2019"The same code that works fine locally with 8GB RAM fails in Azure with a Premium P2 plan."
— Developer, Stack Overflow, February 2020
Azure App Service Tier Memory Analysis
Understanding how Azure App Service allocates memory is critical for diagnosing these issues:
Memory Limits by Tier
| App Service Tier | Total Memory | Per-Process Limit | Recommended For |
|---|---|---|---|
| Basic B1 | 1.75 GB | ~1.0 GB | Development only |
| Basic B2 | 3.5 GB | ~1.8 GB | Light PDF workloads |
| Basic B3 | 7 GB | ~3.5 GB | Moderate workloads |
| Standard S1 | 1.75 GB | ~1.0 GB | Not for PDF generation |
| Standard S2 | 3.5 GB | ~1.8 GB | Light production |
| Standard S3 | 7 GB | ~3.5 GB | Moderate production |
| Premium P1v3 | 8 GB | ~4 GB | PDF-heavy workloads |
| Premium P2v3 | 16 GB | ~8 GB | Large document processing |
| Premium P3v3 | 32 GB | ~16 GB | Enterprise batch processing |
Note: Per-process limits are approximate and vary based on Azure's resource allocation. The "40% memory" failure occurs because your process hits its per-process limit while the overall tier memory appears available.
Azure Functions Memory Considerations
Azure Functions imposes additional constraints:
Consumption Plan:
- Maximum memory: 1.5 GB
- Timeout: 10 minutes (maximum)
- Not suitable for PDF generation with Aspose
Premium Plan (EP1-EP3):
- EP1: 3.5 GB memory
- EP2: 7 GB memory
- EP3: 14 GB memory
- Better suited for PDF workloads but still hits per-instance limits
Dedicated Plan:
- Uses App Service pricing
- Same per-process limits apply
Azure Container Apps
For containerized deployments, Azure Container Apps provides different constraints:
# container-app.yaml
properties:
configuration:
activeRevisionsMode: Single
template:
containers:
- name: pdf-service
image: your-registry.azurecr.io/pdf-service:latest
resources:
cpu: 2.0
memory: 4Gi # Container memory limit
env:
- name: DOTNET_GC_SERVER
value: "1"
Container Apps allow more granular memory control but Aspose.PDF's allocation patterns still cause fragmentation issues within the container's memory space.
Root Cause Analysis
The issue stems from how memory works in Azure App Service:
Per-Process Limits: Azure App Service imposes per-process memory limits that may be lower than the total plan memory. A 4GB plan might only allow 1.5GB per process.
Memory Fragmentation: Aspose.PDF allocates and deallocates memory in patterns that can fragment the managed heap. Large contiguous allocations may fail even with "available" memory.
Native Memory: Aspose.PDF uses native components whose memory usage is not reflected in .NET memory metrics. Azure's memory reporting may not capture this accurately.
Garbage Collection Mode: Azure uses Server GC by default, which behaves differently from Workstation GC used in development. Memory may not be reclaimed as aggressively.
Diagnosing with Azure Monitor and Application Insights
To identify when your application is hitting memory limits, configure Azure monitoring:
Application Insights Setup
Add the Application Insights SDK to track memory metrics:
// Program.cs
using Microsoft.ApplicationInsights.Extensibility;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplicationInsightsTelemetry();
// Custom memory tracking
builder.Services.AddSingleton<ITelemetryInitializer, MemoryMetricInitializer>();
public class MemoryMetricInitializer : ITelemetryInitializer
{
public void Initialize(ITelemetry telemetry)
{
var process = Process.GetCurrentProcess();
telemetry.Context.GlobalProperties["WorkingSetMB"] =
(process.WorkingSet64 / 1024 / 1024).ToString();
telemetry.Context.GlobalProperties["PrivateMemoryMB"] =
(process.PrivateMemorySize64 / 1024 / 1024).ToString();
}
}
Azure Monitor Alerts
Create alerts for memory conditions:
{
"alertRule": {
"name": "HighMemoryUsage",
"condition": {
"allOf": [
{
"metricName": "MemoryWorkingSet",
"operator": "GreaterThan",
"threshold": 1500000000,
"timeAggregation": "Average"
}
]
},
"actions": {
"actionGroups": ["your-action-group-id"]
}
}
}
Kusto Query for Memory Analysis
Use this query in Azure Log Analytics to identify memory patterns:
requests
| where timestamp > ago(24h)
| where name contains "pdf" or name contains "document"
| extend memoryMB = toint(customDimensions["WorkingSetMB"])
| summarize avgMemory = avg(memoryMB), maxMemory = max(memoryMB),
failureRate = countif(success == false) * 100.0 / count()
by bin(timestamp, 5m)
| order by timestamp desc
Attempted Workarounds
Workaround 1: Scale Up the App Service Plan
Approach: Move to a higher tier App Service plan with more memory.
Basic B2 (3.5GB) → Standard S3 (7GB) → Premium P3 (14GB)
Limitations:
- Increases costs significantly
- Per-process limits may still apply
- Does not address root cause
- Memory usage grows with document complexity
Workaround 2: Use 64-bit App Service
Approach: Ensure the App Service is configured for 64-bit.
Configuration → General settings → Platform: 64-bit
Limitations:
- Only helps if 32-bit was the constraint
- Does not address per-process limits
- May not resolve fragmentation issues
Workaround 3: Force Garbage Collection
Approach: Manually trigger garbage collection after PDF operations.
using (var doc = new Aspose.Pdf.Document(stream))
{
// Process document
}
GC.Collect();
GC.WaitForPendingFinalizers();
Limitations:
- Performance overhead
- Does not reclaim native memory
- Not recommended as a standard practice
A Different Approach: IronPDF
IronPDF handles memory differently by using a Chromium subprocess for rendering, isolating memory usage from the main application process.
Why IronPDF Works Better in Azure
IronPDF's subprocess architecture provides benefits in constrained environments:
- Process Isolation: Chromium's memory is in a separate process, not counted against the main app's limits
- Clean Termination: Subprocess memory is fully released after each operation
- No Fragmentation: Each render gets fresh memory space
- Predictable Usage: Memory consumption is consistent between development and production
Code Example
using IronPdf;
public class AzurePdfService
{
public byte[] GeneratePdf(string html)
{
// Works consistently in Azure App Service
var renderer = new ChromePdfRenderer();
// Memory used by Chromium is isolated from the app process
using var pdf = renderer.RenderHtmlAsPdf(html);
return pdf.BinaryData;
// Chromium subprocess memory is released here
}
public byte[] GenerateInvoice(InvoiceData invoice)
{
var renderer = new ChromePdfRenderer();
renderer.RenderingOptions.MarginTop = 20;
renderer.RenderingOptions.MarginBottom = 20;
string html = $@"
<!DOCTYPE html>
<html>
<head>
<style>
body {{ font-family: Arial, sans-serif; }}
.invoice-header {{ background: #0078d4; color: white; padding: 20px; }}
table {{ width: 100%; border-collapse: collapse; }}
th, td {{ padding: 10px; border-bottom: 1px solid #ddd; }}
.total {{ font-size: 1.5em; font-weight: bold; text-align: right; }}
</style>
</head>
<body>
<div class='invoice-header'>
<h1>Invoice #{invoice.Number}</h1>
</div>
<table>
<thead>
<tr><th>Description</th><th>Amount</th></tr>
</thead>
<tbody>
{GenerateLineItems(invoice.Items)}
</tbody>
</table>
<div class='total'>Total: ${invoice.Total:F2}</div>
</body>
</html>";
using var pdf = renderer.RenderHtmlAsPdf(html);
return pdf.BinaryData;
}
private string GenerateLineItems(IEnumerable<LineItem> items)
{
return string.Join("", items.Select(i =>
$"<tr><td>{i.Description}</td><td>${i.Amount:F2}</td></tr>"));
}
}
Azure App Service configuration:
// No special memory configuration needed
// Works on Basic B1 tier for most use cases
Key points about this code:
- Memory behavior is consistent between local and Azure
- Chromium subprocess handles heavy memory operations
- No per-process limit issues for the main application
- Works on smaller App Service tiers
API Reference
For Azure deployment:
Migration Considerations
Licensing
- IronPDF is commercial software with perpetual licensing
- Free trial available for evaluation
- Licensing information
API Differences
- Aspose.PDF: Object-oriented API with Document, Page objects
- IronPDF: HTML-first rendering approach
- Migration involves changing document generation from Aspose objects to HTML templates
What You Gain
- Predictable memory behavior in Azure
- Works on smaller App Service tiers
- No mysterious OutOfMemoryExceptions at 40% usage
What to Consider
- Different document generation paradigm
- Chromium binaries increase deployment size
- Commercial licensing required
Conclusion
Aspose.PDF's memory usage patterns can cause OutOfMemoryException in Azure App Service even when Azure reports available memory. The issue stems from per-process limits, memory fragmentation, and native memory that isn't tracked accurately. For Azure deployments where memory predictability is important, subprocess-based rendering approaches provide more consistent behavior.
Written by Jacob Mellor, who leads technical development at Iron Software.
References
- Stack Overflow: System.OutOfMemoryException in Azure but not locally{:rel="nofollow"} - 24K+ views
For the latest IronPDF documentation and tutorials, visit ironpdf.com.
Top comments (0)