您的位置:首页 > 编程语言 > Java开发

JAVA虚拟机与内存

2016-05-26 11:43 295 查看

资料整理自网络(侵删)

JVM内存

组成
JAVA的JVM的内存可分为3个区:堆(heap)、栈(stack)和方法区(method)

栈区:

1.每个线程包含一个栈区,栈中只保存基础数据类型的对象和自定义对象的引用(不是对象),对象都存放在堆区中
2.每个栈中的数据(原始类型和对象引用)都是私有的,其他栈不能访问。
3.栈分为3个部分:基本类型变量区、执行环境上下文、操作指令区(存放操作指令)。

堆区:

1.存储的全部是对象,每个对象都包含一个与之对应的class的信息。(class的目的是得到操作指令)
2.jvm只有一个堆区(heap)被所有线程共享,堆中不存放基本类型和对象引用,只存放对象本身

方法区:

1.又叫静态区,跟堆一样,被所有的线程共享。方法区包含所有的class和static变量。
2.方法区中包含的都是在整个程序中永远唯一的元素,如class,static变量。
为了更清楚地搞明白发生在运行时数据区里的黑幕,我们来准备2个小道具(2个非常简单的小程序)。

Java中通过多线程机制使得多个任务同时执行处理,所有的线程共享JVM内存区域main memory,而每个线程又单独的有自己的工作内存,当线程与内存区域进行交互时,数据从主存拷贝到工作内存,进而交由线程处理(操作码+操作数)

JVM的逻辑内存模型:



程序计数器

程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看做是当前线程所执行的字节码的行号指示器。在虚拟机的概念模型里(仅是概念模型,各种虚拟机可能会通过一些更高效的方式去实现),字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。

Java 虚拟机栈

存储局部变量表、操作栈、动态链接、方法出口等信息。局部变量表存放了编译期可知的各种基本数据类型(boolean、byte、char、short、int、float、long、double)、对象引用和returnAddress 类型(指向了一条字节码指令的地址)

如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError 异常;如果虚拟机栈可以动态扩展
(当前大部分的Java 虚拟机都可动态扩展,只不过Java 虚拟机规范中也允许固定长度的虚拟机栈),当扩展时无法申请到足够的内存时会抛出OutOfMemoryError 异常。

本地方法栈

本地方法栈(Native Method Stacks)与虚拟机栈所发挥的作用是非常相似的,其区别不过是虚拟机栈为虚拟机执行Java 方法(也就是字节码)服务,而本地方法栈则是为虚拟机使用到的Native 方法服务。本地方法栈区域也会抛出StackOverflowError 和OutOfMemoryError异常。

Java 堆

所有的对象实例以及数组都要在堆上分配(但是随着JIT 编译器的发展与逃逸分析技术的逐渐成熟,栈上分配、标量替换②优化技术将会导致一些微妙的变化发生,所有的对象都分配在堆上也渐渐变得不是那么“绝对”了。)

Java 堆是垃圾收集器管理的主要区域,因此很多时候也被称做“GC 堆”(Garbage Collected Heap)。Java 堆中还可以细分为:新生代和老年代;再细致一点的有Eden 空间、From Survivor 空间、To Survivor 空间等。

方法区

HotSpot 虚拟机中的 “永久代”(Permanent Generation),除了和Java 堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾收集,相对而言,垃圾收集行为在这个区域是比较少出现的,。这个区域的内存回收目标主要是针对常量池的回收和对类型的卸载

运行时常量池(Runtime Constant Pool)是方法区的一部分。Class 文件中除了有类的版本、字段、方法、接口等描述等信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放到方法区的运行时常量池中。

加载运行机制

创建对象
Object obj = new Object();

“Object obj”这部分的语义将会反映到Java 栈的本地变量表中,作为一个reference 类型数据出现

“new Object()”这部分的语义将会反映到Java 堆中,形成一块存储了Object 类型所有实例数据值(Instance Data,对象中各个实例字段的数据)的结构化内存,根据具体类型以及虚拟机实现的对象内存布局(Object Memory Layout)的不同,这块内存的长度是不固定的。另外,在Java 堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现的接口、方法等)的地址信息,这些类型数据则存储在方法区中。

访问:

使用句柄访问:



直接指针访问:



[b]局部变量[/b]

局部变量是指在方法中声明的变量:当变量为基本类型时,存储的是变量的值;当变量为引用类型时,存储的是变量指向所指向地址的值。

局部变量的生命周期

Java程序从开始到结束会有很多次的方法调用,在方法调用时,JVM会为每一个方法在栈中分配一个空间:栈帧,一个栈帧对应一个正在调用的方法,用于存放方法中的局部变量和参数等数据,当方法调用结束时,JVM会吧栈帧清除出去。

成员变量和局部变量的差别

局部变量:-定义在方法中 -没有默认值,必须设定初始值 -方法调用是,存在栈中,方法调用结束,从栈中清除
成员变量:-定义在类中 -有默认值,可以不显示初始化 -所有类实例化,存在堆中,对象被收回时,成员变量失效
(参考来源:http://blog.csdn.net/huayizhixing/article/details/41145779

类的加载
方法区用于存放类的信息,Java程序运行时,首先通过类装载器载入类文件的字节码信息,经过解析后将其装入方法区。
方法只有一份:当类的信息装载到方法区中,除了类的信息外,同时类的方法定义也被加载到方法区。类在实例化对象时,多个对象在堆中有各自的空间,但所有的实例化对象时公用在方法区中的一份方法定义。

初始化机制

类初始化

什么时候类加载:第一次需要使用类信息时加载。(虚拟机在首次加载Java类时,会对静态初始化块、静态成员变量、静态方法进行一次初始化)

类加载的原则:延迟加载,能不加载就不加载。

静态内部类字段初始化

-JAVA中静态内部类字段什么时候初始化?是在外部类加载的时候就初始化吗?(单例模式登记式/静态内部类实现的原理)

-不是的,只有在加载内部类的时候才初始化

登记式/静态内部类(lazy初始化、多线程安全)(利用类创建实例自动处理多线程安全;Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化 instance)

publicclassSingleton{

privatestaticclassSingletonHolder{

privatestatic final Singleton INSTANCE =newSingleton();

}

privateSingleton(){}

publicstatic final Singleton getInstance(){

returnSingletonHolder.INSTANCE;

}

}


类的加载过程

顺序是:父类静态属性-》父类静态代码块-》子类静态变量-》子类静态代码块-》父类非静态变量-》父类非静态代码块-》父类构造函数-》子类非静态变量-》子类非静态代码块-》-》子类构造函数 (这样的加载顺序不是绝对的 因为静态变量和静态代码块跟声明顺序有关)

有父类的情况:
1. 加载父类
1.1 为静态属性分配存储空间并赋初始值
1.2 执行静态初始化块和静态初始化语句(从上至下)
2. 加载子类
2.1 为静态属性分配存储空间
2.2 执行静态初始化块和静态初始化语句(从上至下)
3. 加载父类构造器
3.1 为实例属性分配存数空间并赋初始值
3.2 执行实例初始化块和实例初始化语句
3.3 执行构造器内容
4. 加载子类构造器
4.1 为实例属性分配存数空间并赋初始值
4.2 执行实例初始化块和实例初始化语句
4.3 执行构造器内容
5 回到main()
内部类的加载过程也一样

对象初始化

《疯狂Java程序员的基本修养》





JAVA垃圾回收机制

垃圾回收机制
Java中那些不可达的对象就会变成垃圾
算法:引用计数法、停止-复制(stop and copy)、标记-清扫(mark and sweep)
分代回收:



内存泄漏
java heap的内存泄漏:
如果java heap容量扩充到上限,并且在GC后仍然没有足够的空间分配新的java对象,便会抛出out of memory异常,导致JVM进程崩溃

编程技巧

根据GC的工作原理,我们可以通过一些技巧和方式,让GC运行更加有效率,更加符合应用程序的要求。一些关于程序设计的几点建议:

1.最基本的建议就是尽早释放无用对象的引用。大多数程序员在使用临时变量的时候,都是让引用变量在退出活动域(scope)后,自动设置为 null.我们在使用这种方式时候,必须特别注意一些复杂的对象图,例如数组,队列,树,图等,这些对象之间有相互引用关系较为复杂。对于这类对象,GC 回收它们一般效率较低。如果程序允许,尽早将不用的引用对象赋为null.这样可以加速GC的工作。

2.尽量少用finalize函数。finalize函数是Java提供给程序员一个释放对象或资源的机会。但是,它会加大GC的工作量,因此尽量少采用finalize方式回收资源。

3.如果需要使用经常使用的图片,可以使用soft应用类型。它可以尽可能将图片保存在内存中,供程序调用,而不引起OutOfMemory.

4.注意集合数据类型,包括数组,树,图,链表等数据结构,这些数据结构对GC来说,回收更为复杂。另外,注意一些全局的变量,以及一些静态变量。这些变量往往容易引起悬挂对象(dangling reference),造成内存浪费。

5.当程序有一定的等待时间,程序员可以手动将你认为不需要的数据设为null,然后执行System.gc(),通知GC运行,但是Java语言规范并不保证GC一定会执行。使用增量式GC可以缩短Java程序的暂停时间。(可能会影响效率)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: