Tại sao sun.misc.Unsafe tồn tại và làm thế nào nó có thể được sử dụng trong thế giới thực? [đóng cửa]


267

Tôi đã đi qua gói sun.misc.Unsafe vào một ngày khác và ngạc nhiên về những gì nó có thể làm.

Tất nhiên, lớp học không có giấy tờ, nhưng tôi đã tự hỏi liệu có bao giờ một lý do tốt để sử dụng nó. Những kịch bản có thể phát sinh nơi bạn sẽ cần phải sử dụng nó? Làm thế nào nó có thể được sử dụng trong một kịch bản trong thế giới thực?

Hơn nữa, nếu bạn làm cần nó, làm thế không chỉ ra một cái gì đó mà có lẽ là sai với thiết kế của bạn?

Tại sao Java thậm chí bao gồm lớp này?


7
Các nhà phát triển JDK hiện đang xem xét API này để có thể chuyển đổi thành API công khai trong Java 9. Nếu bạn sử dụng nó, bạn nên mất 5 phút để điền vào bản khảo sát: giám sát: ansymsons-sisc-Unsafe .
Andy Lynch

2
Bài đăng này đang được thảo luận trên meta: meta.stackoverflow.com/questions/299139/ Khăn
Jon Clements

Câu trả lời:


159

ví dụ

  1. VM "nội tại." tức là CAS (So sánh và hoán đổi) được sử dụng trong Bảng băm không khóa, ví dụ: sun.misc.Unsafe.compareAndSwapInt, nó có thể thực hiện các cuộc gọi JNI thực sự thành mã gốc có chứa các hướng dẫn đặc biệt cho CAS

    đọc thêm về CAS tại đây http://en.wikipedia.org/wiki/Compare-and-swap

  2. Chức năng sun.misc.Unsafe của máy chủ VM có thể được sử dụng để phân bổ các đối tượng chưa được khởi tạo và sau đó diễn giải lời gọi hàm tạo như bất kỳ lệnh gọi phương thức nào khác.

  3. Người ta có thể theo dõi dữ liệu từ địa chỉ gốc. Có thể truy xuất địa chỉ bộ nhớ của đối tượng bằng lớp java.lang.Unsafe và hoạt động trực tiếp trên các trường của nó thông qua các phương thức get / put không an toàn!

  4. Biên dịch tối ưu hóa thời gian cho JVM. VM hiệu suất cao bằng cách sử dụng "ma thuật", yêu cầu các hoạt động cấp thấp. ví dụ: http://en.wikipedia.org/wiki/Jike_RVM

  5. Phân bổ bộ nhớ, sun.misc.Unsafe.allocateMemory, vd: - Trình xây dựng DirectByteBuffer gọi nội bộ khi ByteBuffer.allocateDirect được gọi

  6. Theo dõi ngăn xếp cuộc gọi và phát lại với các giá trị được khởi tạo bởi sun.misc.Unsafe, hữu ích cho thiết bị

  7. sun.misc.Unsafe.arrayBase Offerset và ArrayIndexScale có thể được sử dụng để phát triển mảng, một kỹ thuật phá vỡ các mảng lớn thành các đối tượng nhỏ hơn để hạn chế chi phí thời gian thực của việc quét, cập nhật hoặc di chuyển các hoạt động trên các đối tượng lớn

  8. http://robaustin.wikidot.com/how-to-write-to-direct-memory-locations-in-java

thêm về các tài liệu tham khảo tại đây - http://bytescrolls.blogspot.com/2011/04/interesting-uses-of-sunmiscunsafe.html


1
nếu bạn nhận được địa chỉ của một trường bằng cách sử dụng Không an toàn, nó luôn có thể được thay đổi bởi GC, vậy hoạt động đó có vô dụng không?
pdeva

lấy địa chỉ cho những người bạn đã phân bổ
zudokod

chính xác những gì bạn có nghĩa là một trong những tôi đã phân bổ. điều này dường như được sử dụng ở những nơi mà các đối tượng được tạo bằng toán tử 'mới', do đó, câu hỏi của tôi.
pdeva

1
không an toàn.allocateMemory và đặt giá trị
zudokod

1
Về điểm 2, tôi muốn biết làm thế nào bạn có thể gọi hàm tạo như bất kỳ lệnh gọi phương thức nào khác? Bởi vì tôi đã không tìm thấy bất kỳ cách nào để làm điều đó trừ khi bằng mã byte.
Miguel Gamboa

31

Chỉ từ việc chạy tìm kiếm trong một số công cụ tìm kiếm mã, tôi có được các ví dụ sau:

Lớp đơn giản để có quyền truy cập vào đối tượng {@link Unsafe}. {@link Không an toàn} * là bắt buộc để cho phép các hoạt động CAS hiệu quả trên mảng. Lưu ý rằng các phiên bản trong {@link java.util.concản.atomic}, chẳng hạn như {@link java.util.concien.atomic.AtomicLongArray}, yêu cầu bảo đảm đặt hàng bộ nhớ bổ sung thường không cần thiết trong các thuật toán này và cũng đắt tiền trên hầu hết các bộ xử lý.

  • SoyLatte - java 6 cho đoạn trích osx javadoc

/ ** Lớp cơ sở cho FieldAccessors dựa trên sun.misc.Unsafe cho các trường tĩnh. Quan sát là chỉ có chín loại trường theo quan điểm của mã phản chiếu: tám loại nguyên thủy và Đối tượng. Sử dụng lớp Không an toàn thay vì mã byte được tạo sẽ tiết kiệm bộ nhớ và thời gian tải cho FieldAccessors được tạo động. * /

  • SpikeSource

/ * FinalField được gửi qua dây .. làm thế nào để unmarshall và tạo lại đối tượng ở phía bên nhận? Chúng tôi không muốn gọi hàm tạo vì nó sẽ thiết lập các giá trị cho các trường cuối cùng. Chúng ta phải tạo lại trường cuối cùng chính xác như ở bên người gửi. Sun.misc.Unsafe thực hiện điều này cho chúng tôi. * /

Có nhiều ví dụ khác, chỉ cần theo liên kết trên ...


25

Thật thú vị, tôi thậm chí chưa bao giờ nghe nói về lớp học này (có lẽ là một điều tốt, thực sự).

Một điều khiến người ta chú ý là sử dụng Unsafe # setMemory để loại bỏ bộ đệm có chứa thông tin nhạy cảm tại một điểm (mật khẩu, khóa, ...). Bạn thậm chí có thể làm điều này với các trường của các đối tượng "bất biến" (một lần nữa tôi cho rằng sự phản chiếu cũ đơn giản cũng có thể thực hiện thủ thuật ở đây). Tôi không phải là chuyên gia bảo mật mặc dù vậy hãy dùng nó với một hạt muối.


4
I'd never even heard of this class... Tôi đã nói với bạn về điều đó rất nhiều lần! thở dài + :(
Tim Bender

7
Sẽ không có điểm nào, vì Java sử dụng trình thu gom rác thế hệ sao chép và thông tin nhạy cảm của bạn có thể sẽ được đặt ở một nơi khác trong bộ nhớ 'miễn phí' đang chờ ghi đè.
Daniel Cassidy

39
Chưa bao giờ nghe về điều đó, nhưng tôi thích park()tài liệu của họ : "Chặn luồng hiện tại, quay trở lại khi xảy ra tình trạng mất cân bằng hoặc xảy ra tình trạng mất cân bằng đã xảy ra hoặc luồng bị gián đoạn hoặc nếu không tuyệt đối và thời gian không bằng 0 thời gian nano giây đã cho đã trôi qua, hoặc nếu tuyệt đối, thời hạn nhất định tính bằng mili giây kể từ khi Epoch trôi qua, hoặc giả mạo (tức là quay trở lại mà không có "lý do") ". Gần như là "bộ nhớ được giải phóng khi chương trình thoát ra, hoặc, trong những khoảng thời gian ngẫu nhiên, tùy theo điều kiện nào đến trước".
aroth

1
@Daniel, thú vị, tôi đã không xem xét điều đó. Bây giờ bạn có thể thấy tại sao tôi không phải là một chuyên gia bảo mật. :)
Mike Daniels

22

Dựa trên một phân tích rất ngắn gọn về thư viện Java 1.6.12 bằng cách sử dụng nhật thực để theo dõi tham chiếu, dường như mọi chức năng hữu ích của Unsafeđều được bộc lộ theo những cách hữu ích.

Các hoạt động CAS được thể hiện thông qua các lớp Nguyên tử *. Các chức năng thao tác bộ nhớ được phơi bày thông qua các hướng dẫn Đồng bộ hóa DirectByteBuffer (park, unpark) được hiển thị thông qua Trình đồng bộ hóa trừu tượng được sử dụng bởi các cài đặt Khóa.


Các AtomicXXXUpdaters quá chậm và khi bạn thực sự cần chúng: CAS - bạn không đủ khả năng để sử dụng chúng thực sự. Nếu bạn định làm kim loại, bạn sẽ không sử dụng các mức độ trừu tượng và nhiều kiểm tra. Không CAS là một điều xấu trong một vòng lặp đặc biệt. khi phần cứng quyết định dự đoán sai chi nhánh (do tranh chấp cao) nhưng có thêm vài so sánh / chi nhánh chỉ làm tổn thương. Công viên / Unpark được hiển thị thông qua LockSupportkhông phải AQS (cái sau này có ý nghĩa khóa hơn là công viên / unpark)
bestsss

21

Unsafe.throwException - cho phép ném ngoại lệ được kiểm tra mà không cần khai báo chúng.

Điều này rất hữu ích trong một số trường hợp bạn xử lý phản xạ hoặc AOP.

Giả sử bạn Xây dựng proxy chung cho Giao diện do người dùng xác định. Và người dùng có thể chỉ định ngoại lệ nào được đưa ra bởi sự cấy ghép trong trường hợp đặc biệt chỉ bằng cách khai báo ngoại lệ trong giao diện. Sau đó, đây là cách duy nhất tôi biết, để tăng một ngoại lệ được kiểm tra trong Triển khai động của Giao diện.

import org.junit.Test;
/** need to allow forbidden references! */ import sun.misc.Unsafe;

/**
 * Demonstrate how to throw an undeclared checked exception.
 * This is a hack, because it uses the forbidden Class {@link sun.misc.Unsafe}.
 */
public class ExceptionTest {

    /**
     * A checked exception.
     */
    public static class MyException extends Exception {
        private static final long serialVersionUID = 5960664994726581924L;
    }

    /**
     * Throw the Exception.
     */
    @SuppressWarnings("restriction")
    public static void throwUndeclared() {
        getUnsafe().throwException(new MyException());
    }

    /**
     * Return an instance of {@link sun.misc.Unsafe}.
     * @return THE instance
     */
    @SuppressWarnings("restriction")
    private static Unsafe getUnsafe() {
        try {

            Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
            singleoneInstanceField.setAccessible(true);
            return (Unsafe) singleoneInstanceField.get(null);

        } catch (IllegalArgumentException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (SecurityException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (NoSuchFieldException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (IllegalAccessException e) {
            throw createExceptionForObtainingUnsafe(e);
        }
    }

    private static RuntimeException createExceptionForObtainingUnsafe(final Throwable cause) {
        return new RuntimeException("error while obtaining sun.misc.Unsafe", cause);
    }


    /**
     * scenario: test that an CheckedException {@link MyException} can be thrown
     * from an method that not declare it.
     */
    @Test(expected = MyException.class)
    public void testUnsingUnsaveToThrowCheckedException() {
        throwUndeclared();
    }
}

14
bạn có thể làm như vậy với Thread.stop(Throwable)nhu cầu không an toàn, trong cùng một chủ đề, bạn có thể ném bất cứ thứ gì dù sao (không có kiểm tra biên dịch)
bestsss

Bạn có thể thực hiện việc này hoàn toàn thông qua mã byte (Hoặc sử dụng Oliveroc để làm điều đó cho bạn)
Antimon

1
@bestsss Phương thức đó đã bị loại bỏ và ném một UnsupportedOperationExceptionluồng trong luồng hiện tại kể từ Java 8. Tuy nhiên, phiên bản không có đối số mà ném ThreadDeathvẫn hoạt động.
gparyani

@damryfbfnetsi, tôi đã không theo dõi các cuộc thảo luận jdk cốt lõi trong một thời gian dài và không có kế hoạch chuyển sang java 8. Tuy nhiên, đây là một ý tưởng khá khó hiểu vì dù sao nó cũng được triển khai bởi trình tạo mã byte, trừ khi bây giờ trình xác minh thực sự kiểm tra xem chúng phương thức khai báo các hàm ném ... nhưng điều đó có thể không tương thích ngược vì siêu dữ liệu về ngoại lệ ném được miễn phí bị loại bỏ.
bestsss

10

Lớp học không an toàn

Một tập hợp các phương pháp để thực hiện các hoạt động cấp thấp, không an toàn. Mặc dù lớp và tất cả các phương thức là công khai, việc sử dụng lớp này bị hạn chế vì chỉ có mã đáng tin cậy mới có thể có được các thể hiện của nó.

Một công dụng của nó là trong java.util.concurrent.atomiccác lớp:


6

Để sao chép bộ nhớ hiệu quả (sao chép nhanh hơn System.arraycopy () cho các khối ngắn ít nhất); như được sử dụng bởi Java LZFSnappy codec. Họ sử dụng 'getLong' và 'putLong', nhanh hơn so với việc sao chép từng byte một; đặc biệt hiệu quả khi sao chép những thứ như khối 16/32/64 byte.


1
Doh, mảng quét sử dụng các vòng lặp SSE trên x86-64 tốt hơn getLong/putLong(và bạn cũng phải tính toán địa chỉ)
bestsss

Bạn đã thực sự đo lường điều này? Đối với các khối ngắn hơn, tôi thấy hiệu suất luôn tốt hơn trên x86-64 khi sử dụng kết hợp getLong/ putLong: lý tưởng tôi thích System.arraycopy()đơn giản hơn và tất cả; nhưng thử nghiệm thực tế cho thấy khác cho các trường hợp tôi đã thử nghiệm.
StaxMan

có sử dụng không an toàn Tôi không thể thực hiện bất kỳ hiệu suất có ý nghĩa ra khỏi hàm def def. Đối với một vài byte bản sao dài trên các mảng lớn, get / putLong có thể thực sự hoạt động khi trình biên dịch phải kiểm tra độ dài. Một số impl. thêm hàng rào bộ nhớ qua System.arrayCopy (mặc dù có thể bị vô hiệu hóa / kích hoạt), vì vậy đó có thể là thủ phạm thực sự.
bestsss

Đồng ý. Có thể các JDK mới hơn đã thay đổi điều này; ban đầu khi tôi quan sát hoạt động nhanh hơn (với JDK 1.6) tôi cũng ngạc nhiên. Hoặc có lẽ tôi đang quên một số khác biệt cụ thể trong cách sử dụng. Đây là những tối ưu hóa khó khăn (và có thể không ổn định), ngay cả khi chúng hoạt động, và điều cần thiết là phải đo lường hiệu quả.
StaxMan

5

Gần đây tôi đã làm việc để thực hiện lại JVM và thấy rằng một số lượng đáng ngạc nhiên các lớp được triển khai theo các khía cạnh Unsafe. Lớp này hầu hết được thiết kế cho những người triển khai thư viện Java và chứa các tính năng về cơ bản không an toàn nhưng cần thiết để xây dựng các nguyên thủy nhanh. Ví dụ, có các phương thức để nhận và ghi các offset trường thô, sử dụng đồng bộ hóa ở mức phần cứng, cấp phát và giải phóng bộ nhớ, v.v. Nó không dành cho các lập trình viên Java thông thường; nó không có giấy tờ, cụ thể về triển khai và vốn không an toàn (do đó có tên!). Hơn nữa, tôi nghĩ rằngSecurityManager sẽ không cho phép truy cập vào nó trong hầu hết các trường hợp.

Nói tóm lại, nó chủ yếu tồn tại để cho phép những người triển khai thư viện truy cập vào máy bên dưới mà không phải khai báo mọi phương thức trong các lớp nhất định như AtomicIntegerbản địa. Bạn không cần phải sử dụng hoặc lo lắng về nó trong lập trình Java thông thường, vì toàn bộ vấn đề là làm cho phần còn lại của các thư viện đủ nhanh để bạn không cần loại truy cập đó.


trên thực tế, SecurityManager không cho phép truy cập vào nó nếu phản chiếu bị vô hiệu hóa
amara

@ lấp lánh- Bạn có thể giải thích về điều này?
templatetypedef

trong khi nhận được một trường hợp từ getUnsafe không có những yêu cầu khá nghiêm ngặt, Unsafe.class.getDeclaredField("theUnsafe")với .setAccessible(true)và sau đó .get(null)sẽ nhận được nó quá
amara

@ sparkleshy- Tôi ngạc nhiên khi thấy nó hoạt động - người quản lý bảo mật nên gắn cờ đó.
templatetypedef

5

Sử dụng nó để truy cập và phân bổ số lượng lớn bộ nhớ một cách hiệu quả, chẳng hạn như trong công cụ voxel của riêng bạn! (tức là trò chơi theo phong cách Minecraft.)

Theo kinh nghiệm của tôi, JVM thường không thể loại bỏ giới hạn - kiểm tra tại chỗ bạn thực sự cần nó. Ví dụ: nếu bạn đang lặp qua một mảng lớn, nhưng quyền truy cập bộ nhớ thực tế được giấu bên dưới một lệnh gọi phương thức * không ảo trong vòng lặp, JVM vẫn có thể thực hiện kiểm tra giới hạn với mỗi lần truy cập mảng, thay vì chỉ một lần trước đó vòng lặp. Do đó, để tăng hiệu năng tiềm năng lớn, bạn có thể loại bỏ giới hạn JVM - kiểm tra bên trong vòng lặp thông qua phương thức sử dụng sun.misc.Unsafe để truy cập trực tiếp vào bộ nhớ, đảm bảo thực hiện bất kỳ giới hạn nào - tự kiểm tra tại đúng vị trí. (Bạn sẽ giới hạn kiểm tra ở mức độ nào, phải không?)
* bởi không ảo, ý tôi là JVM không cần phải tự động giải quyết bất kỳ phương thức cụ thể nào của bạn, bởi vì bạn đã đảm bảo chính xác rằng lớp / phương thức / thể hiện là một sự kết hợp giữa tĩnh / cuối cùng / những gì bạn có.

Đối với công cụ voxel tự trồng tại nhà của tôi, điều này dẫn đến tăng hiệu suất đáng kể trong quá trình tạo và xê-ri hóa chunk (iow nơi tôi đang đọc / ghi vào toàn bộ mảng cùng một lúc). Kết quả có thể khác nhau, nhưng nếu thiếu giới hạn loại bỏ là vấn đề của bạn, thì điều này sẽ khắc phục nó.

Có một số vấn đề tiềm ẩn lớn với vấn đề này: cụ thể, khi bạn cung cấp khả năng truy cập bộ nhớ mà không giới hạn kiểm tra cho khách hàng của giao diện của bạn, họ có thể sẽ lạm dụng nó. (Đừng quên rằng tin tặc cũng có thể là khách hàng của giao diện của bạn ... đặc biệt là trong trường hợp công cụ voxel được viết bằng Java.) Vì vậy, bạn nên thiết kế giao diện của mình theo cách mà việc truy cập bộ nhớ không thể bị lạm dụng hoặc bạn nên cực kỳ cẩn thận để xác nhận sử dụng dữ liệu trước khi nó có thể bao giờ hết, bao giờ trộn lẫn với giao diện nguy hiểm của bạn. Xem xét những điều thảm khốc mà một hacker có thể làm với quyền truy cập bộ nhớ không được kiểm soát, có lẽ tốt nhất là thực hiện cả hai cách tiếp cận.


4

Các bộ sưu tập heap có thể hữu ích cho việc phân bổ số lượng lớn bộ nhớ và giải phóng nó ngay lập tức sau khi sử dụng mà không bị nhiễu bởi GC. Tôi đã viết một thư viện để làm việc với các mảng / danh sách heap dựa trên sun.misc.Unsafe.


4

Chúng tôi đã triển khai các bộ sưu tập lớn như Mảng, HashMaps, TreeMaps bằng cách sử dụng Không an toàn.
Và để tránh / giảm thiểu sự phân mảnh, chúng tôi đã triển khai cấp phát bộ nhớ bằng cách sử dụng các khái niệm về dlmalloc không an toàn.
Điều này giúp chúng tôi đạt được hiệu suất đồng thời.


3

Unsafe.park()Unsafe.unpark()để xây dựng các cấu trúc kiểm soát đồng thời tùy chỉnh và các cơ chế lập lịch hợp tác.


24
có sẵn công khai dưới dạngjava.util.concurrent.locks.LockSupport
bestsss

1

Không tự mình sử dụng nó, nhưng tôi cho rằng nếu bạn có một biến chỉ thỉnh thoảng được đọc bởi nhiều hơn một luồng (vì vậy bạn không thực sự muốn làm cho nó biến động), bạn có thể sử dụng putObjectVolatilekhi viết nó trong luồng chính và readObjectVolatilekhi làm các bài đọc hiếm từ các chủ đề khác.


1
nhưng theo các cuộc thảo luận về chủ đề dưới đây, sự biến động không rõ ràng gần như nhanh như các chất không bay hơi dù sao stackoverflow.com/questions/5573782/
Lỗi

bạn không thể thay thế ngữ nghĩa dễ bay hơi bằng cách viết đơn giản và đọc dễ bay hơi ... đây là một công thức cho thảm họa vì nó có thể hoạt động trong một cài đặt nhưng không phải là khác. Nếu bạn đang tìm kiếm để có ngữ nghĩa dễ bay hơi với một chủ đề nhà văn duy nhất, bạn có thể sử dụng AtomicReference.lazySet trên chủ đề viết và lấy () trên các độc giả (xem bài đăng này để thảo luận về chủ đề này). Đọc dễ bay hơi là tương đối rẻ, nhưng không miễn phí, xem ở đây .
Nitsan Wakart

"... bạn có thể sử dụng putObjectVoliverse khi viết nó ..." Tôi không đề xuất cách viết đơn giản.
Matt Crinklaw-Vogt

1

Bạn cần nó nếu bạn cần thay thế chức năng được cung cấp bởi một trong các lớp hiện đang sử dụng nó.

Đây có thể là tuần tự hóa / giải tuần tự tùy chỉnh / nhanh hơn / gọn hơn, một bộ đệm nhanh hơn / lớn hơn / phiên bản thay đổi kích thước của ByteBuffer hoặc thêm một biến nguyên tử, ví dụ như không được hỗ trợ.

Tôi đã sử dụng nó cho tất cả những điều này tại một số thời điểm.



0

Đối tượng dường như có sẵn để hoạt động ở mức thấp hơn mức mà mã Java thường cho phép. Nếu bạn đang mã hóa một ứng dụng mức cao thì JVM trừu tượng hóa việc xử lý bộ nhớ và các hoạt động khác ngoài mức mã để dễ lập trình hơn. Bằng cách sử dụng thư viện Không an toàn, bạn hoàn thành hiệu quả các hoạt động cấp thấp thường được thực hiện cho bạn.

Như woliveirajr đã nói "Random ()" sử dụng Unsafe để tạo giống như nhiều hoạt động khác sẽ sử dụng hàm allocateMemory () có trong Unsafe.

Là một lập trình viên, có lẽ bạn có thể thoát khỏi việc không bao giờ cần thư viện này nhưng việc kiểm soát chặt chẽ các yếu tố cấp thấp sẽ có ích (đó là lý do tại sao vẫn có hội và mã ở mức độ thấp hơn) trong các sản phẩm chính)

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.