当前位置: 主页 > JAVA语言

java float存储方式-java基础知识:Java内存的基本概念与进程

发布时间:2023-06-26 09:06   浏览次数:次   作者:佚名

一、 基本概念

每运行一个java程序会产生一个java进程,每个java进程可能包含一个或者多个线程,每一个Java进程对应唯一一个JVM实例,每一个JVM实例唯一对应一个堆,每一个线程有一个自己私有的栈。进程所创建的所有类的实例(也就是对象)或数组(指的是数组的本身,不是引用)都放在堆中,并由该进程所有的线程共享。Java中分配堆内存是自动初始化的,即为一个对象分配内存的时候,会初始化这个对象中变量。虽然Java中所有对象的存储空间都是在堆中分配的,但是这个对象的引用却是在栈中分配,也就是说在建立一个对象时在堆和栈中都分配内存,在堆中分配的内存实际存放这个被创建的对象的本身,而在栈中分配的内存只是存放指向这个堆对象的引用而已。局部变量 new 出来时,在栈空间和堆空间中分配空间,当局部变量生命周期结束后,栈空间立刻被回收,堆空间区域等待GC回收。

具体的概念:JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method,也叫静态区):

堆区:

1.存储的全部是对象,每个对象都包含一个与之对应的class的信息(class的目的是得到操作指令) ;

2.jvm只有一个堆区(heap),且被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身和数组本身;

栈区:

1.每个线程包含一个栈区,栈中只保存基础数据类型本身和自定义对象的引用;

2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问;

3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令);

方法区(静态区):

1.被所有的线程共享,方法区包含所有的class(class是指类的原始代码,要创建一个类的对象,首先要把该类的代码加载到方法区中,并且初始化)和static变量。

2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。

二、实例演示

AppMain.java

海量float压缩存储_float 存储范围_java float存储方式

public class AppMain //运行时, jvm 把appmain的代码全部都放入方法区 { public static void main(String[] args) //main 方法本身放入方法区。 { Sample test1 = new Sample( " 测试1 " ); //test1是引用,所以放到栈区里, Sample是自定义对象应该放到堆里面 Sample test2 = new Sample( " 测试2 " );test1.printName(); test2.printName(); } }public class Sample //运行时, jvm 把appmain的信息都放入方法区 { /** 范例名称 */ private String name; //new Sample实例后, name 引用放入栈区里, name 对应的 String 对象放入堆里/** 构造方法 */ public Sample(String name) { this .name = name; }/** 输出 */ public void printName() //在没有对象的时候,print方法跟随sample类被放入方法区里。 { System.out.println(name); } }

java程序运行时内存分配详解1

运行该程序时,首先启动一个Java虚拟机进程,这个进程首先从classpath中找到AppMain.class文件,读取这个文件中的二进制数据,然后把Appmain类的类信息存放到运行时数据区的方法区中,这就是AppMain类的加载过程。

接着,Java虚拟机定位到方法区中AppMain类的Main()方法的字节码,开始执行它的指令。这个main()方法的第一条语句就是:

复制代码 代码如下:

Sample test1=new Sample("测试1");

该语句的执行过程:

1、 Java虚拟机到方法区找到Sample类的类型信息,没有找到,因为Sample类还没有加载到方法区(这里可以看出,java中的内部类是单独存在的,而且刚开始的时候不会跟随包含类一起被加载,等到要用的时候才被加载)。Java虚拟机立马加载Sample类,把Sample类的类型信息存放在方法区里。

2、 Java虚拟机首先在堆区中为一个新的Sample实例分配内存, 并在Sample实例的内存中存放一个方法区中存放Sample类的类型信息的内存地址。

3、 JVM的进程中,每个线程都会拥有一个方法调用栈,用来跟踪线程运行中一系列的方法调用过程,栈中的每一个元素就被称为栈帧,每当线程调用一个方法的时候就会向方法栈压入一个新帧。这里的帧用来存储方法的参数、局部变量和运算过程中的临时数据。

4、位于“=”前的Test1是一个在main()方法中定义的一个变量(一个Sample对象的引用)java float存储方式,因此,它被会添加到了执行main()方法的主线程的JAVA方法调用栈中。而“=”将把这个test1变量指向堆区中的Sample实例。

5、JVM在堆区里继续创建另一个Sample实例,并在main方法的方法调用栈中添加一个Test2变量,该变量指向堆区中刚才创建的Sample新实例。

6、JVM依次执行它们的printName()方法。当JAVA虚拟机执行test1.printName()方法时,JAVA虚拟机根据局部变量test1持有的引用,定位到堆区中的Sample实例,再根据Sample实例持有的引用,定位到方法去中Sample类的类型信息,从而获得printName()方法的字节码,接着执行printName()方法包含的指令,开始执行。

三、辨析

在Java语言里堆(heap)和栈(stack)里的区别 :

java float存储方式_float 存储范围_海量float压缩存储

1. 栈(stack)与堆(heap)都是Java用来在Ram中存放数据的地方。与C++不同,Java自动管理栈和堆,程序员不能直接地设置栈或堆。

2. 栈的优势是,存取速度比堆要快,仅次于直接位于CPU中的寄存器。但缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。另外,栈数据可以共享(详见下面的介绍)。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,Java的垃圾收集器会自动收走这些不再使用的数据。但缺点是,由于要在运行时动态分配内存,存取速度较慢。

Java中的2种数据类型:

一种是基本类型(primitive types), 共有8类,即int, short, long, byte, float, double, boolean, char(注意,并没有string的基本类型)。这种类型的定义是通过诸如int a = 3; long b = 255L;的形式来定义的,称为自动变量。自动变量存的是字面值,不是类的实例,即不是类的引用,这里并没有类的存在。如int a = 3; 这里的a是一个指向int类型的引用,指向3这个字面值。这些字面值的数据,由于大小可知,生存期可知(这些字面值固定定义在某个程序块里面,程序块退出后,字段值就消失了),出于追求速度的原因,就存在于栈中。

栈有一个很重要的特性:存在栈中的数据可以共享。假设我们同时定义: int a = 3;int b = 3; 编译器先处理int a = 3;首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,如果没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。接着处理int b = 3;在创建完b的引用变量后,由于在栈中已经有3这个字面值,便将b直接指向3的地址。这样,就出现了a与b同时均指向3的情况。

这种字面值的引用与类对象的引用不同。假定两个类对象的引用同时指向一个对象,如果一个对象引用变量修改了这个对象的内部状态,那么另一个对象引用变量也即刻反映出这个变化。相反,通过字面值的引用来修改其值,不会导致另一个指向此字面值的引用的值也跟着改变的情况。如上例,我们定义完a与 b的值后,再令a=4;那么,b不会等于4,还是等于3。在编译器内部,遇到a=4;时,它就会重新搜索栈中是否有4的字面值,如果没有,重新开辟地址存放4的值;如果已经有了,则直接将a指向这个地址。因此a值的改变不会影响到b的值。

另一种是包装类数据,如Integer, String, Double等将相应的基本数据类型包装起来的类。这些类数据全部存在于堆中,Java用new()语句来显示地告诉编译器,在运行时才根据需要动态创建,因此比较灵活,但缺点是要占用更多的时间。

四、总结

java内存分配条理还是很清楚的,如果要彻底搞懂,可以去查阅JVM相关的书籍。在java中,内存分配最让人头疼的是String对象,由于其特殊性,所以很多程序员容易搞混淆,下一篇文章再详细讲解。

泛型构造器(constructor)声明

泛型类和接口

如果一个类或接口上有一个或多个类型变量,那它就是泛型。类型变量由尖括号界定,放在类或接口名的后面:

public interface List extends Collection {...}

简单的说,类型变量扮演的角色就如同一个参数,它提供给编译器用来类型检查的信息。

Java类库里的很多类,例如整个Collection框架都做了泛型化的修改。例如,我们在上面的第一段代码里用到的List接口就是一个泛型类。在那段代码里,box是一个List对象,它是一个带有一个Apple类型变量的List接口的类实现的实例。编译器使用这个类型变量参数在get方法被调用、返回一个Apple对象时自动对其进行类型转换。

实际上,这新出现的泛型标记,或者说这个List接口里的get方法是这样的:

海量float压缩存储_float 存储范围_java float存储方式

T get(int index);

get方法实际返回的是一个类型为T的对象,T是在List声明中的类型变量。

泛型方法和构造器(Constructor)

非常的相似,如果方法和构造器上声明了一个或多个类型变量,它们也可以泛型化。

public static T getFirst(List list)

这个方法将会接受一个List类型的参数,返回一个T类型的对象。你既可以使用Java类库里提供的泛型类,也可以使用自己的泛型类。类型安全的写入数据…下面的这段代码是个例子,我们创建了一个List实例,然后装入一些数据:

List str = new ArrayList(); str.add("Hello "); str.add("World.");

如果我们试图在List装入另外一种对象,编译器就会提示错误:

str.add(1);

类型安全的读取数据…

当我们在使用List对象时java float存储方式,它总能保证我们得到的是一个String对象:

String myString = str.get(0);

遍历:类库中的很多类,诸如Iterator,功能都有所增强,被泛型化。List接口里的iterator()方法现在返回的是Iterator,由它的T next()方法返回的对象不需要再进行类型转换,你直接得到正确的类型。

for (Iterator iter = str.iterator(); iter.hasNext();) {String s = iter.next();System.out.print(s);}

使用foreach,“for each”语法同样受益于泛型。前面的代码可以写出这样:

java float存储方式_float 存储范围_海量float压缩存储

for (String s: str) {System.out.print(s);}

这样既容易阅读也容易维护。

自动封装(Autoboxing)和自动拆封(Autounboxing),在使用Java泛型时,autoboxing/autounboxing这两个特征会被自动的用到,就像下面的这段代码:

List ints = new ArrayList(); ints.add(0);ints.add(1);int sum = 0;for (int i : ints) { sum += i; }

然而,你要明白的一点是,封装和解封会带来性能上的损失,所有,通用要谨慎的使用。

泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。

Java语言引入泛型的好处是安全简单。

在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的。对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是一个安全隐患。

泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。

泛型在使用中还有一些规则和限制:

1、泛型的类型参数只能是类类型(包括自定义类),不能是简单类型。

2、同一种泛型可以对应多个版本(因为参数类型是不确定的),不同版本的泛型类实例是不兼容的。

3、泛型的类型参数可以有多个。

4、泛型的参数类型可以使用extends语句,例如。习惯上成为“有界类型”。

5、泛型的参数类型还可以是通配符类型。例如Class classType = Class.forName(java.lang.String);

float 存储范围_java float存储方式_海量float压缩存储

泛型还有接口、方法等等,内容很多,需要花费一番功夫才能理解掌握并熟练应用。在此给出我曾经了解泛型时候写出的两个例子(根据看的印象写的),实现同样的功能,一个使用了泛型,一个没有使用,通过对比,可以很快学会泛型的应用,学会这个基本上学会了泛型70%的内容。

例子一:使用了泛型

public class Gen﹤T﹥ {private T ob; //定义泛型成员变量public Gen(T ob) {this.ob = ob;}public T getOb() {return ob;}public void setOb(T ob) {this.ob = ob;}public void showTyep() {System.out.println("T的实际类型是: " + ob.getClass().getName());}}public class GenDemo {public static void main(String[] args){//定义泛型类Gen的一个Integer版本Gen﹤Integer﹥ intOb=new Gen﹤Integer﹥(88);intOb.showTyep();int i= intOb.getOb();System.out.println("value= " + i);System.out.println("----------------------------------");//定义泛型类Gen的一个String版本Gen﹤String﹥ strOb=new Gen﹤String﹥("Hello Gen!");strOb.showTyep();String s=strOb.getOb();System.out.println("value= " + s);}

例子二:没有使用泛型

public class Gen2 {private Object ob; //定义一个通用类型成员public Gen2(Object ob) {this.ob = ob;}public Object getOb() {return ob;}public void setOb(Object ob) {this.ob = ob;}public void showTyep() {System.out.println("T的实际类型是: " + ob.getClass().getName());}}public class GenDemo2 {public static void main(String[] args) {//定义类Gen2的一个Integer版本Gen2 intOb = new Gen2(new Integer(88));intOb.showTyep();int i = (Integer) intOb.getOb();System.out.println("value= " + i);System.out.println("----------------------------------");//定义类Gen2的一个String版本Gen2 strOb = new Gen2("Hello Gen!");strOb.showTyep();String s = (String) strOb.getOb();System.out.println("value= " + s);}}

运行结果:

两个例子运行Demo结果是相同的,控制台输出结果如下:

T的实际类型是:

java.lang.Integer

value= 88

----------------------------------

T的实际类型是: java.lang.String

value= Hello Gen!

Process finished with exit code 0

看明白这个,以后基本的泛型应用和代码阅读就不成问题了。

以上就是对java泛型的实例分析,学习Java泛型的朋友可以参考下。