我们专注攀枝花网站设计 攀枝花网站制作 攀枝花网站建设
成都网站建设公司服务热线:400-028-6601

网站建设知识

十年网站开发经验 + 多家企业客户 + 靠谱的建站团队

量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决

java留后门的代码 源码怎么留后门

java反序列化漏洞工具怎么使用

反序列化顾名思义就是用二进制的形式来生成文件,由于common-collections.jar几乎在所有项目里都会被用到,所以当这个漏洞被发现并在这个jar包内实现攻击时,几乎影响了一大批的项目,weblogic的中枪立刻提升了这个漏洞的等级(对weblogic不熟悉的可以百度)。

成都创新互联服务项目包括蠡县网站建设、蠡县网站制作、蠡县网页制作以及蠡县网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,蠡县网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到蠡县省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!

至于如何使用这个漏洞对系统发起攻击,举一个简单的例子,我通过本地java程序将一个带有后门漏洞的jsp(一般来说这个jsp里的代码会是文件上传和网页版的SHELL)序列化,将序列化后的二进制流发送给有这个漏洞的服务器,服务器会自动根据流反序列化的结果生成文件,然后就可以大摇大摆的直接访问这个生成的JSP文件把服务器当后花园了。

如果Java应用对用户输入,即不可信数据做了反序列化处理,那么攻击者可以通过构造恶意输入,让反序列化产生非预期的对象,非预期的对象在产生过程中就有可能带来任意代码执行。

所以这个问题的根源在于类ObjectInputStream在反序列化时,没有对生成的对象的类型做限制;假若反序列化可以设置Java类型的白名单,那么问题的影响就小了很多。

老师让我们做java web项目,给了一些要求,第四步的后门部分怎么做啊,我知道是在servers

你找到tomact的安装目录,就跟你安装QQ一样,总有个安装目录吧。 里面有个config目录,再里面就有sever.xml

怎么样才能写出安全的Java代码?不被黑客攻击?

在本文中,我们讨论了对付 13 种不同静态暴露的技巧。对于每种暴露,我们解释了不处理这些安全性问题所造成的影响。我们还为您推荐了一些准则,要开发不受这些静态安全性暴露威胁的、健壮且安全的 Java 应用程序,您应该遵循这些准则。一有合适的时机,我们就提供代码样本(既有暴露的代码也有无暴露的代码)。

对付高严重性暴露的技巧

请遵循下列建议以避免高严重性静态安全性暴露:

限制对变量的访问

让每个类和方法都成为 final,除非有足够的理由不这样做

不要依赖包作用域

使类不可克隆

使类不可序列化

使类不可逆序列化

避免硬编码敏感数据

查找恶意代码

限制对变量的访问

如果将变量声明为 public,那么外部代码就可以操作该变量。这可能会导致安全性暴露。

影响

如果实例变量为 public,那么就可以在类实例上直接访问和操作该实例变量。将实例变量声明为 protected 并不一定能解决这一问题:虽然不可能直接在类实例基础上访问这样的变量,但仍然可以从派生类访问这个变量。

清单 1 演示了带有 public 变量的代码,因为变量为 public 的,所以它暴露了。

清单 1. 带有 public 变量的代码

class Test {

public int id;

protected String name;

Test(){

id = 1;

name = "hello world";

}

//code

}

public class MyClass extends Test{

public void methodIllegalSet(String name){

this.name = name; // this should not be allowed

}

public static void main(String[] args){

Test obj = new Test();

obj.id = 123; // this should not be allowed

MyClass mc = new MyClass();

mc.methodIllegalSet("Illegal Set Value");

}

}

建议

一般来说,应该使用取值方法而不是 public 变量。按照具体问题具体对待的原则,在确定哪些变量特别重要因而应该声明为 private 时,请将编码的方便程度及成本同安全性需要加以比较。清单 2 演示了以下列方式来使之安全的代码:

清单 2. 不带有 public 变量的代码

class Test {

private int id;

private String name;

Test(){

id = 1;

name = "hello world";

}

public void setId(int id){

this.id = id;

}

public void setName(String name){

this.name = name;

}

public int getId(){

return id;

}

public String getName(){

return name;

}

}

让每个类和方法都为 final

不允许扩展的类和方法应该声明为 final。这样做防止了系统外的代码扩展类并修改类的行为。

影响

仅仅将类声明为非 public 并不能防止攻击者扩展类,因为仍然可以从它自己的包内访问该类。

建议

让每个类和方法都成为 final,除非有足够的理由不这样做。按此建议,我们要求您放弃可扩展性,虽然它是使用诸如 Java 语言之类的面向对象语言的主要优点之一。在试图提供安全性时,可扩展性却成了您的敌人;可扩展性只会为攻击者提供更多给您带来麻烦的方法。

不要依赖包作用域

没有显式地标注为 public、private 或 protected 的类、方法和变量在它们自己的包内是可访问的。

影响

如果 Java 包不是封闭的,那么攻击者就可以向包内引入新类并使用该新类来访问您想保护的内容。诸如 java.lang 之类的一些包缺省是封闭的,一些 JVM 也让您封闭自己的包。然而,您最好假定包是不封闭的。

建议

从软件工程观点来看,包作用域具有重要意义,因为它可以阻止对您想隐藏的内容进行偶然的、无意中的访问。但不要依靠它来获取安全性。应该将类、方法和变量显式标注为 public、private 或 protected 中适合您特定需求的那种。

使类不可克隆

克隆允许绕过构造器而轻易地复制类实例。

影响

即使您没有有意使类可克隆,外部源仍然可以定义您的类的子类,并使该子类实现 java.lang.Cloneable。这就让攻击者创建了您的类的新实例。拷贝现有对象的内存映象生成了新的实例;虽然这样做有时候是生成新对象的可接受方法,但是大多数时候是不可接受的。清单 3 说明了因为可克隆而暴露的代码:

清单 3. 可克隆代码

class MyClass{

private int id;

private String name;

public MyClass(){

id=1;

name="HaryPorter";

}

public MyClass(int id,String name){

this.id=id;

this.name=name;

}

public void display(){

System.out.println("Id ="+id+"

"+"Name="+name);

}

}

// hackers code to clone the user class

public class Hacker extends MyClass implements Cloneable {

public static void main(String[] args){

Hacker hack=new Hacker();

try{

MyClass o=(MyClass)hack.clone();

o.display();

}

catch(CloneNotSupportedException e){

e.printStackTrace();

}

}

}

建议

要防止类被克隆,可以将清单 4 中所示的方法添加到您的类中:

清单 4. 使您的代码不可克隆

public final Object clone()

throws java.lang.CloneNotSupportedException{

throw new java.lang.CloneNotSupportedException();

}

如果想让您的类可克隆并且您已经考虑了这一选择的后果,那么您仍然可以保护您的类。要做到这一点,请在您的类中定义一个为 final 的克隆方法,并让它依赖于您的一个超类中的一个非 final 克隆方法,如清单 5 中所示:

清单 5. 以安全的方式使您的代码可克隆

public final Object clone()

throws java.lang.CloneNotSupportedException {

super.clone();

}

类中出现 clone() 方法防止攻击者重新定义您的 clone 方法。

使类不可序列化

序列化允许将类实例中的数据保存在外部文件中。闯入代码可以克隆或复制实例,然后对它进行序列化。

影响

序列化是令人担忧的,因为它允许外部源获取对您的对象的内部状态的控制。这一外部源可以将您的对象之一序列化成攻击者随后可以读取的字节数组,这使得攻击者可以完全审查您的对象的内部状态,包括您标记为 private 的任何字段。它也允许攻击者访问您引用的任何对象的内部状态。

建议

要防止类中的对象被序列化,请在类中定义清单 6 中的 writeObject() 方法:

清单 6. 防止对象序列化

private final void writeObject(ObjectOutputStream out)

throws java.io.NotSerializableException {

throw new java.io.NotSerializableException("This object cannot

be serialized");

}

通过将 writeObject() 方法声明为 final,防止了攻击者覆盖该方法。

使类不可逆序列化

通过使用逆序列化,攻击者可以用外部数据或字节流来实例化类。

影响

不管类是否可以序列化,都可以对它进行逆序列化。外部源可以创建逆序列化成类实例的字节序列。这种可能为您带来了大量风险,因为您不能控制逆序列化对象的状态。请将逆序列化作为您的对象的另一种公共构造器 — 一种您无法控制的构造器。

建议

要防止对对象的逆序列化,应该在您的类中定义清单 7 中的 readObject() 方法:

清单 7. 防止对象逆序列化

private final void readObject(ObjectInputStream in)

throws java.io.NotSerializableException {

throw new java.io.NotSerializableException("This object cannot

be deserialized");

}

通过将该方法声明为 final,防止了攻击者覆盖该方法。

避免硬编码敏感数据

您可能会尝试将诸如加密密钥之类的秘密存放在您的应用程序或库的代码。对于你们开发人员来说,这样做通常会把事情变得更简单。

影响

任何运行您的代码的人都可以完全访问以这种方法存储的秘密。没有什么东西可以防止心怀叵测的程序员或虚拟机窥探您的代码并了解其秘密。

建议

可以以一种只可被您解密的方式将秘密存储在您代码中。在这种情形下,秘密只在于您的代码所使用的算法。这样做没有多大坏处,但不要洋洋得意,认为这样做提供了牢固的保护。您可以遮掩您的源代码或字节码 — 也就是,以一种为了解密必须知道加密格式的方法对源代码或字节码进行加密 — 但攻击者极有可能能够推断出加密格式,对遮掩的代码进行逆向工程从而揭露其秘密。

这一问题的一种可能解决方案是:将敏感数据保存在属性文件中,无论什么时候需要这些数据,都可以从该文件读取。如果数据极其敏感,那么在访问属性文件时,您的应用程序应该使用一些加密/解密技术。

查找恶意代码

从事某个项目的某个心怀叵测的开发人员可能故意引入易受攻击的代码,打算日后利用它。这样的代码在初始化时可能会启动一个后台进程,该进程可以为闯入者开后门。它也可以更改一些敏感数据。

这样的恶意代码有三类:

类中的 main 方法

定义过且未使用的方法

注释中的死代码

影响

入口点程序可能很危险而且有恶意。通常,Java 开发人员往往在其类中编写 main() 方法,这有助于测试单个类的功能。当类从测试转移到生产环境时,带有 main() 方法的类就成为了对应用程序的潜在威胁,因为闯入者将它们用作入口点。

请检查代码中是否有未使用的方法出现。这些方法在测试期间将会通过所有的安全检查,因为在代码中不调用它们 — 但它们可能含有硬编码在它们内部的敏感数据(虽然是测试数据)。引入一小段代码的攻击者随后可能调用这样的方法。

避免最终应用程序中的死代码(注释内的代码)。如果闯入者去掉了对这样的代码的注释,那么代码可能会影响系统的功能性。

可以在清单 8 中看到所有三种类型的恶意代码的示例:

清单 8. 潜在恶意的 Java 代码

public void unusedMethod(){

// code written to harm the system

}

public void usedMethod(){

//unusedMethod(); //code in comment put with bad intentions,

//might affect the system if uncommented

// int x = 100;

// x=x+10; //Code in comment, might affect the

//functionality of the system if uncommented

}

建议

应该将(除启动应用程序的 main() 方法之外的)main() 方法、未使用的方法以及死代码从应用程序代码中除去。在软件交付使用之前,主要开发人员应该对敏感应用程序进行一次全面的代码评审。应该使用“Stub”或“dummy”类代替 main() 方法以测试应用程序的功能。

对付中等严重性暴露的技巧

请遵循下列建议以避免中等严重性静态安全性暴露:

不要依赖初始化

不要通过名称来比较类

不要使用内部类

不要依赖初始化

您可以不运行构造器而分配对象。这些对象使用起来不安全,因为它们不是通过构造器初始化的。

影响

在初始化时验证对象确保了数据的完整性。

例如,请想象为客户创建新帐户的 Account 对象。只有在 Account 期初余额大于 0 时,才可以开设新帐户。可以在构造器里执行这样的验证。有些人未执行构造器而创建 Account 对象,他可能创建了一个具有一些负值的新帐户,这样会使系统不一致,容易受到进一步的干预。

建议

在使用对象之前,请检查对象的初始化过程。要做到这一点,每个类都应该有一个在构造器中设置的私有布尔标志,如清单 9 中的类所示。在每个非 static 方法中,代码在任何进一步执行之前都应该检查该标志的值。如果该标志的值为 true,那么控制应该进一步继续;否则,控制应该抛出一个例外并停止执行。那些从构造器调用的方法将不会检查初始化的变量,因为在调用方法时没有设置标志。因为这些方法并不检查标志,所以应该将它们声明为 private 以防止用户直接访问它们。

清单 9. 使用布尔标志以检查初始化过程

public class MyClass{

private boolean initialized = false;

//Other variables

public MyClass (){

//variable initialization

method1();

initialized = true;

}

private void method1(){ //no need to check for initialization variable

//code

}

public void method2(){

try{

if(initialized==true){

//proceed with the business logic

}

else{

throw new Exception("Illegal State Of the object");

}

}catch(Exception e){

e.printStackTrace();

}

}

}

如果对象由逆序列化进行初始化,那么上面讨论的验证机制将难以奏效,因为在该过程中并不调用构造器。在这种情况下,类应该实现 ObjectInputValidation 接口:

清单 10. 实现 ObjectInputValidation

interface java.io.ObjectInputValidation {

public void validateObject() throws InvalidObjectException;

}

所有验证都应该在 validateObject() 方法中执行。对象还必须调用 ObjectInputStream.RegisterValidation() 方法以为逆序列化对象之后的验证进行注册。 RegisterValidation() 的第一个参数是实现 validateObject() 的对象,通常是对对象自身的引用。注:任何实现 validateObject() 的对象都可能充当对象验证器,但对象通常验证它自己对其它对象的引用。RegisterValidation() 的第二个参数是一个确定回调顺序的整数优先级,优先级数字大的比优先级数字小的先回调。同一优先级内的回调顺序则不确定。

当对象已逆序列化时,ObjectInputStream 按照从高到低的优先级顺序调用每个已注册对象上的 validateObject()。

不要通过名称来比较类

有时候,您可能需要比较两个对象的类,以确定它们是否相同;或者,您可能想看看某个对象是否是某个特定类的实例。因为 JVM 可能包括多个具有相同名称的类(具有相同名称但却在不同包内的类),所以您不应该根据名称来比较类。

影响

如果根据名称来比较类,您可能无意中将您不希望授予别人的权利授予了闯入者的类,因为闯入者可以定义与您的类同名的类。

例如,请假设您想确定某个对象是否是类 com.bar.Foo 的实例。清单 11 演示了完成这一任务的错误方法:

清单 11. 比较类的错误方法

if(obj.getClass().getName().equals("Foo")) // Wrong!

// objects class is named Foo

}else{

// object's class has some other name

}

建议

在那些非得根据名称来比较类的情况下,您必须格外小心,必须确保使用了当前类的 ClassLoader 的当前名称空间,如清单 12 中所示:

清单 12. 比较类的更好方法

if(obj.getClass() == this.getClassLoader().loadClass("com.bar.Foo")){

// object's class is equal to

//the class that this class calls "com.bar.Foo"

}else{

// object's class is not equal to the class that

// this class calls "com.bar.Foo"

}

然而,比较类的更好方法是直接比较类对象看它们是否相等。例如,如果您想确定两个对象 a 和 b 是否属同一个类,那么您就应该使用清单 13 中的代码:

清单 13. 直接比较对象来看它们是否相等

if(a.getClass() == b.getClass()){

// objects have the same class

}else{

// objects have different classes

}

尽可能少用直接名称比较。

不要使用内部类

Java 字节码没有内部类的概念,因为编译器将内部类转换成了普通类,而如果没有将内部类声明为 private,则同一个包内的任何代码恰好能访问该普通类。

影响

因为有这一特性,所以包内的恶意代码可以访问这些内部类。如果内部类能够访问括起外部类的字段,那么情况会变得更糟。可能已经将这些字段声明为 private,这样内部类就被转换成了独立类,但当内部类访问外部类的字段时,编译器就将这些字段从专用(private)的变为在包(package)的作用域内有效的。内部类暴露了已经够糟糕的了,但更糟糕的是编译器使您将某些字段成为 private 的举动成为徒劳。

建议 如果能够不使用内部类就不要使用内部类。

对付低严重性暴露的技巧

请遵循下列建议以避免低严重性静态安全性暴露:

避免返回可变对象

检查本机方法

避免返回可变对象

Java 方法返回对象引用的副本。如果实际对象是可改变的,那么使用这样一个引用调用程序可能会改变它的内容,通常这是我们所不希望见到的。

影响

请考虑这个示例:某个方法返回一个对敏感对象的内部数组的引用,假定该方法的调用程序不改变这些对象。即使数组对象本身是不可改变的,也可以在数组对象以外操作数组的内容,这种操作将反映在返回该数组的对象中。如果该方法返回可改变的对象,那么事情会变得更糟;外部实体可以改变在那个类中声明的 public 变量,这种改变将反映在实际对象中。

清单 14 演示了脆弱性。getExposedObj() 方法返回了 Exposed 对象的引用副本,该对象是可变的:

清单 14. 返回可变对象的引用副本

class Exposed{

private int id;

private String name;

public Exposed(){

}

public Exposed(int id, String name){

this.id = id;

this.name = name;

}

public int getId(){

return id;

}

public String getName(){

return name;

}

public void setId(int id){

this.id=id;

}

public void setName(String name){

this.name = name;

}

public void display(){

System.out.println("Id = "+ id + " Name = "+ name);

}

}

public class Exp12{

private Exposed exposedObj = new Exposed(1,"Harry Porter");

public Exposed getExposedObj(){

return exposedObj; //returns a reference to the object.

}

public static void main(String[] args){

Exp12 exp12 = new Exp12();

exp12.getExposedObj().display();

Exposed exposed = exp12.getExposedObj();

exposed.setId(10);

exposed.setName("Hacker");

exp12.getExposedObj().display();

}

}

建议

如果方法返回可改变的对象,但又不希望调用程序改变该对象,请修改该方法使之不返回实际对象而是返回它的副本或克隆。要改正清单 14 中的代码,请让它返回 Exposed 对象的副本,如清单 15 中所示:

清单 15. 返回可变对象的副本

public Exposed getExposedObj(){

return new Exposed(exposedObj.getId(),exposedObj.getName());

}

或者,您的代码也可以返回 Exposed 对象的克隆。

检查本机方法

本机方法是一种 Java 方法,其实现是用另一种编程语言编写的,如 C 或 C++。有些开发人员实现本机方法,这是因为 Java 语言即使使用即时(just-in-time)编译器也比许多编译过的语言要慢。其它人需要使用本机代码是为了在 JVM 以外实现特定于平台的功能。

影响

使用本机代码时,请小心,因为对这些代码进行验证是不可能的,而且本机代码可能潜在地允许 applet 绕过通常的安全性管理器(Security Manager)和 Java 对设备访问的控制。

建议

如果非得使用本机方法,那么请检查这些方法以确定:

它们返回什么

它们获取什么作为参数

它们是否绕过安全性检查

它们是否是 public、private 等等

它们是否含有绕过包边界从而绕过包保护的方法调用

结束语

编写安全 Java 代码是十分困难的,但本文描述了一些可行的实践来帮您编写安全 Java 代码。这些建议并不能解决您的所有安全性问题,但它们将减少暴露数目。最佳软件安全性实践可以帮助确保软件正常运行。安全至关重要和高可靠系统设计者总是花费大量精力来分析和跟踪软件行为。只有通过将安全性作为至关紧要的系统特性来对待 — 并且从一开始就将它构建到应用程序中,我们才可以避免亡羊补牢似的、修修补补的安全性方法。

参考资料

请通过单击文章顶部或底部的讨论来参加本文的论坛。

了解关于 Java 安全性 API 的更多知识。

developerWorks 安全专题上通常含有有关计算机安全性的优秀资源。

Larry Koved、 Anthony J. Nadalin、Don Neal 和 Tim Lawson 合作编写的 “The evolution of Java security”(developerWorks,1998 年)对 Java 语言的安全性模型早期开发进行了深入探讨。

Sing Li 在他的 Java 安全性系列文章(由两部分组成的)(developerWorks, 2001 年 2 月)中向开发人员显示:尽管社区可能不得不重新考虑 Java 2 中的安全性设计,还是出现了只对开发人员有帮助,可以满足他们的需求的一致的进展:

第一部分

第二部分

John Viega、Tom Mutdosch、 Gary McGraw 和 Ed Felten 合著的 “Statically scanning Java code for security vulnerabilities” (IEEE Software,2000 年 9 月)介绍了一种 Java 工具,可以使用该工具来检查您的 Java 代码中的安全性漏洞。

G. McGraw 和 E. Felten 合作编写的 Securing Java: Getting Down to Business with Mobile Code(John Wiley 和 Sons,1998 年)深入涵盖了 Java 安全性。(文档是 PDF 格式的。)

定期检查 IBM 研究 Java 安全页面以便 IBM 在安全性领域的创新有重要发展时能够跟踪这一创新。

如果您的 Java 代码运行在 S/390 系统上,那么您将需要查阅 S/390 Java 安全页面以获取额外的信息。

关于作者

Bijaya Nanda Sahu 是就职于印度 IBM Global Services 的软件工程师。他从事过各种因特网技术和框架(J2EE、WSBCC、JADE)、 WebSphere 相关技术、UML 和 OOAD 方面的工作。目前,他从事因特网银行安全性问题方面的工作,重点在 WebSphere Application Server 和 Portal Server 上。可以通过 bijaya.sahu@in.ibm.com 和他联系

怎么确定Java对象的大小

首先,我们先写一段大家可能不怎么写或者认为不可能的代码:一个类中,几个类型都是private类型,没有public方法,如何对这些属性进行读写操作,看似不可能哦,为什么,这违背了面向对象的封装,其实在必要的时候,留一道后门可以使得语言的生产力更加强大,对象的序列化不会因为没有public方法就无法保存成功吧,OK,我们简单写段代码开个头,逐步引入到怎么样去测试对象的大小,一下代码非常简单,相信不用我解释什么:

import java.lang.reflect.Field;

class NodeTest1 {

private int a = 13;

private int b = 21;

}

public class Test001 {

public static void main(String []args) {

NodeTest1 node = new NodeTest1();

Field []fields = NodeTest1.class.getDeclaredFields();

for(Field field : fields) {

field.setAccessible(true);

try {

int i = field.getInt(node);

field.setInt(node, i * 2);

System.out.println(field.getInt(node));

} catch (IllegalArgumentException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

}

}

}

}

代码最基本的意思就是:实例化一个NodeTest1这个类的实例,然后取出两个属性,分别乘以2,然后再输出,相信大家会认为这怎么可能,NodeTest1根本没有public方法,代码就在这里,将代码拷贝回去运行下就OK了,OK,现在不说这些了,运行结果为:

26

42

为什么可以取到,是每个属性都留了一道门,主要是为了自己或者外部接入的方便,相信看代码自己仔细的朋友,应该知道门就在:field.setAccessible(true);代表这个域的访问被打开,好比是一道后门打开了,呵呵,上面的方法如果不设置这个,就直接报错。

看似和对象大小没啥关系,不过这只是抛砖引玉,因为我们首先要拿到对象的属性,才能知道对象的大小,对象如果没有提供public方法我们也要知道它有哪些属性,所以我们后面多半会用到这段类似的代码哦!

对象测量大小的方法关键为java提供的(1.5过后才有):java.lang.instrument.Instrumentation,它提供了丰富的对结构的等各方面的跟踪和对象大小的测量的API(本文只阐述对象大小的测量方法),于是乎我心喜了,不过比较恶心的是它是实例化类:sun.instrument.IntrumentationImpl是sun开头的,这个鬼东西有点不好搞,翻开源码构造方法是private类型,没有任何getInstance的方法,写这个类干嘛?看来这个只能被JVM自己给初始化了,那么怎么将它自己初始化的东西取出来用呢,唯一能想到的就是agent代理,那么我们先抛开代理,首先来写一个简单的对象测量方法:

步骤1:(先创建一个用于测试对象大小的处理类)

import java.lang.instrument.Instrumentation;

public class MySizeOf {

private static Instrumentation inst;

/**

*这个方法必须写,在agent调用时会被启用

*/

public static void premain(String agentArgs, Instrumentation instP) {

inst = instP;

}

/**

* 直接计算当前对象占用空间大小,包括:当前类及超类的基本类型实例字段大小

* 引用类型实例字段引用大小、实例基本类型数组总占用空间、实例引用类型数组引用本身占用空间大小

* 但是不包括超类继承下来的和当前类声明的实例引用字段的对象本身的大小、实例引用数组引用的对象本身的大小

* 用来测量java对象的大小(这里先理解这个大小是正确的,后面再深化)

*/

public static long sizeOf(Object o) {

if(inst == null) {

throw new IllegalStateException("Can not access instrumentation environment.\n" +

"Please check if jar file containing SizeOfAgent class is \n" +

"specified in the java's \"-javaagent\" command line argument.");

}

return inst.getObjectSize(o);

}

}

步骤2:上面我们写好了agent的代码,此时我们要将上面这个类编译后打包为一个jar文件,并且在其包内部的META-INF/MANIFEST.MF文件中增加一行:Premain-Class: MySizeOf代表执行代理的全名,这里的类名称是没有package的,如果你有package,那么就写全名,我们这里假设打包完的jar包名称为agent.jar(打包过程这里简单阐述,就不细说了),OK,继续向下走:

步骤3:编写测试类,测试类中写:

public class TestSize {

public static void main(String []args) {

System.out.println(MySizeOf.sizeOf(new Integer(1)));

System.out.println(MySizeOf.sizeOf(new String("a")));

System.out.println(MySizeOf.sizeOf(new char[1]));

}

}

下一步准备运行,运行前我们准备初步估算下结果是什么,目前我是在32bit模式下运行jvm(注意,不同位数的JVM参数设置不一样,对象大小也不一样大)。

(1) 首先看Integer对象,在32bit模式下,class区域占用4byte,mark区域占用最少4byte,所以最少8byte头部,Integer内部有一个int类型的数据,占4个byte,所以此时为8+4=12,java默认要求按照8byte对象对其,所以对其到16byte,所以我们理论结果第一个应该是16;

(2) 再看String,长度为1,String对象内部本身有4个非静态属性(静态属性我们不计算空间,因为所有对象都是共享一块空间的),4个非静态属性中,有offset、count、hash为int类型,分别占用4个byte,char value[]为一个指针,指针的大小在bit模式下或64bit开启指针压缩下默认为4byte,所以属性占用了16byte,String本身有8byte头部,所以占用了24byte;其次,一个String包含了子对象char数组,数组对象和普通对象的区别是需要用一个字段来保存数组的长度,所以头部变成12byte,java中一个char采用UTF-16编码,占用2个byte,所以是14byte,对其到16byte,24+16=40byte;

(3) 第三个在第二个基础上已经分析,就是16byte大小;

也就是理论结果是:16、40、16;

步骤4:现在开始运行代码:运行代码前需要保证classpath把刚才的agent.jar包含进去:

D:javac TestSize.java

D:java -javaagent:agent.jar TestSize

16

24

16

第一个和第三个结果一致了,不过奇怪了,第二个怎么是24,不是40,怎么和理论结果偏差这么大,再回到理论结果中,有一个24曾经出现过,24是指String而不包含char数组的空间大小,那么这么算还真是对的,可见,java默认提供的方法只能测量对象当前的大小,如果要测量这个对象实际的大小(也就是包含了子对象,那么就需要自己写算法来计算了,最简单的方法就是递归,不过递归一项是我不喜欢用的,无意中在一个地方看到有人用栈写了一个代码写得还不错,自己稍微改了下,就是下面这种了)。

import java.lang.instrument.Instrumentation;

import java.lang.reflect.Array;

import java.lang.reflect.Field;

import java.lang.reflect.Modifier;

import java.util.IdentityHashMap;

import java.util.Map;

import java.util.Stack;

public class MySizeOf {

static Instrumentation inst;

public static void premain(String agentArgs, Instrumentation instP) {

inst = instP;

}

public static long sizeOf(Object o) {

if(inst == null) {

throw new IllegalStateException("Can not access instrumentation environment.\n" +

"Please check if jar file containing SizeOfAgent class is \n" +

"specified in the java's \"-javaagent\" command line argument.");

}

return inst.getObjectSize(o);

}

/**

* 递归计算当前对象占用空间总大小,包括当前类和超类的实例字段大小以及实例字段引用对象大小

*/

public static long fullSizeOf(Object obj) {//深入检索对象,并计算大小

MapObject, Object visited = new IdentityHashMapObject, Object();

StackObject stack = new StackObject();

long result = internalSizeOf(obj, stack, visited);

while (!stack.isEmpty()) {//通过栈进行遍历

result += internalSizeOf(stack.pop(), stack, visited);

}

visited.clear();

return result;

}

//判定哪些是需要跳过的

private static boolean skipObject(Object obj, MapObject, Object visited) {

if (obj instanceof String) {

if (obj == ((String) obj).intern()) {

return true;

}

}

return (obj == null) || visited.containsKey(obj);

}

private static long internalSizeOf(Object obj, StackObject stack, MapObject, Object visited) {

if (skipObject(obj, visited)) {//跳过常量池对象、跳过已经访问过的对象

return 0;

}

visited.put(obj, null);//将当前对象放入栈中

long result = 0;

result += sizeOf(obj);

Class ?clazz = obj.getClass();

if (clazz.isArray()) {//如果数组

if(clazz.getName().length() != 2) {// skip primitive type array

int length = Array.getLength(obj);

for (int i = 0; i length; i++) {

stack.add(Array.get(obj, i));

}

}

return result;

}

return getNodeSize(clazz , result , obj , stack);

}

//这个方法获取非数组对象自身的大小,并且可以向父类进行向上搜索

private static long getNodeSize(Class ?clazz , long result , Object obj , StackObject stack) {

while (clazz != null) {

Field[] fields = clazz.getDeclaredFields();

for (Field field : fields) {

if (!Modifier.isStatic(field.getModifiers())) {//这里抛开静态属性

if (field.getType().isPrimitive()) {//这里抛开基本关键字(因为基本关键字在调用java默认提供的方法就已经计算过了)

continue;

}else {

field.setAccessible(true);

try {

Object objectToAdd = field.get(obj);

if (objectToAdd != null) {

stack.add(objectToAdd);//将对象放入栈中,一遍弹出后继续检索

}

} catch (IllegalAccessException ex) {

assert false;

}

}

}

}

clazz = clazz.getSuperclass();//找父类class,直到没有父类

}

return result;

}

}


当前文章:java留后门的代码 源码怎么留后门
标题链接:http://shouzuofang.com/article/doccjse.html

其他资讯