危险代码:内存中的Java类和对象为何变得不安全—Part1

本文深入探讨了Java类和对象在内存中的结构与布局,包括对象占用的空间、属性布局等细节,并介绍了如何利用sun.misc.Unsafe进行内存操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文由 ImportNew - 吴际 翻译自 zeroturnaround。如需转载本文,请先参见文章末尾处的转载要求。

让我们开始展示内存中Java类和对象结构

你可曾好奇过Java内存管理核心构件?你是否问过自己某些奇怪的问题,比如:

  • 一个类在内存中占据多少空间?
  • 我的对象在内存中消耗了多少空间?
  • 对象的属性在内存中是如何被布局的?

如果这些问题听起来很熟悉,那么你就想到了点子上。对于像我们这样的在RebelLabs的Java极客来说,这些难解的谜题已经在我们脑海中缠绕了很长时间:如果你对探究类检测器感兴趣,想知道如何布局让所有的类更容易地从内存中取到指定变量,或是想在系统运行时侵入内存中的这些字段。这就意味着你能切实改变内存中的数据甚至是代码!

其它可能勾起你兴趣的知识点有,“堆外缓存”和“高性能序列化”的实现。这是一对构建在对象缓存结构上很好的实例,揭示了获取类和实例内存地址的方法,缓存中类和实例的布局以及关于对象成员变量布局的详细解释。我们希望尽可能简单地阐释这些内容,但是尽管如此这篇文章并不适合Java初学者,它要求具备对Java编程原理有一定的了解。

注意:下面关于类和对象的布局所写的内容特指Java SE 7,所以不推荐使用者想当然地认为这些适用于过去或将来的Java版本。方便起见,我们在GitHub项目上发布了这篇文章的示例代码,可以在这里找到 https://github.com/serkan-ozal/ocean-of-memories/tree/master/src/main/java/com/zeroturnaround/rebellabs/oceanofmemories/article1

在Java中最直接的内存操作方法是什么?

Java最初被设计为一种安全的受控环境。尽管如此,Java HotSpot还是包含了一个“后门”,提供了一些可以直接操控内存和线程的低层次操作。这个后门类——sun.misc.Unsafe——被JDK广泛用于自己的包中,如java.niojava.util.concurrent。但是丝毫不建议在生产环境中使用这个后门。因为这个API十分不安全、不轻便、而且不稳定。这个不安全的类提供了一个观察HotSpot JVM内部结构并且可以对其进行修改。有时它可以被用来在不适用C++调试的情况下学习虚拟机内部结构,有时也可以被拿来做性能监控和开发工具。

为何变得不安全

sun.misc.Unsafe这个类是如此地不安全,以至于JDK开发者增加了很多特殊限制来访问它。它的构造器是私有的,工厂方法getUnsafe()的调用器只能被Bootloader加载。如你在下面代码片段的第8行所见,这个家伙甚至没有被任何类加载器加载,所以它的类加载器是null。它会抛出SecurityException 异常来阻止侵入者。

publicfinal class Unsafe {
   ...
   privateUnsafe() {}
   privatestatic final Unsafe theUnsafe = newUnsafe();
   ...
   publicstatic Unsafe getUnsafe() {
      Class cc = sun.reflect.Reflection.getCallerClass(2);
      if(cc.getClassLoader() != null)
          thrownew SecurityException("Unsafe");
      returntheUnsafe;
   }
   ...
}

幸运的是这里有一个Unsafe的变量可以被用来取得Unsafe的实例。我们可以轻松地编写一个复制方法通过反射来实现,如下所示:
http://highlyscalable.wordpress.com/2012/02/02/direct-memory-access-in-java/

publicstatic Unsafe getUnsafe() {
   try{
           Field f = Unsafe.class.getDeclaredField("theUnsafe");
           f.setAccessible(true);
           return(Unsafe)f.get(null);
   }catch(Exception e) { 
       /* ... */
   }
}

Unsafe一些有用的特性

  1. 虚拟机“集约化”(VM intrinsification):如用于无锁Hash表中的CAS(比较和交换)。再比如compareAndSwapInt这个方法用JNI调用,包含了对CAS有特殊引导的本地代码。在这里你能读到更多关于CAS的信息:http://en.wikipedia.org/wiki/Compare-and-swap
  2. 主机虚拟机(译注:主机虚拟机主要用来管理其他虚拟机。而虚拟平台我们看到只有guest VM)的sun.misc.Unsafe功能能够被用于未初始化的对象分配内存(用allocateInstance方法),然后将构造器调用解释为其他方法的调用。
  3. 你可以从本地内存地址中追踪到这些数据。使用java.lang.Unsafe类获取内存地址是可能的。而且可以通过unsafe方法直接操作这些变量!
  4. 使用allocateMemory方法,内存可以被分配到堆外。例如当allocateDirect方法被调用时DirectByteBuffer构造器内部会使用allocateMemory
  5. arrayBaseOffsetarrayIndexScale方法可以被用于开发arraylets,一种用来将大数组分解为小对象、限制扫描的实时消耗或者在大对象上做更新和移动。

在下一篇中,由于可以在类中获取内存地址,我们将给出一些使用“Unsafe”的实例


原文链接: zeroturnaround 翻译: ImportNew.com 吴际
译文链接: http://www.importnew.com/7844.html
转载请保留原文出处、译者和译文链接。]


### Java 中复数的设计与实现 #### 定义复数 `Complex` 为了在Java中处理复数及其运算,可以定义一个名为`Complex`的来封装复数的概念。此应包含表示实部(real part)虚部(imaginary part)的私有字段,并提供公共的方法来进行基本算术操作。 ```java public class Complex { private double realPart; // 实部 private double imagPart; // 虚部 public Complex(double r, double i){ this.realPart = r; this.imagPart = i; } // 获取实部 public double getReal(){ return this.realPart; } // 设置实部 public void setReal(double value){ this.realPart = value; } // 获取虚部 public double getImag(){ return this.imagPart; } // 设置虚部 public void setImag(double value){ this.imagPart = value; } } ``` 上述代码展示了如何声明并初始化一个带有参数化构造函数的`Complex`[^1]。此构造函数允许创建新的复数值时指定其实部虚部。 #### 添加复数运算功能 为了让`Complex`支持常见的数学运算,比如加法、减法等,可以在该内增加相应的方法: ```java // 加法 public Complex add(Complex other){ return new Complex(this.getReal() + other.getReal(), this.getImag() + other.getImag()); } // 减法 public Complex subtract(Complex other){ return new Complex(this.getReal() - other.getReal(), this.getImag() - other.getImag()); } // 乘法 public Complex multiply(Complex other){ double re = (this.getReal()*other.getReal())-(this.getImag()*other.getImag()); double im = (this.getReal()*other.getImag())+(this.getImag()*other.getReal()); return new Complex(re,im); } @Override public String toString(){ return "("+getReal()+", "+getImag()+"i)"; } ``` 这些方法实现了两个复数之间的四则运算逻辑,并返回一个新的`Complex`对象作为结果[^3]。特别注意的是,在重写`toString()`方法之后,可以直接打印出复数的形式,便于调试或展示给用户查看。 #### 使用示例 下面是一个简单的例子,说明了怎样利用上面定义好的`Complex`执行一些基础的操作: ```java public static void main(String[] args){ Complex c1 = new Complex(3.0,-2.0); // 创建第一个复数c1=3-2i Complex c2 = new Complex(-1.0,4.0); // 创建第二个复数c2=-1+4i System.out.println("C1+C2="+c1.add(c2)); // 输出两者的 System.out.println("C1-C2="+c1.subtract(c2)); // 输出两者之差 System.out.println("C1*C2="+c1.multiply(c2)); // 输出两者积的结果 } ``` 这段程序会计算并显示两个特定复数间的加法、减法及乘法的结果[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值