Java字节码中的一些特定处理

2014/04/19 22:58:06 No Comments

我们在阅读JAVA字节码时,应该会对其中的一些设定有疑问。为什么是这样的呢,这里从我个人的理解对其中的一些名词进行理解,同时也方便在阅读此书的读者参考。书名为《Java虚拟机规范(Java SE 7版)》

本文包括的一些描述为以下列表

  • 常量池大小设定
  • stackMapTable属性
  • new指令后的dup
  • attribute属性
  • long和double的特殊处理
  • monitorenter和monitorexit的使用场景
  • enclosingMethod属性
  • localVariableTable和localVariableTypeTable
  • constantValue属性

常量池大小设定
常量池的大小的虽然为N,但实际的常量数量即为N-1,但并不是说它的下标值为0到N-1,它的下标是从1到N-1。这里面省略了下标值为0的值,虽然字节码中没有此值。我们可以认为下标为0的值即代表某一种null值。包括其它地方进行引用时,也采用的这个位置处理。如字节码中常量池大小为26,那么最后一个下标肯定为25.

(more…)

在windows中使用Intellij Idea时选择自定义的64位JVM

2014/04/19 14:00:17 No Comments

本文英文原文自:https://intellij-support.jetbrains.com/entries/23455956-Selecting-the-JDK-version-the-IDE-will-run-under
在java开发过程中,我们一般使用32位的jdk,因为开发过程中需要频繁地重启应用,并且需要占用内存少,所以对于64位的jdk来说就没有多大的必要.但对于在开发中使用的IDE来说,却需要长时间的运行,如果能够优化IDE的运行效率,那么对于编码本身就有很大的帮助,谁也不想在编码中机器响应慢(想一下按个提示键,等半天的情况).这时候,使用64位的server版jvm就很有必要了.即IDE使用64位server版JVM,而开发使用32位jdk.

本文介绍如何在intellij idea中配置64位的jdk,以便让idea启动时使用64位的jdk,而不是默认的32位.主要介绍idea是如何查找jdk,进行配置使用的.

在idea提供的安装包中,绑定了默认的一个jdk版本,一般情况下我们只需要使用这个jdk就行了。只不过这个jdk是32位的,意味着我们只能支持双击idea.exe来启动idea。

如果需要使用64位的idea,那么需要我们自己安装一个64位的jdk,然后idea64.exe按照一定的查找规则来找到64位的jdk,如果没找到,则直接报错。当然,我们也可以通过idea.bat这个脚本来配置相应的信息,来定制这个查找过程。

(more…)

详细分解classFile常量池(HelloWorld)

2014/02/20 17:57:52 No Comments

当一个java文件被javac编译之后,就可以成为一个通用的class文件格式。我们之前说JVM里面的运行期常量池,主要也就是说的从classFile中读取的常量信息。作为JVM来说,它并没有对类,接口,实例等有一个内部的数据结构,所有的指令的引用信息均来自于常量池中。
Java Virtual Machine instructions do not rely on the run-time layout of classes, interfaces, class instances, or arrays. Instead, instructions refer to symbolic information in the constant_pool table.
本文自:http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html 4.4节

这里,我们有一个很简单的HelloWorld例子,如下所示:

public class T {
    public static void main(String args[]) {
        String s = "abcdefghijk";
        System.out.println(s);
    }
}

采用指令:javac -g:none T.java进行编译。编译之后产生的class如下所示:

Offset     0  1  2  3  4  5  6  7   8  9  A  B  C  D  E  F

00000000   CA FE BA BE 00 00 00 33  00 1A 0A 00 06 00 0C 08     3        
00000010   00 0D 09 00 0E 00 0F 0A  00 10 00 11 07 00 12 07                   
00000020   00 13 01 00 06 3C 69 6E  69 74 3E 01 00 03 28 29        <init>   ()
00000030   56 01 00 04 43 6F 64 65  01 00 04 6D 61 69 6E 01   V   Code   main 
00000040   00 16 28 5B 4C 6A 61 76  61 2F 6C 61 6E 67 2F 53     ([Ljava/lang/S
00000050   74 72 69 6E 67 3B 29 56  0C 00 07 00 08 01 00 0B   tring;)V        
00000060   61 62 63 64 65 66 67 68  69 6A 6B 07 00 14 0C 00   abcdefghijk     
00000070   15 00 16 07 00 17 0C 00  18 00 19 01 00 01 54 01                 T 
00000080   00 10 6A 61 76 61 2F 6C  61 6E 67 2F 4F 62 6A 65     java/lang/Obje
00000090   63 74 01 00 10 6A 61 76  61 2F 6C 61 6E 67 2F 53   ct   java/lang/S
000000A0   79 73 74 65 6D 01 00 03  6F 75 74 01 00 15 4C 6A   ystem   out   Lj
000000B0   61 76 61 2F 69 6F 2F 50  72 69 6E 74 53 74 72 65   ava/io/PrintStre
000000C0   61 6D 3B 01 00 13 6A 61  76 61 2F 69 6F 2F 50 72   am;   java/io/Pr
000000D0   69 6E 74 53 74 72 65 61  6D 01 00 07 70 72 69 6E   intStream   prin
000000E0   74 6C 6E 01 00 15 28 4C  6A 61 76 61 2F 6C 61 6E   tln   (Ljava/lan
000000F0   67 2F 53 74 72 69 6E 67  3B 29 56 00 21 00 05 00   g/String;)V !   
00000100   06 00 00 00 00 00 02 00  01 00 07 00 08 00 01 00                   
00000110   09 00 00 00 11 00 01 00  01 00 00 00 05 2A B7 00                *?
00000120   01 B1 00 00 00 00 00 09  00 0A 00 0B 00 01 00 09    ?             
00000130   00 00 00 17 00 02 00 02  00 00 00 0B 12 02 4C B2                 L?
00000140   00 03 2B B6 00 04 B1 00  00 00 00 00 00                   +? ?     

本文是描述如何从这个classFile中详细的找出相应的常量池信息,从文件本身描述的信息中手动地找出相应的内部结构,以方便进一步掌握class的结构形式,以方便了解JVM规范及组织。

(more…)

JVM运行期内存结构(Run-Time Data Areas)

2014/02/18 17:45:52 No Comments

这里翻译自:http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html 第2.5节运行期数据区域划分.

JVM规范定义了程序在运行期间不同数据区域的划分。其中部分数据是在JVM启动时创建,同时在JVM结束时消失。另一些则与线程相关,在线程被创建时被创建,线程结束时这部分数据被删除。
总共的区域划分为 PC寄存器线程执行栈数据堆方法区常量池本地执行栈,共6个区域。

A PC寄存器

在同一时刻,在JVM内部有多个线程在同时执行。在某一时刻,线程就会取出一条指令进行执行,那么PC寄存器就表示当前线程正在执行哪一条指令,即正在执行指令的一个引用指针,以方便进行定位和执行下一条指令。即寄存器会同时记录每个线程的执行点。如果当前线程正在执行native方法时,寄存器的值是未定义的。

B 线程执行栈

线程执行栈即表示每一个线程在执行不同的方法所创建的按方法区分的帧。就比如在调用ex.printStackTrace时,每一个调用方法就表示一个方法帧。在每个方法帧上,存储着在当前执行点上存储的一些临时变量以及下个方法返回的数据值。在具体执行时,当调用一个新方法时,就会创建一个新的执行帧,同时push到当前的执行栈中;当方法返回时,就会把当前的执行帧从执行栈中pop掉。

在具体实现时,每个执行栈的内存并没有要求一定是连续的。
在某些情况下,特别是在递归调用中,经常会报StackOverflowError的错误,就是因为创建的执行帧太多,而每个执行帧会占用一定的内存。当总执行栈超过设定值时,就会发生这个异常。比如,在Oracle JVM上,可以通过-Xss来指定每个执行栈的最大内存。

(more…)

总结java内存数据的可见性和dcl的根本问题原因

2011/08/04 00:39:14 1 Comment

    本文主要内容为网络收集,经过加工处理,参考文章见附录。
    说到java的并发编程,就必须要了解在并发中的一些数据的读取,特别是关键性数据的读取。为防止由于多线程的访问对于数据的读取产生读取顺序上的不一致,java引入了内存模型的概念,其根本概念即happen-before规则。

    happen-before规定在jvm实现内部,各个运行中的变量的读取顺序以及它对于多个线程在读取上的可见性。因为,在多个线程进行同时操作时,变量会根据每个线程保存它自己的一个副本(类似于clone),并且在必要的时候同主存(即原始的数据)同步一次。正是因为有这样的概念,在多线程中,我们看到的对象的数据值,可能就不是最新的一个数据值,而是一个过期的数据,或者说是一个错误的数据。而基于想象中正确的逻辑进行编程的话,就可能出现一些问题。
    happens-before就是“什么什么一定在什么什么之前运行”,也就是保证顺序性。即在多个线程或者同一个线程的不同操作之间,要满足说哪个操作一定会在哪个操作之前发生。那么在相对靠后的操作中使用的变量所读取的值,则一定是在操作之前写入的数据,不会读取到还未写入的值。

(more…)

JVM对于不同classLoader加载的对象之间default或protected字段的访问限制

2011/07/25 22:22:13 No Comments

    通常对于对象的default及protected访问限制,一般的说法即为只能由同包或子类才能访问。对于同包的类,是可以访问default及protected的字段的。但如下情况除外:
    如果一个由其它途径加载的类尝试访问同一个包中由其它加载器加载的类的受保护或默认级别字段时,将产生一个IllegalAccessError错误,即不允许访问指定的字段。
    即只有相同加载器之间才可以访问受保护字段。

验证程序如下所示:类Foo

public class Foo implements IFoo {
	protected int i = 2;
}

类Foo2将要访问Foo:

public class Foo2 {
	public static void redFoo() {
		Foo f = new Foo();
		System.out.println(f.i);
	}
}

    以下为执行验证的代码:

		MyClassLoader2 myClassLoader2 = new MyClassLoader2(T.class.getClassLoader());
		Class<?> clazz = myClassLoader2.loadClass("m_ylf.study.java.classLoad.Foo2");
//		Class<?> clazz2 = myClassLoader2.loadClass("m_ylf.study.java.classLoad.Foo");
		clazz.getDeclaredMethod("redFoo").invoke(null);

    在以上代码中,将由类加载器单独加载Foo2,而Foo仍由appClassLoader来加载。上面的程序即通过调用Foo2的redFoo方法来读取Foo类的受保护字段,并输出信息。执行结果如下所示:

Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
......
Caused by: java.lang.IllegalAccessError: tried to access field com.m_ylf.study.java.classLoad.Foo.i from class com.m_ylf.study.java.classLoad.Foo2
	at com.m_ylf.study.java.classLoad.Foo2.redFoo(Foo2.java:8)
	... 11 more

    即不能访问该字段,需要注意到的是,在以上的代码中,我们专门注释了使用myClassLoader加载Foo的代码。如果我们取消注释,即也叫myClassLoader来加载Foo,那么结果即能够正确的输出相对应的数字(具体输出结果就不再列出,可以单独测试)。
    需要注意的是,如果Foo中的字段i,本身就是public类型的,则该错误也不会发生。即这个约束只限制在default以及protected字段上。
    这也是为了将一个恶意的代码,加载到虚拟机,来尝试访问本来不能够被访问的信息,如标准java API中的受保护信息(标准API是由java 系统加载器来加载的)。
    以上的叙述取自《深入JVM虚拟机》,由笔者验证得出。

(more…)