sách gpt4 ăn đã đi

Hãy nhớ toàn bộ quá trình sử dụng gdb để chẩn đoán các vấn đề về gc

In lại Tác giả: Tôi là chú chim nhỏ Thời gian cập nhật: 2023-02-04 22:31:15 26 4
mua khóa gpt4 giày nike

Bản gốc: Nhật ký mã hóa (ID tài khoản công khai WeChat: codelogs), hoan nghênh chia sẻ, vui lòng giữ lại nguồn để in lại.

Giới thiệu

Sau khi giải quyết vấn đề tiêu tốn thời gian GC lâu lần trước, hệ thống đã trở nên ổn định hơn rất nhiều. Đây là bài viết trước "Mức tiêu thụ thời gian GC cao, nguyên nhân thực sự là do lưu lượng dịch vụ nhỏ?" 》 Tuy nhiên, sau một thời gian, khi kiểm tra nhật ký GC, tôi phát hiện ra một vấn đề khác về GC, như sau: Từ bức ảnh này, chúng ta có thể thấy rằng GC của chúng ta có một số đột biến và đôi khi có sự phân bổ bộ nhớ lớn đột ngột.

Kiểm tra nhật ký GC và thấy rằng có các bản ghi phân bổ đối tượng lớn, như sau:

                        
                          $ grep 'phân bổ khổng lồ đồng thời' gc.log | awk 'match($0,/yêu cầu phân bổ: (\w+) byte/,a){in a[1]}' |sort -nr 1941835784 1889656848

                        
                      

Có thể thấy rằng kích thước phân bổ của một phân bổ đối tượng lớn là 1,9G. Ai có thể cưỡng lại được điều này! .

async-profiler nhắm mục tiêu phân bổ đối tượng lớn

Như đã đề cập trong bài viết đã đề cập ở trên, sử dụng async-profiler có thể dễ dàng xác định vị trí call stack của phân bổ đối tượng lớn. Phương pháp này như sau:

                        
                          ./profiler.sh bắt đầu --all-user -e G1CollectedHeap::humongous_obj_allocate -f ./humongous.jfr jps

                        
                      

Sau đó sử dụng jmc để mở tệp humongous.jfr. Ngăn xếp cuộc gọi như sau:

Đây là một hoạt động khử lưu huỳnh tiết kiệm và gọi phương thức TCompactProtocol.readDouble. Mã phương thức như sau: Tuy nhiên, chỉ có một mảng 8 byte được tạo ở đây. Không thể phân bổ 1,9G bộ nhớ. .

Sau một số hiểu biết, điều này là do async-profiler nhận được ngăn xếp cuộc gọi thông qua AsyncGetCallTrace và ngăn xếp mà AsyncGetCallTrace thu được đôi khi không chính xác. Cộng đồng Java đã báo cáo vấn đề này nhưng vẫn chưa được giải quyết. Liên kết vấn đề: https://bugs.openjdk.org/browse/JDK-8178287.

Tìm những người theo dõi khác

Có rất nhiều công cụ theo dõi chế độ kernel trên Linux, chẳng hạn như perf, bcc và systemtap, nhưng tất cả chúng đều yêu cầu quyền root và tôi không thể xin được quyền này.

Trong chế độ người dùng, strace và ltrace được triển khai dựa trên lệnh gọi hệ thống ptrace. Tôi đã thử chúng và không thể theo dõi trực tiếp hàm phân bổ đối tượng lớn G1CollectedHeap::humongous_obj_allocate trong G1.

Tôi cũng đã tìm kiếm trực tuyến trong vài ngày, hy vọng tìm thấy một công cụ theo dõi chế độ người dùng thuần túy hữu ích, nhưng tiếc là tôi không thể tìm thấy nó trong vài ngày. Cuối cùng, tôi chỉ có thể tập trung vào công cụ gỡ lỗi c/C++ gdb. vì gdb Đây là một công cụ gỡ lỗi nên nó phải có khả năng xem các tham số gọi và ngăn xếp lệnh gọi của hàm đã chỉ định, miễn là bạn tìm thấy ứng dụng tương ứng! .

Viết tập lệnh gdb

Sau một số nghiên cứu và khám phá (PS: thực sự tôi đã mất gần 2 tuần), cuối cùng tôi đã viết được một tập lệnh gdb thực tế như sau:

                        
                          xử lý tất cả nostop noprint pass xử lý SIGINT dừng in nopass break *(_ZN15G1CollectedHeap22humongous_obj_allocateEmh + 0x58c06f - 0x58c060) while 1 continue # Nếu là Ctrl+c, thoát nếu $_siginfo nếu $_siginfo.si_signo == 2 tách thoát khỏi end end printf "word_size is %d\n",$rsi if $rsi > 100*1024*1024/8 # In ngày shell thời gian hiện tại +%FT%T # In chuỗi luồng hiện tại # In ngăn xếp cuộc gọi hiện tại bt python import subprocess # Gửi kill tới jvm - 3 tín hiệu, cụ thể là tín hiệu SIGQUIT python proc = subprocess.Popen(['kill','-3',str(gdb.selected_inferior().pid)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1, Universal_newlines=True) thiết bị xuất chuẩn python, stderr = proc.communicate() python print(stdout) python print(stderr) tách thoát khỏi kết thúc

                        
                      

Những sinh viên chưa bao giờ học gdb có thể không hiểu nó. Điều đó không quan trọng.

                        
                          xử lý tất cả nostop noprint pass xử lý SIGINT stop in nopass

                        
                      

Hai tay cầm này được sử dụng để xử lý tín hiệu Linux. Vì chúng ta không cần gỡ lỗi các vấn đề về tín hiệu nên chúng ta không để gdb xử lý tín hiệu để có thể thoát khỏi tập lệnh gdb khi nhấn Ctrl+c.

                        
                          phá vỡ *(_ZN15G1CollectedHeap22humongous_obj_allocateEmh + 0x58c06f - 0x58c060)

                        
                      

Việc ngắt này nhằm đặt điểm dừng cho hàm phân bổ đối tượng lớn G1CollectedHeap::humongous_obj_allocate trong G1. Mã nguồn của phương thức như sau: Tham số word_size cho biết có bao nhiêu từ bộ nhớ được phân bổ. Trên máy 64 bit, 1 từ là. bằng 8 byte, vì vậy nếu bạn có thể theo dõi Bằng cách đạt đến giá trị tham số này, bạn có thể biết kích thước của đối tượng lớn được phân bổ mỗi lần.

Vì JVM được viết bằng C++ và trình biên dịch C++ sẽ xử lý tên hàm để tương thích với C ABI, nên tên hàm được biên dịch trở thành _ZN15G1CollectedHeap22humongous_obj_allocateEmh. Truy vấn bảng ký hiệu của tệp nhị phân thông qua nm. tên.

                        
                          $ tất cả các triển khai liên quan đến java /usr/local/jdk/jdk1.8.0_202/bin/java # jvm đều có trong thư viện động libjvm.so $ find /usr/local/jdk/jdk1.8.0_202 | grep libjvm.so /usr/local/jdk/jdk1.8.0_202/jre/lib/amd64/server/libjvm.so $ nm /usr/local/jdk/jdk1.8.0_202/jre/lib/amd64/server/libjvm.so |grep humongous_obj_allocate 000000000058c060 t _ZN15G1CollectedHeap22humongous_obj_allocateEmh 000000000058b1a0 t _ZN15G1CollectedHeap41humongous_obj_allocate_initialize_khu vựcEjjmh

                        
                      

Nhìn lại đoạn script đã đặt breakpoint trước đó:

                        
                          phá vỡ *(_ZN15G1CollectedHeap22humongous_obj_allocateEmh + 0x58c06f - 0x58c060)

                        
                      

+ 0x58c06f - 0x58c060 Đây là thao tác offset địa chỉ. Học sinh đã học về hợp ngữ nên biết rằng sau khi gọi một hàm, một số lệnh hợp ngữ ở đầu hàm thường liên quan đến các thao tác đăng ký tham số. Thanh ghi tham số x86 như sau:

                        
                          rdi đại diện cho tham số đầu tiên rsi đại diện cho tham số thứ hai rdx đại diện cho tham số thứ ba RCx đại diện cho tham số thứ tư r8 đại diện cho tham số thứ năm r9 đại diện cho tham số thứ sáu

                        
                      

Bạn có thể sử dụng objdump để phân tách libjvm.so và xem mã hợp ngữ để xác định dòng hướng dẫn nào mà điểm dừng sẽ được bù vào. Hãy chờ nhé, nội dung liên quan đến hợp ngữ ở bên dưới.

                        
                          $ objdump -d /usr/local/jdk/jdk1.8.0_202/jre/lib/amd64/server/libjvm.so |less -S

                        
                      

Sau đó tìm kiếm hàm _ZN15G1CollectedHeap22humongous_obj_allocateEmh như sau: Lý do tại sao bạn cần thêm offset là vì thanh ghi rsi (tham số thứ hai) sẽ chỉ có giá trị sau + 0x58c06f - 0x58c060. value là do trong lập trình đối tượng C++, tham số đầu tiên là this.

Khi đó logic sau đây rất dễ hiểu như sau: Đầu tiên là vòng lặp, sau đó tiếp tục có nghĩa là để chương trình chạy Khi chương trình chạm đến điểm dừng, tiếp tục sẽ được thực thi. Phần giữa là xử lý tín hiệu, chủ yếu là thoát khỏi vòng lặp bằng Ctrl+c. Cuối cùng, in ra giá trị của rsi thông qua in, từ đó theo dõi giá trị của tham số word_size.

Sau đó in thông tin về luồng và ngăn xếp cuộc gọi như sau: Khi bộ nhớ được phân bổ lớn hơn 100M, hãy in thời gian hiện tại, luồng hiện tại và ngăn xếp cuộc gọi hiện tại.

Nhưng ngăn xếp cuộc gọi được in bằng lệnh bt của gdb giống như sau: Vì Java được thông dịch và thực thi nên không thể lấy được ngăn xếp cuộc gọi bt của phần java.

Không có ngăn xếp cuộc gọi Java, tập lệnh theo dõi này thật khập khiễng. Tôi đã bị mắc kẹt ở đây một thời gian dài và đã thử nhiều phương pháp.

Những sinh viên quen thuộc với Java nên biết rằng JVM có chức năng chẩn đoán ẩn. Nếu tín hiệu SIGQUIT được gửi đến quy trình JVM, JVM sẽ in thông tin ngăn xếp luồng ở đầu ra tiêu chuẩn và tín hiệu SIGQUIT có thể được gửi qua kill -3. , do đó có Đoạn mã sau: gdb thực sự mạnh mẽ, với các phần mở rộng python tích hợp và thông qua gói quy trình con của python, bạn có thể thực thi lệnh kill -3.

Việc tách và thoát sau đây được sử dụng để thoát gdb, vì vậy không cần đi sâu vào chi tiết.

Chạy tập lệnh gdb để theo dõi các đối tượng lớn

Đặt tên cho tập lệnh gdb ở trên là trace.gdb, sau đó bạn có thể sử dụng lệnh gdb để chạy tập lệnh đó như sau:

                        
                          $ gdb -q --batch -x theo dõi.gdb -p `pgrep java`

                        
                      

Trong số đó, pgrep java được sử dụng để lấy số tiến trình của tiến trình java.

Lưu ý: gdb về cơ bản là một trình gỡ lỗi dựa trên lệnh gọi hệ thống ptrace. Khi điểm dừng bị tấn công, quy trình sẽ phải chịu một chi phí chuyển đổi đáng kể, vì vậy phương pháp này chỉ có thể theo dõi các hàm không được gọi thường xuyên.

Sau khi chạy, các tham số và thông tin luồng được theo dõi như sau: 166 sau LWP là số luồng, là 0xa6 khi chuyển sang hệ thập lục phân. Sau đó đi tới nhật ký đầu ra tiêu chuẩn của quy trình java và tìm ngăn xếp lệnh gọi Java của luồng này, như sau:

Việc phân bổ đối tượng lớn được bắt đầu bằng hàm readBinary. Việc gỡ lỗi hàm này như sau: Ôi chúa ơi, nó tạo ra một mảng byte rất lớn. Không có gì ngạc nhiên khi có một phân bổ đối tượng lớn 1,9G! .

Lệnh gọi readBinary được kích hoạt bởi mã này:

                        
                          Nhà máy TProtocolFactory = new TCompactProtocol.Factory(); Trình giải tuần tự TDeserializer = new TDeserializer(nhà máy); deserializer.deserialize(deserializeObj, sourceBytes);

                        
                      

Đây là quá trình giải tuần tự hóa tiết kiệm, giải tuần tự hóa mảng byte sourceBytes thành đối tượng deserializeObj.

Khi sourceBytes được tuần tự hóa từ đối tượng deserializeObj, không có vấn đề gì với quá trình khử lưu lượng.

Khi sourceBytes không được tuần tự hóa từ đối tượng deserializeObj, khi mã giải tuần tự hóa phân tích độ dài (độ dài) trường từ sourceBytes, nó có thể là một giá trị tùy ý, có thể dẫn đến việc tạo ra một mảng byte cực lớn.

Nhưng chúng tôi đã viết mã này để phát hiện xem sourceBytes có được deserializeObj tuần tự hóa hay không, vì vậy sourceBytes có thể không được deserializeObj tuần tự hóa! .

Sau khi kiểm tra nhanh mã tiết kiệm một lúc, tôi thấy rằng độ dài tối đa của trường có thể bị giới hạn như sau: Hãy nghĩ mà xem, độ dài của một trường được deserialized chắc chắn sẽ không dài bằng toàn bộ dữ liệu được deserialized, vì vậy hãy sử dụng sourceBytes.length Chỉ cần giới hạn nó.

                        
                          Nhà máy TProtocolFactory = new TCompactProtocol.Factory(sourceBytes.length, sourceBytes.length); Trình giải tuần tự TDeserializer = new TDeserializer(nhà máy); trình giải tuần tự.deserialize(deserializeObj, sourceBytes);

                        
                      

Sau giới hạn, một ngoại lệ sẽ được đưa ra nếu trường quá dài, vì vậy nếu ngoại lệ khử lưu lượng xảy ra, điều đó có nghĩa là sourceBytes hiện tại không được deserializeObj tuần tự hóa.

Tóm tắt

Tôi đã mất rất nhiều thời gian để viết tập lệnh gdb này vì tôi không biết liệu gdb có thể làm điều này trước hay không và tôi không phải là lập trình viên C/C++ và tôi không quen với kiến ​​thức liên quan đến hợp ngữ. bỏ cuộc nhiều lần trong quá trình này.

May mắn thay, cuối cùng nó đã thành công và nó cho phép tôi có được một cách mới để giải quyết vấn đề, điều này rất đáng giá.

Cuối cùng, bài viết về việc ghi nhớ toàn bộ quá trình sử dụng gdb để chẩn đoán các vấn đề về gc sẽ kết thúc tại đây. Nếu bạn muốn biết thêm về việc ghi nhớ toàn bộ quá trình sử dụng gdb để chẩn đoán các vấn đề về gc, vui lòng tìm kiếm các bài viết của CFSDN hoặc tiếp tục duyệt các bài viết liên quan. Tôi hy vọng tất cả các bạn sẽ ủng hộ blog của tôi trong tương lai! .

26 4 0
tôi là một con chim nhỏ
Hồ sơ

Tôi là một lập trình viên xuất sắc, rất giỏi!

Nhận phiếu giảm giá taxi Didi miễn phí
Phiếu giảm giá taxi Didi
Chứng chỉ ICP Bắc Kinh số 000000
Hợp tác quảng cáo: 1813099741@qq.com 6ren.com
Xem sitemap của VNExpress