从面试题看问题之JVM和内存

来源:互联网 发布:淘宝盗图投诉多久生效 编辑:程序博客网 时间:2024/06/08 10:15

原文:http://mp.weixin.qq.com/s/9WW912UrDPRQUG8260mKTA


什么是Java虚拟机?

为什么Java被称作是“平台无关的编程语言”?


Java虚拟机是一个可以执行Java字节码的虚拟机进程。Java源文件被编译成能被Java虚拟机执行的字节码文件(.class文件)。


Java被设计成允许应用程序可以运行在任意的平台,而不需要程序员为每一个平台单独重写或者是重新编译。Java虚拟机让这个变为可能,因为它知道底层硬件平台的指令长度和其他特性。


下图为JVM的物理结构:



JAVA中的 JDK,JRE,JVM有什么区别?



java中被编译后的.class并不直接与机器的操作系统相对应,而是经过虚拟机间接与操作系统交互,由虚拟机将程序解释给本地系统执行。   


 只有JVM还不能成class的执行,因为在解释class的时候JVM需要调用解释所需要的类库lib,而jre包含lib类库。


Java Runtime Environment(JRE):java运行时环境,通过它,Java的开发者才得以将自己开发的程序发布到用户手中,让用户使用。它包括java虚拟机,及Java核心,和其他java运行时的必备组件,以及一些其他插件。


JDK(Java Development Kit):java开发工具包:是完整的Java软件开发包,包含了JRE,编译器和其他的工具(比如:JavaDoc,Java调试器),可以让开发者开发、编译、执行Java应用程序。


简述jvm的内存模型?



(1) 程序计数器是一块较小的内存空间,可以看作是当前线程所执行的字节码的行号指示器。分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成;


(2) 通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用JVM中的栈空间,栈可以被分为虚拟机栈和本地方法栈;


(3) 而通过new关键字和构造器创建的对象则放在堆空间,堆是垃圾收集器管理的主要区域;


(4) 方法区和堆都是各个线程共享的内存区域,用于存储已经被JVM加载的类信息、常量、静态变量、JIT编译器编译后的代码等数据;


(5) 程序中的字面量(literal)如直接书写的100、"hello"和常量都是放在常量池中,常量池是方法区的一部分;


(6) 栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,栈和堆的大小都可以通过JVM的启动参数来进行调整,栈空间用光了会引发StackOverflowError,而堆和常量池空间不足则会引发OutOfMemoryError。


String str = new String("hello");


上面的语句中变量str放在栈上,用new创建出来的字符串对象放在堆上,而"hello"这个字面量是放在方法区的。


补充1:较新版本的Java(从Java 6的某个更新开始)中,由于JIT编译器的发展和"逃逸分析"技术的逐渐成熟,栈上分配、标量替换等优化技术使得对象一定分配在堆上这件事情已经变得不那么绝对了。


补充2:运行时常量池相当于Class文件常量池具有动态性,Java语言并不要求常量一定只有编译期间才能产生,运行期间也可以将新的常量放入池中,String类的intern()方法就是这样的。 看看下面代码的执行结果是什么并且比较一下Java 7以前和以后的运行结果是否一致。


String s1 = new StringBuilder("go") 


.append("od").toString();System.out.println(s1.intern() == s1);String s2 = new 


StringBuilder("ja") .append("va").toString();System.out.println(s2.intern() == s2);


jvm中堆和栈有什么区别?


◆栈:存放基本类型的数据和对象的引用,但对象本身不存放在栈中,而是存放在堆中。


在函数中定义的一些基本类型的变量数据和对象的引用变量都在函数的栈内存中分配。即存放的是局部变量。


当在一段代码块定义一个变量时,Java就在栈中 为这个变量分配内存空间,当该变量退出该作用域后,Java会自动释放掉为该变量所分配的内存空间,该内存空间可以立即被另作他用。


◆堆:存放用new产生的数据。


堆内存用来存放由new创建的对象和数组。 在堆中分配的内存,由Java虚拟机的自动垃圾回收器来管理。


◆小例子


在函数中定义一个数组

int[] arr = new int[3];


内存中:



特点一:在堆中,每一个new出来的对象都有一个地址值(一般是一个16进制的对象),堆再把这个值赋值给栈中的局部变量。堆和栈就通过这个地址联系起来。


所以我们直接打印arr,输出的是地址。


System.out.println(arr);


输出结果: [I@15db9742


特点二:堆中每个变量都有默认值


比如数组:arr[0],arr[1],arr[2] 默认值为0


特点三:堆中使用完毕就变成了垃圾,但是没有立即回收,会在垃圾回收器中回收 点:栈内存中的数据使用完就释放(脱离作用域后,比如函数结束)


描述一下JVM加载class文件的原理机制?


VM中类的装载是由类加载器(ClassLoader)和它的子类来实现的,Java中的类加载器是一个重要的Java运行时系统组件,它负责在运行时查找和装入类文件中的类。


由于Java的跨平台性,经过编译的Java源程序并不是一个可执行程序,而是一个或多个类文件。


当Java程序需要使用某个类时,JVM会确保这个类已经被加载、连接(验证、准备和解析)和初始化。


类的加载是指把类的.class文件中的数据读入到内存中,通常是创建一个字节数组读入.class文件,然后产生与所加载类对应的Class对象。


加载完成后,Class对象还不完整,所以此时的类还不可用。当类被加载后就进入连接阶段,这一阶段包括验证、准备(为静态变量分配内存并设置默认的初始值)和解析(将符号引用替换为直接引用)三个步骤。


最后JVM对类进行初始化,包括:1)如果类存在直接的父类并且这个类还没有被初始化,那么就先初始化父类;2)如果类中存在初始化语句,就依次执行这些初始化语句。


类的加载是由类加载器完成的,类加载器包括:根加载器(BootStrap)、扩展加载器(Extension)、系统加载器(System)和用户自定义类加载器(java.lang.ClassLoader的子类)。


从Java 2(JDK 1.2)开始,类加载过程采取了父亲委托机制(PDM)。PDM更好的保证了Java平台的安全性,在该机制中,JVM自带的Bootstrap是根加载器,其他的加载器都有且仅有一个父类加载器。类的加载首先请求父类加载器加载,父类加载器无能为力时才由其子类加载器自行加载。JVM不会向Java程序提供对Bootstrap的引用。



下面是关于几个类加载器的说明:


Bootstrap:一般用本地代码实现,负责加载JVM基础核心类库(rt.jar); Extension:从java.ext.dirs系统属性所指定的目录中加载类库,它的父加载器是Bootstrap;


System:又叫应用类加载器,其父类是Extension。它是应用最广泛的类加载器。它从环境变量classpath或者系统属性java.class.path所指定的目录中记载类,是用户自定义加载器的默认父加载器。


64 位 JVM 中,int 的长度是多数?


Java 中,int 类型变量的长度是一个固定值,与平台无关,都是 32 位。意思就是说,在 32 位 和 64 位 的Java 虚拟机中,int 类型的长度是相同的。


32 位 JVM 和 64 位 JVM 的最大堆内存分别是多数?


理论上说上 32 位的 JVM 堆内存可以到达 2^32,即 4GB,但实际上会比这个小很多。


不同操作系统之间不同,如 Windows 系统大约 1.5 GB,Solaris 大约 3GB。64 位 JVM允许指定最大的堆内存,理论上可以达到 2^64,这是一个非常大的数字,实际上你可以指定堆内存大小到 100GB。甚至有的 JVM,如 Azul,堆内存到 1000G 都是可能的。


原创粉丝点击