Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 36 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
36
Dung lượng
579,92 KB
Nội dung
Memory Management Normally, developers take memory management for granted, and rightly so. In most cases, the .NET memory-management system handles all the details about creating and destroying objects so that you can concentrate on your application and not bookkeeping details. However, there are times when it’s useful to know a bit about how memory is managed in the application. For certain types of applications, a few changes to the way you handle objects can make a big difference in overall performance. This chapter explains how memory management works in Visual Basic. It explains some tech- niques you can use to minimize impact on the memory-management system, and it tells how to make your memory use more efficient. Before you can learn about specific techniques for managing memory, however, you must under- stand a little about how the memory system works. In particular, you must know a bit about the garbage collector. Garbage Collection Visual Basic .NET uses a garbage-collection memory-management scheme. As a program creates objects, the system takes unused memory from the managed heap. When the program no longer has any variables referring to an object, the object is freed and its memory is potentially available for reuse. However, the garbage collector doesn’t yet know that the object has been freed. Over time, the program creates new objects and frees others. Eventually, the managed heap runs out of free memory. At that point, the garbage collector runs. 30_053416 ch23.qxd 1/2/07 6:37 PM Page 585 Actually the garbage collector can execute whenever it thinks it will be profitable to do so. That may be when the managed heap is really empty, or it may be when the garbage collector thinks enough objects have been freed that it would be a good idea to clean things up a bit. Generally, you should assume that the garbage collector might run at any time and not worry about exactly when it runs. The garbage collector now performs garbage collection to free up memory that was used, but is no longer needed by the program. The garbage collector manages memory in multiple pieces called generations. You can think of these as multiple piles of trash with different distances from the door. The generations closest to the door are eas- ier to recycle than those farthest away. Initially, the garbage collector sifts through the first pile of trash, called generation 0 (or Gen 0 or gen0) rel- atively frequently. When it can no longer find much usable space in generation 0, the garbage collector recycles generation 1. When there isn’t much unused memory available in generations 0 or 1, it collects generation 2. Again, this is a bit simplified. The garbage collector can recycle any generation whenever it thinks that may be profitable. In general, though, generation 0 collection happens relatively frequently, generation 1 collection happens less often, and generation 2 collection happens rarely. When it recycles generation 0, the garbage collector finds the objects that are still in use and moves them into a contiguous area in generation 1 so that they are collected less frequently in the future. It then releases the unused memory in generation 0 for future use. Eventually, if an object lives long enough, the garbage collector promotes it from generation 1 to genera- tion 2. Objects in generation 2 participate in garbage collections only rarely, so these objects will occupy memory for a fairly long time. Note that the garbage collector need not always use three generations. Currently, Visual Basic .NET uses three garbage generations, but future garbage collectors might use different strategies. If you really need to know, use GC.MaxGeneration to find out how many generations are available. This is described in more detail later in this chapter. Ideally, generation 0 contains short-lived objects that are allocated and released fairly quickly, generation 1 contains longer-lived objects that survive generation 0 garbage collection, and generation 2 contains very long-lived objects that the program uses for a long time. The garbage collector is optimized to make generation 0 collection fast, generation 1 collection slower, and generation 2 collection slowest of all. So, it’s important that generation 0 collection occurs most often, generation 1 collection occurs less often, and generation 2 collection occurs only rarely. For a good article that provides additional detail about how the garbage collector works, see msdn .microsoft.com/msdnmag/issues/1100/gci . 586 Part IV: Specific Techniques 30_053416 ch23.qxd 1/2/07 6:37 PM Page 586 Finalization Some objects refer to other objects that are not controlled by managed .NET code. Because those objects are not referred to by normal references, the garbage collector cannot clean them up automatically as it can other objects. For example, a Pen object uses graphics resources that are not managed by .NET. The garbage collector doesn’t understand those resources, so it cannot manage them as it does managed objects. To free its unmanaged resources, the Pen class provides a Finalize method. When the program instan- tiates an object that has a Finalize method, the garbage collector adds the object to a finalization queue. Later, when the garbage collector discovers that the object is available for collection, it cannot destroy the object because it has not yet been finalized. Instead, the garbage collector removes the object from the finalization queue and moves it to a list of objects that are ready for finalization. The garbage collector then calls the Finalize methods for the objects in the ready list, and removes them from that list. The next time the garbage collector runs, it will again find that these objects are inaccessible to the pro- gram. This time, they are not in the finalization queue, so it can reclaim them. It takes two garbage col- lections before these objects can be destroyed and their memory made available for reuse. Example program FinalizedClass (available for download at www.vb-helper.com/one_on_ one.htm ) uses the following code to demonstrate finalization: Public Class Form1 Private Class Big Private BigArray(100000) As Integer Protected Overrides Sub Finalize() Debug.WriteLine(“Finalize”) End Sub End Class Private m_HasFinalize As Big ‘ Alloctae an object. Private Sub btnAllocate_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnAllocate.Click m_HasFinalize = New Big Debug.WriteLine(“Allocate: Memory Allocated: “ & GC.GetTotalMemory(False)) End Sub ‘ Release the object. Private Sub btnFree_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnFree.Click Debug.WriteLine(“Freeing object”) m_HasFinalize = Nothing Debug.WriteLine(“Free: Memory Allocated: “ & GC.GetTotalMemory(False)) End Sub ‘ Force garbage collection. 587 Chapter 23: Memory Management 30_053416 ch23.qxd 1/2/07 6:37 PM Page 587 Private Sub btnCollect_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnCollect.Click GC.Collect() Debug.WriteLine(“Collect: Memory Allocated: “ & GC.GetTotalMemory(False)) End Sub End Class The Big class allocates a large chunk of memory and provides a Finalize method that displays a mes- sage when the object is finalized. When you click the Allocate button, the program makes a new Big object. It then uses the GC class’s GetTotalMemory method to display the estimated amount of memory currently allocated. When you click the Free button, the program sets its reference to the Big object to Nothing. At this point the object is a candidate for garbage collection, but garbage collection does not actually occur. When you click the Collect button, the program calls the GC class’s Collect method to force garbage collection in all generations. The first time you click this button, the garbage collector removes the object from the finalization list and calls its Finalize method. The second time you click this button, the garbage collector reclaims the object’s memory. The following text shows the output of one test: Allocate: Memory Allocated: 1018164 Freeing object Free: Memory Allocated: 1059124 Finalize Collect: Memory Allocated: 936168 Collect: Memory Allocated: 503456 Collect: Memory Allocated: 503444 Collect: Memory Allocated: 503444 When I clicked the Allocate button, the program created the new object and reported roughly 1 million allocated bytes of memory. When I clicked the Free button, the program freed its reference to the object and reported slightly more memory allocated. Next, I clicked the Collect button. The garbage collector called the object’s Finalize method and the program reported that about 936,168 bytes were still allocated. Some memory was reclaimed, but the object’s memory is still not completely available. When I clicked the Collect button again, the garbage collector finally freed the object and the program reported much less memory allocated. In fact, the program reports approximately 400,000 fewer bytes allocated and the Big object occupies roughly 400,000 bytes (100,000 times 4 bytes per Integer in the array). It is only during this second garbage collection that the object is truly free. 588 Part IV: Specific Techniques 30_053416 ch23.qxd 1/2/07 6:37 PM Page 588 Disposing Resources An object that has a Finalize method is only completely freed after two garbage-collection passes. The first pass calls the object’s Finalize method to release unmanaged resources. The second pass actually releases the object’s memory. Not only does this mean that the garbage collector must run twice to clean up these objects, but it also means the program has little control over when the objects’ Finalize methods are eventually called. Your program generally doesn’t have much control over when the garbage collector runs, so there’s no obvious way for you to know when the Finalize method executes. If you need Finalize to run quickly, that can be a problem. For example, suppose a class writes data into a file and then closes the file in its Finalize method. Because you don’t know when that will occur, you cannot rely on the file being closed later. Because you don’t know when a Finalize method will be called, this garbage-collection strategy is called non-deterministic finalization. You can make garbage collection more efficient and more predictable if you dispose of the object’s resources without waiting for garbage collection. You know immediately that the object’s resources are free and the next garbage collection will reclaim the object completely without requiring a second pass. You can free a standard .NET Framework object’s unmanaged resources by calling its Dispose method. The Dispose method frees the object’s unmanaged resources and then calls the GC class’s SuppressFinalize routine to tell the garbage collector that it no longer needs to call the object’s Finalize method. Calling Dispose is particularly important for small objects such as Pens and Brushes that an applica- tion may create and destroy frequently. For example, a graphics program might create a new Pen and Brush to draw each of the items it is displaying every time it paints its drawing surface. Although these objects are relatively small, they can quickly use up a lot of memory if the program draws a lot of items. They also tie up graphics resources. The Using keyword makes disposing of these kinds of objects automatic. If you declare the object in a Using statement, Visual Basic automatically calls the object’s Dispose method when it exits the Using block. The following code shows how example program UsingPen (available for download at www.vb-helper .com/one_on_one.htm ) draws an ellipse. It defines a Pen object named red_pen in a Using statement. Then it reaches the End Using statement, and the program automatically calls the Pen’s Dispose method. Private Sub Form1_Paint(ByVal sender As Object, _ ByVal e As System.Windows.Forms.PaintEventArgs) Handles Me.Paint Using red_pen As New Pen(Color.Red, 5) Dim rect As New Rectangle(10, 10, _ Me.ClientSize.Width - 20, Me.ClientSize.Height - 20) e.Graphics.DrawEllipse(red_pen, rect) End Using End Sub 589 Chapter 23: Memory Management 30_053416 ch23.qxd 1/2/07 6:37 PM Page 589 A Using block always calls the object’s Dispose method whether the program exits the block by using an Exit or Return statement, by throwing an exception, or by some other method. Disposing Custom Classes If you build a class that uses unmanaged resources, you should make the class implement the IDisposable interface so that it can properly handle finalization and the Dispose method. If you start a new class, type Implements IDisposable, and press Enter, Visual Basic fills in some of the code you need to build a disposable class for you. Unfortunately, in the version I’m running, at least, the code is incomplete (it doesn’t include a Finalize method) and misleading (some of the comments are wrong). The following code shows a more complete and correct version: Public Class Big Implements IDisposable ‘ Indicates whether we have already been disposed. Private m_WasDisposed As Boolean = False ‘ Dispose of resources. Protected Overridable Sub Dispose(ByVal disposing As Boolean) If Not m_WasDisposed Then If disposing Then ‘ Dispose was called explicitly. ‘ Free managed resources. ‘ TODO: Free the managed resources. End If ‘ Free unmanaged resources. ‘ TODO: Free the unmanaged resources. End If m_WasDisposed = True End Sub ‘ Dispose of resources explicitly. ‘ Do not change this code. Make changes ‘ to the other version of Dispose. Public Sub Dispose() Implements IDisposable.Dispose Dispose(True) GC.SuppressFinalize(Me) End Sub ‘ Finalize the object. Protected Overrides Sub Finalize() ‘ Call Dispose passing False to indicate that ‘ we are not running from a program call to Dispose(). Dispose(False) End Sub End Class 590 Part IV: Specific Techniques 30_053416 ch23.qxd 1/2/07 6:37 PM Page 590 The m_WasDisposed variable remembers whether the object’s resources have already been disposed. The first version of the Dispose subroutine uses this value to only dispose of its resources once. If the main program accidentally calls the object’s Dispose method twice, the code does nothing during the second call. The first version of Dispose takes a parameter named disposing that tells it whether the routine is being called explicitly or implicitly by the Finalize method. If disposing is True, then Dispose releases all of the object’s managed and unmanaged resources. If disposing is False, then Dispose releases only its managed resources. At first, this may seem strange. Why shouldn’t the code dispose of managed resources when it is being called from Finalize? The reason is that it is not safe to refer to objects that are being finalized. There’s no way to determine the order in which objects will be finalized, and the program will throw an excep- tion if it tries to refer to another object that has already been finalized. How could this happen? Suppose you have a group of several objects that refer to each other. If no other variable in your program refers to any of those objects, the garbage collector will not mark them as in use and will collect them all. It’s anybody’s guess which objects are collected first, so they must not try to refer to each other during finalization. In many cases, non-managed resources use handles to operating system objects that are not represented by.NET Framework objects. Often, you can use the SafeHandle class and its descendants to manage these resources safely without implementing IDisposable yourself. For example, the SafeFileHandle class providers a wrapper for file handles. It provides IsClosed and IsInvalid methods to indicate whether a handle is open and valid. Its Close and Dispose methods close the object’s file handle. Before you start writing your own class to deal with an unmanaged handle, check the SafeHandle class and its descendants to see if one of them already meets your needs. Pre-allocating Objects Normally, your programs should ignore garbage collection and let the garbage collector do its job with- out interference. Occasionally, you may be able to improve performance if you know something special about how the application works. For example, suppose you know that your program is about to create several thousand objects, but it will not need them all at the same time. Suppose the program is about to perform 10,000 calculations during each of which it will allocate around 100 objects. If you let the garbage collector handle allocation and deallocation as usual, the program will create and destroy around 1 million objects (10,000 calcula- tions times 100 objects). Depending on the size of the objects, that may lead to several rounds of garbage collection. Instead of following this “use it and lose it” strategy, suppose the program pre-allocates 100 objects and reuses them as necessary. In that case, the program only creates and destroys 100 objects, so it is unlikely that it will need to perform garbage collection even once. 591 Chapter 23: Memory Management 30_053416 ch23.qxd 1/2/07 6:37 PM Page 591 Example program Preallocate (available for download at www.vb-helper.com/one_on_one.htm) compares these two strategies. When you click the Run button, the program performs a large number of trials where it builds a linked list of 100 Cell objects. The following code shows the Cell class. The MyForm variable refers to the program’s main form. Values is an array of integers that just uses up some memory. NextCell is a reference to the next cell in the current list. Public Class Cell Public MyForm As Form1 Public Values(100) As Integer Public NextCell As Cell Public Sub New(ByVal new_MyForm As Form1) MyForm = new_MyForm End Sub Protected Overrides Sub Finalize() If MyForm.GCRunning Then Exit Sub Debug.WriteLine(“Garbage collecting “ & MyForm.NumGCs & “ ”) MyForm.GCRunning = True MyForm.NumGCs += 1 End Sub End Class The Cell class’s Finalize method checks the form’s GCRunning variable and exits if the value is True. Otherwise, the method prints a message indicating that garbage collection is occurring, sets the form’s GCRunning variable to True, and increments the garbage-collection count. The following code shows how program Preallocate responds when you click the Run button: Public GCRunning As Boolean = False Public NumGCs As Integer = 0 Private Sub btnRun_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnRun.Click Dim start_time As Date = Now NumGCs = 0 Dim num_trials As Long = Long.Parse(txtNumTrials.Text) For i As Long = 1 To num_trials ‘ Allocate a bunch of objects. Dim top As New Cell(Me) top.NextCell = Nothing For j As Integer = 1 To 99 Dim new_cell As New Cell(Me) new_cell.NextCell = top top = new_cell Next j ‘ Free the objects. 592 Part IV: Specific Techniques 30_053416 ch23.qxd 1/2/07 6:37 PM Page 592 top = Nothing GCRunning = False Next i ‘ Force a final GC. GC.Collect() Debug.WriteLine(“Done”) Dim stop_time As Date = Now Dim elapsed_time As TimeSpan = stop_time.Subtract(start_time) Debug.WriteLine(“Elapsed time: “ & elapsed_time.TotalSeconds.ToString(“0.00”)) End Sub After recording its start time, the program loops through a number of trials. For each trial, it builds a linked list of 100 Cell objects. It then sets the topmost object to Nothing, so the program no longer has a reference to the objects and they are candidates for garbage collection. If garbage collection occurs, any cells that are available for garbage collection execute their Finalize methods. The first of those objects displays a message and sets the form’s GCRunning variable to True. After collection is complete, the program resets GCRunning to False so Cells that are finalized later display a new message. The routine finishes by displaying the elapsed time. The following code runs when you click the program’s Preallocate & Run button: Private Sub btnPreallocateRun_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnPreallocateRun.Click Dim start_time As Date = Now NumGCs = 0 ‘ Preallocate some Cell objects. Dim available(100) As Cell available(available.Length - 1) = New Cell(Me) available(available.Length - 1).NextCell = Nothing For i As Integer = available.Length - 2 To 0 Step -1 available(i) = New Cell(Me) available(i).NextCell = available(i + 1) Next i Dim top_available As Cell = available(0) ‘ Perform the trials. Dim num_trials As Long = Long.Parse(txtNumTrials.Text) For i As Long = 1 To num_trials ‘ Allocate a bunch of objects. Dim top As New Cell(Me) top.NextCell = Nothing For j As Integer = 1 To 99 Dim new_cell As Cell = top_available top_available = top_available.NextCell new_cell.NextCell = top 593 Chapter 23: Memory Management 30_053416 ch23.qxd 1/2/07 6:37 PM Page 593 top = new_cell Next j ‘ Free the objects. Dim last_cell As Cell = top_available Do While last_cell.NextCell IsNot Nothing last_cell = last_cell.NextCell Loop last_cell.NextCell = top top = Nothing GCRunning = False Next i ‘ Deallocate the objects. top_available = Nothing Erase available ‘ Force a final GC. GC.Collect() Dim stop_time As Date = Now Dim elapsed_time As TimeSpan = stop_time.Subtract(start_time) Debug.WriteLine(“Elapsed time: “ & elapsed_time.TotalSeconds.ToString(“0.00”)) End Sub This code starts by pre-allocating an array of 101 Cell objects. It initializes the objects so that they form a linked list and sets top_available to refer to the first object in the array. The program then performs its trials much as before. This time, however, it doesn’t create new Cell objects from scratch. Instead, when it needs a new Cell object, it takes one from the top of the available list. When it finishes a trial, the program places the Cells it has used back in the available array for later reuse. After it finishes its trials, the program sets top_available to Nothing and erases the available array so the program has no references to the Cell objects. It forces a final garbage collection and displays the elapsed time. In one test, letting the garbage collector run without interference required eight garbage collections and took 5.28 seconds. Pre-allocating the Cell objects required a single garbage collection at the end and took 1.17 seconds. This example demonstrates a very special case. Usually, the garbage collector does just fine by itself, but if you know that the program must repeatedly allocate and deallocate many objects while keeping only a few alive at one time, then you may be able to improve performance by pre-allocating the objects. There is a danger to using this method, however. If garbage collection runs while the pre-allocated objects are in use, they will be promoted to generation 1. That will mean they will not be candidates for future garbage collection until the garbage collector performs generation 1 garbage collection, which could be quite awhile later. Of course, it’s entirely possible that a lot of the temporary objects would be in use if you leave the garbage collector alone, so it’s not certain that pre-allocating objects is worse. 594 Part IV: Specific Techniques 30_053416 ch23.qxd 1/2/07 6:37 PM Page 594 [...]... Database Programming (Stephens), 134 Visual Basic Design Patterns (Grand), 201 Visual Basic Graphics Programming: Hands-On Applications and Advanced Color Development (Stephens), 496 Visual Basic NET and XML (Stephens), 157 Visual Source Safe (VSS) revision control system, 362–363 Visual Studio (Microsoft) taking advantage of, 400–401 testing tools, 470–471 VSS (Visual Source Safe) revision control... interface, 172 Design Patterns (Gamma), 163, 201 606 Designer, 334 Designer attribute, 356 design- time features, implementing, 303 support, XML comments, 350 developer documentation described, 369 high-level design, 369 inline comments, 371–373 module-level documentation, 370–371 specification, 369 system-level documentation, 369–370 developers layers separating database, 75 number of Visual Basic, 12 development. .. user types, 478–479 ClickOnce, 482–483 up-front design, 397–398 upgrading from Visual Basic 6 to Visual Basic NET, 8 use case diagrams, UML, 80–82, 91 performance requirements, 20 user documentation, 365 user acceptance testing, 30 control philosophy, UI design, 98, 393–394 environment, understanding, 102 job, understanding, 101 manual, 365 number of Visual Basic, 12 resistance to projects, 17 respecting,... 319 ScribbleControlEditor class, 319–320 ScribbleControlEditorBasicsPage class, 320–321 scripting cautions about use of, 237 238 , 239 –241 design consequences, 239 expressions, evaluating, 259–261 other queries, 238 239 SQL attacks, 240 executing queries, 241–244 generating queries, 244–247 knowledge of, 238 running commands, 247–250 Visual Basic code described, 251 object model, exposing, 254–257 running,... pre-allocating, 591–595 resources, disposing, 589–590 weak references, 595–597 reflection as part of, 559 upgrading from Visual Basic 6 to, 8 619 Index 31_053416 bindex.qxd 31_053416 bindex.qxd 1/2/07 6:37 PM Page 620 VBA (Visual Basic for Applications) VBA (Visual Basic for Applications), 11 VBScript, 11 verbosity C++, C#, 9 VB, 13 versions, operating system, 31 Visual Basic See VB; VB NET Visual Basic. .. platform dependencies, 8 pointers, awkwardness of using, 6 power and flexibility, 9 prototyping and simple applications, 10 scripting described, 251 object model, exposing, 254–257 running, 251–254 VBScript, 11 self-documenting, 10 syntactic annoyances, 6–7 talent pool, 12 upgrading from Visual Basic 6 to Visual Basic NET, 8 verbosity, 9 VB (Visual Basic) NET application dependencies, 8 memory management custom... 85–86, 91 state chart diagrams, 87–89 tools, 92–95 use case diagrams, 80–82, 91 unary operator (-) , 467 undo and redo, user-interface (UI) design principles, 123 127 unfolding dialog, 105 1/2/07 6:37 PM Page 619 VB (Visual Basic) NET UnhandledException event handler, 439, 448–450 unit testing relatively self-contained pieces of code, 457–458 routines, 26–27 Universal Modeling Language See UML unnamed... 87–89 state, preserving in UI design, 122 status, Bug Hunter program, 73 Stephens, Rod Visual Basic Database Programming, 134 Visual Basic Graphics Programming: Hands-On Applications and Advanced Color Development, 496 Visual Basic NET and XML, 157 storage problems, waterfall model and, 36 strategy, classes representing, 199–200 strings, 6 strong name key file, 141 structure, class versus, 72 Structured... 45–46 described, 40–41 iterative prototyping, 41–43 staged delivery, 43–45 throwaway prototyping, 41 stages high-level design, 23 high-level feasibility analysis, 21–22 idea formulation and refinement, 16–17 implementation, 25–26 lower-level design, 24–25 low-level feasibility analysis, 22 23 requirements gathering, 19–21 support, 31–33 team building, 17–19 testing described, 26 regression testing,... databases or object store data storage design, 160–161 memory management, pre-allocating, 591–595 model, exposing while scripting Visual Basic, 254–257 object, characteristic of See property object view, application, 76 object-oriented design bug hunter example, 69–70 candidate classes, picking, 70–71 candidates, converting to classes, 71–74 database classes, adding, 75 design views, studying different, . 563–564 add-ins code, adding, 227 230 creating, 222–226 DelegationAddIn project, 231 236 described, 222 improving, 230 231 advise, don’t act philosophy, 99–100 Agile and Iterative Development. changes to the way you handle objects can make a big difference in overall performance. This chapter explains how memory management works in Visual Basic. It explains some tech- niques you can use. Rectangle(10, 10, _ Me.ClientSize.Width - 20, Me.ClientSize.Height - 20) e.Graphics.DrawEllipse(red_pen, rect) End Using End Sub 589 Chapter 23: Memory Management 30_053416 ch23.qxd 1/2/07 6:37 PM Page 589 A