Java直接内存访问的技巧有哪些

15次阅读
没有评论

本篇文章给大家分享的是有关 Java 直接内存访问的技巧有哪些,丸趣 TV 小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着丸趣 TV 小编一起来看看吧。

Java 直接内存访问的技巧

Java 被设计成一个安全,可管理的环境,然而 Java HotSpot 有一个后门,提供了对低级别的,对直接内存和线程的操作。这个后门是—-sun.misc.Unsafe。这个类在 JDK 中有广泛的应用,例如,java.nio 和 java.util.concurrent。很难想象在日常开发中使用这些危险的,不可移植和未经校验的 API。然而,Unsafe 提供一种简单的方法来观察 HotSpot JVM 内部的一些技巧。

获取 Unsafe

sun.misc.Unsafe 这个类的访问是受限的,它的构造方法是私有的,相应的工厂方法要求必须被 Bootloader 载入才能使用,也就是说,只有 JDK 内部分才能使用这个工厂方法来构造 Unsafe 对象。

1

2

3

4

5

6

7

8

9

10

11

12

13

public final class Unsafe {

 …

 private Unsafe() {}

 private static final Unsafe theUnsafe = new Unsafe();

 …

 public static Unsafe getUnsafe() {

 Class cc = sun.reflect.Reflection.getCallerClass(2);

 if (cc.getClassLoader() != null)

 throw new SecurityException(Unsafe);

 return theUnsafe;

 }

 …

}

幸运地是,有一个 theUnsafe 属性可以被利用来检索 Unsafe 实例,我们可以见到的写一个反射方法,来获取 Unsafe 实例:

1

2

3

4

5

6

7

public static Unsafe getUnsafe() {

try {

Field f = Unsafe.class.getDeclaredField(theUnsafe);

f.setAccessible(true);

return (Unsafe)f.get(null);

} catch (Exception e) {/* … */}

}

下面将学习一些 Unsafe 的方法。

1.long getAddress(long address)  和 void putAddress(long address, long x) 
对直接内存进行读写。

2.int getInt(Object o, long offset) , void putInt(Object o, long offset, int x)

另一个类似的方法对直接内存进行读写,将 C 语言的结构体和 Java 对象进行转换。

3.long allocateMemory(long bytes)

这个可以看做是 C 语言的 malloc() 函数的一种包装。

sizeof() 函数

Java 对象的结构如下图所示:

第一个技巧,是模拟 C 语言的 sizefo() 函数,这个函数返回对象的字节大小。我们可以用如下的代码实现 sizeof() 函数:

1

2

3

4

5

6

7

8

9

public static long sizeOf(Object object) {

 Unsafe unsafe = getUnsafe();

 return unsafe.getAddress(normalize( unsafe.getInt(object, 4L) ) + 12L );

}

 

public static long normalize(int value) {

 if(value = 0) return value;

 return (~0L 32) value;

}

我们需要使用 normalize() 函数,因为如果内存地址如果在 2^31 和 2^32 之间,将会自动的覆盖邻近的整型,也就是说用补码的方式进行存储。让我们在 32 位 JVM(JDK6 或者 7)中进行测试:

1

2

3

4

5

// 执行 sizeOf(new MyStructure()) 得到如下的结果:

 

class MyStructure {} // 8: 4 ( 起始标记) + 4 (指向类的指针)

class MyStructure {int x;} // 16: 4 (起始标记) + 4 (指向类的指针) + 4 (int) + 4 填充字节用来对齐 64 位块

class MyStructure {int x; int y;} // 16: 4 (起始标记) + 4 (指向类的指针) + 2*4

直接内存管理

Unsafe 允许通过 allcateMemory 和 freeMemory 方法对内存进行显示的分配和回收,直接分配的内存不在 GC 的控制内,并且不受限于 JVM 堆的大小。通常,通过 NIO 的脱离堆约束的缓冲,这些方法是安全有效的,但是有趣的是这让标准的 Java 引用映射非堆内存变成了可能:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

MyStructure structure = new MyStructure(); // create a test object

structure.x = 777;

 

long size = sizeOf(structure);

long offheapPointer = getUnsafe().allocateMemory(size);

getUnsafe().copyMemory(

 structure,  // source object

 0,  // source offset is zero – copy an entire object

 null,  // destination is specified by absolute address, so destination object is null

 offheapPointer, // destination address

 size

); // test object was copied to off-heap

 

Pointer p = new Pointer(); // Pointer is just a handler that stores address of some object

long pointerOffset = getUnsafe().objectFieldOffset(Pointer.class.getDeclaredField( pointer));

getUnsafe().putLong(p, pointerOffset, offheapPointer); // set pointer to off-heap copy of the test object

 

structure.x = 222; // rewrite x value in the original object

System.out.println(  ((MyStructure)p.pointer).x  ); // prints 777

 

….

 

class Pointer {

 Object pointer;

}

所以,事实上是可以对真实对象进行内存分配和回收的,不单单只是 NIO 中的字节缓冲。当然,有一个比较大的问题是,GC 将会在这样的内存欺骗之后发生。

继承自 Final 类和 void*

想象一下有一个以 String 为参数的方法,但它需要经行外部的重载。具体代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

Carrier carrier = new Carrier();

carrier.secret = 777;

 

String message = (String)(Object)carrier; // ClassCastException

handler(message);

 

 

void handler(String message) {

 System.out.println(((Carrier)(Object)message).secret );

}

 

 

class Carrier {

 int secret;

}

为了让这段代码能工作,首先需要更改 Carrier 类去伪装成 String 的子类。superclasses 列表被存储在 Carrier 类结构体 28 的位置,如上文图中所示。原则上,添加如下的代码可以让 Carrer 转化成 String:

1

2

3

long carrierClassAddress = normalize(unsafe.getInt(carrier, 4L) );

long stringClassAddress = normalize(unsafe.getInt( , 4L) );

unsafe.putAddress(carrierClassAddress + 32, stringClassAddress); // insert pointer to String class to the list of Carrier s superclasses

这样,类型转化可以正常工作。然而,这样的转换方式是不切当并且违反虚拟机规范的。更详细的方法将包含如下的步骤:

1. 在 Carrier 类中 32 的位置实际上包含了一个指向 Carrier 类自己的指针,所以这个指针将被转移到 36 的位置上,不仅仅是被指针重写到 String 类。

2. 当 Carrier 继承自 String 的时候,String 类的 final 标记将被移掉。

sun.misc.Unsafe 提供了几乎是不受限制的监控和修改虚拟机运行时数据结构的能力。尽管这些能力几乎是和 Java 开发本身不相干的,但是对于想要学习 HotSpot 虚拟机但是没有 C ++ 代码调试,或者需要去创建特别的分析工具的人来说,Unsafe 是一个伟大的工具。

以上就是 Java 直接内存访问的技巧有哪些,丸趣 TV 小编相信有部分知识点可能是我们日常工作会见到或用到的。希望你能通过这篇文章学到更多知识。更多详情敬请关注丸趣 TV 行业资讯频道。