如何使用Unsafe操作内存升级中的Java类和对象

他的最新文章
他的热门文章
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)如何使用Unsafe操作内存中的Java类和对象_百度知道
如何使用Unsafe操作内存中的Java类和对象
我有更好的答案
如果是JAVA里面,都是safe的,,,,,,想操作得在JVM层考虑
为您推荐:
其他类似问题
戒酒的相关知识
换一换
回答问题,赢新手礼包
个人、企业类
违法有害信息,请在下方选择后提交
色情、暴力
我们会通过消息、邮箱等方式尽快将举报结果通知您。危险代码:如何使用Unsafe操作内存中的Java类和对象—Part1 - ImportNew
| 标签: ,
让我们开始展示内存中Java类和对象结构
你可曾好奇过Java内存管理核心构件?你是否问过自己某些奇怪的问题,比如:
一个类在内存中占据多少空间?
我的对象在内存中消耗了多少空间?
对象的属性在内存中是如何被布局的?
如果这些问题听起来很熟悉,那么你就想到了点子上。对于像我们这样的在RebelLabs的Java极客来说,这些难解的谜题已经在我们脑海中缠绕了很长时间:如果你对探究类检测器感兴趣,想知道如何布局让所有的类更容易地从内存中取到指定变量,或是想在系统运行时侵入内存中的这些字段。这就意味着你能切实改变内存中的数据甚至是代码!
其它可能勾起你兴趣的知识点有,“堆外缓存”和“高性能序列化”的实现。这是一对构建在对象缓存结构上很好的实例,揭示了获取类和实例内存地址的方法,缓存中类和实例的布局以及关于对象成员变量布局的详细解释。我们希望尽可能简单地阐释这些内容,但是尽管如此这篇文章并不适合Java初学者,它要求具备对Java编程原理有一定的了解。
注意:下面关于类和对象的布局所写的内容特指Java SE 7,所以不推荐使用者想当然地认为这些适用于过去或将来的Java版本。方便起见,我们在GitHub项目上发布了这篇文章的示例代码,可以在这里找到。
在Java中最直接的内存操作方法是什么?
Java最初被设计为一种安全的受控环境。尽管如此,Java HotSpot还是包含了一个“后门”,提供了一些可以直接操控内存和线程的低层次操作。这个后门类——sun.misc.Unsafe——被JDK广泛用于自己的包中,如java.nio和java.util.concurrent。但是丝毫不建议在生产环境中使用这个后门。因为这个API十分不安全、不轻便、而且不稳定。这个不安全的类提供了一个观察HotSpot JVM内部结构并且可以对其进行修改。有时它可以被用来在不适用C++调试的情况下学习虚拟机内部结构,有时也可以被拿来做性能监控和开发工具。
为何变得不安全
sun.misc.Unsafe这个类是如此地不安全,以至于JDK开发者增加了很多特殊限制来访问它。它的构造器是私有的,工厂方法getUnsafe()的调用器只能被Bootloader加载。如你在下面代码片段的第8行所见,这个家伙甚至没有被任何类加载器加载,所以它的类加载器是null。它会抛出SecurityException 异常来阻止侵入者。
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 theU
幸运的是这里有一个Unsafe的变量可以被用来取得Unsafe的实例。我们可以轻松地编写一个复制方法通过反射来实现,如下所示:
public static Unsafe getUnsafe() {
Field f = Unsafe.class.getDeclaredField(&theUnsafe&);
f.setAccessible(true);
return (Unsafe)f.get(null);
} catch (Exception e) {
Unsafe一些有用的特性
虚拟机“集约化”(VM intrinsification):如用于无锁Hash表中的CAS(比较和交换)。再比如compareAndSwapInt这个方法用JNI调用,包含了对CAS有特殊引导的本地代码。在这里你能读到更多关于CAS的信息:。
主机虚拟机(译注:主机虚拟机主要用来管理其他虚拟机。而虚拟平台我们看到只有guest VM)的sun.misc.Unsafe功能能够被用于未初始化的对象分配内存(用allocateInstance方法),然后将构造器调用解释为其他方法的调用。
你可以从本地内存地址中追踪到这些数据。使用java.lang.Unsafe类获取内存地址是可能的。而且可以通过unsafe方法直接操作这些变量!
使用allocateMemory方法,内存可以被分配到堆外。例如当allocateDirect方法被调用时DirectByteBuffer构造器内部会使用allocateMemory。
arrayBaseOffset和arrayIndexScale方法可以被用于开发arraylets,一种用来将大数组分解为小对象、限制扫描的实时消耗或者在大对象上做更新和移动。
在下一篇中,由于可以在类中获取内存地址,我们将给出一些使用“Unsafe”的实例。
原文链接:
- 译文链接: [ 转载请保留原文出处、译者和译文链接。]
关于作者:
(新浪微博:)
https是见过的介绍最详细的文章了
关于ImportNew
ImportNew 专注于 Java 技术分享。于日 11:11正式上线。是的,这是一个很特别的时刻 :)
ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站。 import 可认为是学习和吸收, new 则可认为是新知识、新技术圈子和新朋友……
新浪微博:
推荐微信号
反馈建议:ImportNew.
广告与商务合作QQ:
– 好的话题、有启发的回复、值得信赖的圈子
– 写了文章?看干货?去头条!
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 活跃 & 专业的翻译小组
– 国内外的精选博客文章
– UI,网页,交互和用户体验
– JavaScript, HTML5, CSS
– 专注Android技术分享
– 专注iOS技术分享
– 专注Java技术分享
– 专注Python技术分享
& 2018 ImportNew他的最新文章
他的热门文章
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)危险代码:如何使用Unsafe操作内存中的Java类和对象—Part4 - ImportNew
| 标签: ,
字段布局和对齐
和C/C++不同,Java没有 sizeOf 运算符计算基本数据类型类型或对象所占用的内存空间,sizeOf 运算符在IO操作和内存管理中非常实用。事实上,由于基本数据类型的大小在语言规范中预先定义,Java中也不会出现指针拷贝内存和指针运算(因为没有指针)。因此,sizeOf 运算符并没有存在的必要。
有两种方法能够确定一个类及其属性共占用了多少内存空间。分别是浅尺寸(shallow size)和深尺寸(deep size)。浅尺寸就是对象本身各个字段所占用内存的大小,不包含该类中所引用的其他对象;于是引入了深尺寸概念,深尺寸在浅尺寸的基础上增加了该类引用的其他对象的浅尺寸。
Sun Java虚拟机规定:除数组外的所有对象都包含一个长度为双字(two-word,一个字由两个字节组成)的 header,一个长度为单字(one-word)flags,以及一个指向对应类引用的单字(one-word)的字段。当我们用 new Object() 方法创建一个对象时,堆上就会为其分配8个字节的内存空间。
对于一个继承了 Object 的类来说,情况会变得复杂而有趣。在这8个字节后面,类的各个属性在堆内存上会按照一定的规则对齐,但并不按照他们的声明顺序进行对齐。
基本数据类型按照下列顺序进行对齐:
double、long
int、float
short、char
boolean、byte
接下来,该类中所引用其他类的对象也会在堆上进行对齐。JVM会把对象的大小调整为8字节的倍数()。
请看下面的示例:
class BooleanClass {
这里会自动填充7个字节,整个对象的大小被扩大到16个字节。
Headers (include flags and ref to class) : 8 bytes
value of byte : 1 byte
padding : 7 bytes
关于OOP的更多信息
有关OOP的一些基本信息已经在 OOP 和 压缩OOP 章节介绍过了。我们假定你已经对JVM中 OOP 相关的术语有一定的了解了,下面就让我们对它作进一步的了解。
OOP由两个机器字长度的字段组成(32位JVM上机器字长为4字节,64位JVM上机器字长为8字节),这两个字段分别是 Mark 和 Klass。这两个字段出现在该实例的所有成员字段之前(译注:这两个字段应该就是对应上面字段的布局和对齐章节中 headers 所占用的8个字节)。但对于数组对象来说,在这两个字段之前会有一个额外的字段用于记录数组的长度。Mark 字段用于垃圾回收(在mark-and-sweep回收算法中使用),Klass 字段用于指向其对应类的元数据。所有的基本数据类型字段和引用字段都排在OOP(Mark 和 Klass 字段)的后面,包括引用其他对象的字段,甚至是引用OOP的字段(
Klass 字段是一个指向对应类的元数据(包括字段的定义以及类似C++的虚函数表)的指针。每一个实例都携带一份类的元数据是一种非常低效的方式,KlassOOPs能让所有对象共享同一份元数据从而减少不必要的开销。需要注意的是 KlassOOP 和类加载器所产生的 Class object(java.lang.class 类型的对象)并不相同。下面是两者之间的区别:
Class objects 只是普通的Java对象。Class objects和其他的Java对象一样可以用OOP(InstanceOOPs)表示。其表现行为也与其他的Java对象相同,还可以存放到Java变量里。
KlassOOPs 是类的元数据在JVM中的表现形式,例如类的虚函数表就存放在 KlassOOPs 中。由于 KlassOOPs 生存在堆的永久区里(Permgen space),因此在Java代码中无法直接获得 KlassOOPs 的引用。你也可以简单地认为 KlassOOP 是对应类的 Class object 在虚拟机级别上的镜像。
Mark字段指向一个维护着OOP相关管理信息的数据结构。在32位JVM中,mark字段的数据结构为():
哈希值(25 bits):记录着该对象的 HashCode() 方法的返回值。
年龄(4 bits):对象的年龄(即这个对象所经历过的垃圾回收的次数)。
偏向锁(1 bit)+ 锁(2 bits):用于表示该对象的同步状态。
Java 5引入了一种全新的对象同步方式,叫做偏向锁(Java 6中默认使用偏向锁 Biased-Lock)。经过观察发现,在多数情况下对象在运行时往往只被一个线程锁住,因此引入了偏向锁的概念。处于偏向锁状态中的对象会优先朝向第一个锁住它的线程,这个线程也会获得到更好的锁性能。Mark字段中会记录获取到该对象偏向锁的线程:
Java线程指针:23 bits
历元时间戳(Epoch):2 bits
年龄:4 bits
偏向锁状态:1 bit
锁状态:2 bits
如果另一个线程尝试锁定该对象,偏向锁就会失效(无法重新获取)。这样,所有的线程都必须通过显式调用lock、unlock方法来锁定和解锁对象。
下面是对象可能出现的状态:
未锁定状态(Unlocked)
偏向锁状态(Biased)
轻量级锁定(Lightweight Locked)
重量级锁定(Heavyweight Locked)
标记状态(Marked,仅会在垃圾回收期间出现)
[klass pointer ] 8
byte (pointer)
] values of all fields including fields from super classe
[klass pointer ] 8
byte (4 byte for compressed-oops)
] values of all fields including fields from super classes
请参考:。
下面我们来聊一聊深尺寸的计算,并考虑继承关系的影响。我们继续在32位的JVM上以 SampleClass 和 SampleBaseClass 为例。下面是 SampleClass 对象的内存布局。请再仔细看看这两个类的代码和各个字段,以便于更好的理解后续内容。
mark字段是内存布局中的第一个字(0x69e34e01),字段中包含有该对象的哈希值,还有锁状态、对象年龄之类的标志位。
klass 字段指向 SampleClass 类的定义,即0x34104cc0。
字段 s 的值是20(0×0014),s是父类 SampleBaseClass 中的字段。父类字段排在内存布局的最前面,并且不会和子类的字段交叉排列。内存布局中的父类字段会以完整的系统字长结束。在字段的结尾,如果字长为4字节会按4字节进行自动补齐;如果字长为8字节,也会按4字节进行自动补齐。两个填充字节(0×0000)可用于填充长度为4字节(一个字长)的空隙。
字段 i 的值为5(0×)。字段 l 排在字段 i 的后面,它的值为10(0x000a)。
正如上文提到的类属性顺序:首先是long和double,其次是int和float,然后是char和short,再然后是byte和boolean,最后是引用类型。属性按照各自的粒度进行对齐。当子类的第一个字段是double或者long类型,而父类并不满足8字节对齐,JVM为了填补这个空隙会破例尝试在子类的最前面摆放一个相对较短的字段。JVM会依次尝试摆放int、short和byte类型的字段,最后尝试引用类型的字段。
因此整型字段排在长整型字段的前面,字段 i 排在字段 l 的前面。
本文到此结束,我们还会带来更多的惊喜!
我们希望你喜欢本文对Java中非常酷的底层机制所做的深入探讨,希望能从中有所收获。现在你已经知道了如何利用不安全的后门直接访问内存和线程并完成一些底层操作,能够通过一个类的对象轻松的获取到这个类和这个实例的内存地址,或者是根据预定义的偏移量计算出类的内存地址。
现在你还知道如何了解一个类的内存布局,知道如何通过字段的完整对齐来最小化内存的占用。本文通篇都以 SampleClass 类为例,在保持示例一致的同时也有助于读者更好的理解本文的内容。我们还详细介绍了所有32位JVM和64位JVM相关的例子,希望本文能覆盖到更多的读者。
感谢你阅读本文!你对我们的留言将是我们前进的最大动力,请把你最真实的想法告诉我们。Twitter 。
原文链接:
- 译文链接: [ 转载请保留原文出处、译者和译文链接。]
关于作者:
(新浪微博:)
https是见过的介绍最详细的文章了
关于ImportNew
ImportNew 专注于 Java 技术分享。于日 11:11正式上线。是的,这是一个很特别的时刻 :)
ImportNew 由两个 Java 关键字 import 和 new 组成,意指:Java 开发者学习新知识的网站。 import 可认为是学习和吸收, new 则可认为是新知识、新技术圈子和新朋友……
新浪微博:
推荐微信号
反馈建议:ImportNew.
广告与商务合作QQ:
– 好的话题、有启发的回复、值得信赖的圈子
– 写了文章?看干货?去头条!
– 为IT单身男女服务的征婚传播平台
– 优秀的工具资源导航
– 活跃 & 专业的翻译小组
– 国内外的精选博客文章
– UI,网页,交互和用户体验
– JavaScript, HTML5, CSS
– 专注Android技术分享
– 专注iOS技术分享
– 专注Java技术分享
– 专注Python技术分享
& 2018 ImportNew

我要回帖

更多关于 内存 的文章

 

随机推荐