java学习笔记(二)

来源:互联网 发布:容迟网络 编辑:程序博客网 时间:2024/06/03 21:00
Java编译过程(此部分不完整,后期会更新)
Java编译过程与c/c++编译过程不同,java编译程序将java源程序编译成jvm可执行代码—java字节码(.class)
那么java在编译过程中会如何进行编译呢?
1.    Jdk根据编译参数encoding确定源代码字符集,如果不指定该参数,系统会根据操作系统的file.encodeing参数获取操作系统编码格式,国内的windows通常都是gbk。
2.    Jdk根据字符集信息,将源文件编译成java内部的unicode格式,并将编译后的内容保存到内存(在内存中又有一定规则,留下传送门)
3.    Jdk将内容保存完好的内存信息写入.class文件,生成二进制文件
那么问题来了,很多人发现自己在ide中配置源文件字符集为utf-8后再直接运行java会出现错误,就是因为不加encoding参数的编译过程会默认使用系统的字符集,而windows默认的为gbk,而在ide中编译时,ide会在编译参数中增加该参数。

如果你用ant来进行编译活动,那么要确认源码的字符集,然后在相应的ant编译任务中增加encoding参数。

再来看看c/c++的编译过程:
当c编译器编译生成一个对象代码时,该对象是为某一特定硬件平台生成的,所以在编译过程中,程序通过查表将所有对符号的引用转换为特定的内存偏移量。
而java的编译过程:
         Java编译器不对变量和方法的引用编译为数值引用,也不确定程序执行过程中的内存布局,而是将符号引用信息保存在字节码中,由解释器在运行过程中创立内存布局,然后通过查表来确定一个方法所在的地址,这也有效的保证了java的可移植性和安全性。
 --jvm工作原理:
运行jvm字符吗的工作是有解释器完成的,解释执行过程分为三步:代码装入、代码校验、代码执行。
装入代码的工作由“类装载器classlader”完成。类装载器负责装入运行一个程序需要的所有代码,也包括代码中类所继承的类和被调用的类当类装载器装入一个类时,该类被放在自己的名字空间中,除了通过符号引用自己的名字空间以外的类,类之间是隔离的(安全性)在内存分配方面,本台计算机上所有的类都在同意地址空间,而所有从外部引进的类都有一个自己独立的名字空间,这使得本地类通过共享相同的名字空间获得较高的运行效率,又保证其与外部引进的类不会相互影响。
在装载完类后,解释器便可以确定整个执行程序的内存布局。解释器为符号引用于特定的地址空间简历对应的关系查询表(有点类似于文件系统)通过在这一阶段确定代码的内布局,解决了由超类(又名父类)的改变导致子类崩溃的问题,也防止了代码的非法访问
随后,被装入的代码由字节码校验器进行检查。校验器可以发现操作数栈溢出、非法数据类型转化等多种错误。通过校验后,代码便开始执行了。
java字节码的执行有两种方式:

1)即时编译方式:解释器先将字节编译成机器码,然后再执行该机器码。 (执行次数多的代码段会用此方式,但是在Android6.0后全部都是这种执行方式,编译慢但是执行快,详情见jit)

2)解释执行方式:解释器通过每次解释并执行一小段代码来完成java字节码程序的所有操作。
Java泛型
http://blog.csdn.net/jinuxwu/article/details/6771121/
http://www.cnblogs.com/lwbqqyumidi/p/3837629.html
泛型的作用:规范化参数的类型,防止由于类型不一致导致出错。在实际使用时,由于需求的不同,可以用<T>代表泛型,在使用时可以根据实际数据类型来用实际的数据类型将泛型T替换。当使用泛型类的时候,虽然传入了不同泛型的实参,但是并没有真正意义上生成不同的类型,传入不同泛型实参的泛型类在内存上只有一个,即还是原来最基本的类型。
究其原因,在java中泛型这一概念提出的目的,导致只是作用于代码编辑阶段,在编译过程中,对于泛型结果正确性检验后,会将泛型的相关信息擦除,也就是说在class文件中是不包含任何泛型信息的。这也导致了<Number>和<integer>并不是父子关系,而其实际上是平等的,且都是<?>的子类
通配符可以通过类型通配符上限通过形如Box<? extends Number>形式定义,相对应的,类型通配符下限为Box<? super Number>形式,其含义与类型通配符上限正好相反,如此可以定义类型的上限和下限。
Jni(native)
本地库调用,主要用于调用动态链接库的代码,比如.so(linux)和.dll(windows),那么什么是动态链接库呢?与之对应的是静态链接库,静态链接库在编译期间就会在库中找到对应的方法,并用该方法来替代原有的native方法,类似于饿汉模式,而动态链接库只有在用到的时候才会被加载,类似懒汉模式。详见http://blog.csdn.net/qiuyujiaoqiuyulong/article/details/8293640

Java内存分区问题
Java将内存分为两种,一种叫做栈内存,一种是堆内存。
在函数中定义的一些基本类型的变量和对象引用的变量都是在函数的栈内存中分配。当在一段代码块中定义一个变量时,java就在这个栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间课立刻另作他用。
堆内存用于存放有new创建的对象和数组。在堆中分配的内存,由java虚拟机自动垃圾回收器来管理,在队中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量, 该变量的取值等于数组或者对象在堆内存中的首地址,在堆中这个特殊的变量就成了数组或者对象的引用变量。以后就可以在程序中使用栈内存中的引用变量来访问堆中的数组或者对象,引用变量相当于维数组或者对象起的一个别名或者代号。
引用变量是普通变量,定义时在栈中分配内存,引用变量在程序运行到作用域外释放,而数组或对象本身在堆中分配,即使程序运行到使用new产生数组和对象的语句所在的代码块之外,数组和对象本身占用的堆内存也不会被释放。数组和对象在没有引用变量指向它的时候才变成垃圾,不能再被使用,但是任然占用内存,在随后的一个不确定的时间被垃圾回收器释放,实际上栈中的变量指向堆内存中的变量,就是java中的指针。但是注意,java中的引用是只可以赋值,不可以对引用进行操作的,也就是受限的指针。
按照编译原理的观点,程序运行时的内存分配有三种策略,分别是静态的、栈式的和堆式的。
静态存储分配:在编译时就能确定每个数据目标在运行时刻的存储空间需求,因而在编译时就可以给他们分配固定的内存空间,这种分配策略要求程序代码中不允许游客便数据结构,比如说可变数组的存在,也不允许有嵌套或者递归的结构出现,因为它们都会导致编译程序无法准确计算存储空间。
栈式存储分配也可以成为动态存储分配,是由一个类似于堆栈的运行栈来实现的,和静态存储分配相反,在栈式存储方案中,程序对数据去的需求在编译时是完全未知的,只有到运行的时候才能知道,但是规定在运行中进入一个程序模块时,必须知道该模块所需的数据区大小才能够为期分配内存,和我们在 数据结构所熟知的栈一样,栈式存储分配按照先进后厨的原则进行分配
比较::::静态存储分配要求在编译时能够知道所有变量的存储要求,栈式存储分配要求在过程的入口处必须知道所有的存储要求,而堆式存储分配则专门负责在编译时或运行时模块入口处都无法确定存储要求的数据结构的内存分配,比如可变长度串和对象实例,堆由大片的可利用块或空闲块组成,堆中的内存可以按照任意顺序分配和释放。
通俗而言,堆主要是用来存放对象的,栈主要是用来执行程序的。
在编程中,例如在C/C++中,所有的方法调用都是通过栈来进行的,所有的局部变量、形式参数都是从栈中分配内存空间的。实际上也不用分配,从栈顶向上用就行,就像工厂中的传送带,Stack Pointer会自动指引你到放东西的位置,你只用吧东西放下来就行。退出函数是,修改栈指针后就可以吧栈中的内容销毁,这样的模式最快,所以用来运行程序。需要注意的是,在分配的时候,比如为一个即将要调用的程序模块分配数据区时,应事先知道这个数据区的大小,也就是说虽然分配是在程序运行时进行的,但是分配的大小是确定的,不变的,而这个是在编译时确定的。而堆的空间是动态分配的。
总而言之,就是栈内的数据是共享的,堆内的数据不是共享的
              栈更符合cpu处理的习惯,执行速度更快
              栈用于存储基本变量和句柄(引用),堆用于存储对象
              栈内的数据容量是固定的,而堆的容量可以动态规划
              对于String而言,如果是String s1=”111”这种格式,那么会存在栈中
          如果是new出来的,全都是在堆中。
                  补充一个:
String s1=new String("kvill");
  String s2=s1.intern();
  System.out.println( s1==s1.intern() ); //false        
  System.out.println( s1+" "+s2 ); //kvill kvill
  System.out.println( s2==s1.intern() );//true


再补充介绍一点:存在于.class文件中的常量池,在运行期被JVM装载,并且可以 扩充。String的 intern()方法就是扩充常量池的 一个方法;当一个String实例str调用intern()方法时,Java 查找常量池中 是否有相同Unicode的字符串常量,如果有,则返回其的引用,如果没有,则在常 量池中增加一个Unicode等于str的字符串并返回它的引用;看示例就清楚了

  示例:

  String s0= "kvill";
  String s1=new String("kvill");
  String s2=new String("kvill");
  System.out.println( s0==s1 );
  System.out.println( "**********" );
  s1.intern();
  s2=s2.intern(); //把常量池中"kvill"的引用赋给s2
  System.out.println( s0==s1);
  System.out.println( s0==s1.intern() );
  System.out.println( s0==s2 );

  结果为:false false //虽然执行了s1.intern(),但它的返回值没有赋给s1 true //说明s1.intern()返回的是常量池中"kvill"的引用 true

注意:当常量池中没有该String的内容时,如果执行了str.intern();那么会在常量池中添加一个以str内容为内容的变量,但是str的引用任然为堆中对象的引用,并没有变为常量池中对象的引用

String s1 = "ja";  
String s2 = "va";  
String s3 = "java";  
String s4 = s1 + s2;  
System.out.println(s3 == s4);//false  
System.out.println(s3.equals(s4));//true

参考:http://bbs.csdn.net/topics/290004554
http://my.oschina.net/u/1464779/blog/225590
http://uule.iteye.com/blog/1417299(特别推荐)


0 0
原创粉丝点击