背景
最近在看《深入理解java虚拟机这本书》,看书的时候,有一些疑问,所以写篇博客总结一下。
java的发展轨迹和历史变迁图
疑问
都有哪些语言是跨平台的?
可移植讲的是一个程序可以经过简单的编译后在不同的平台上运行,而跨平台是不经过编译就可以在不同的平台上运行。
其实目前大部分语言都可以说是跨平台/可移植的,举几个例子
php通过不同操作系统平台的解释器来实现跨平台
go可以通过编译成不同操作系统平台的对应代码来实现可移植
除了虚拟机,还有哪些跨平台方式
1.解释器
2.编译成对应操作系统代码
其他主流语言的内存管理是怎么做的?
- C/C++这种内存堆空间的申请和释放完全靠自己管理
- Java 依赖JVM来做内存管理,不了解jvm内存管理的机制,很可能会因一些错误的代码写法而导致内存泄漏或内存溢出
- Python内存管理是由私有堆空间管理的,所有的python对象和数据结构都存储在私有堆空间中。程序员没有访问堆的权限,只有解释器才能操作。
- Go的内存管理也是自动的,Go 的内存管理基本上参考 tcmalloc 来实现的,只是细节上根据自身的需要做了一些小的优化调整。
Java里都有哪些语法糖
- switch 支持 String 与枚举(java7)
- 泛型
- 自动装箱与拆箱
- 方法变长参数(java1.5)
- 枚举
- 内部类
- 数值字面量
- for-each
- try-with-resource
什么是Java模块化
模块化要解决的问题是:运行Java程序的时候,实际上我们用到的JDK模块,并没有那么多。不需要的模块,完全可以删除。
过去发布一个Java应用程序,要运行它,必须下载一个完整的JRE,再运行jar包。而完整的JRE块头很大,有100多M。怎么给JRE瘦身呢?
现在,JRE自身的标准库已经分拆成了模块,只需要带上程序用到的模块,其他的模块就可以被裁剪掉。
可以看一下廖雪峰的解释:
Java的程序计数器和cpu的计数器有啥区别?
程序计数器(Program Counter Register),也有称作为PC寄存器。想必学过汇编语言的朋友对程序计数器这个概念并不陌生,在汇编语言中,程序计数器是指CPU中的寄存器,它保存的是程序当前执行的指令的地址(也可以说保存下一条指令的所在存储单元的地址),当CPU需要执行指令时,需要从程序计数器中得到当前需要执行的指令所在存储单元的地址,然后根据得到的地址获取到指令,在得到指令之后,程序计数器便自动加1或者根据转移指针得到下一条指令的地址,如此循环,直至执行完所有的指令。
虽然JVM中的程序计数器并不像汇编语言中的程序计数器一样是物理概念上的CPU寄存器,但是JVM中的程序计数器的功能跟汇编语言中的程序计数器的功能在逻辑上是等同的,也就是说是用来指示 执行哪条指令的。
由于在JVM中,多线程是通过线程轮流切换来获得CPU执行时间的,因此,在任一具体时刻,一个CPU的内核只会执行一条线程中的指令,因此,为了能够使得每个线程都在线程切换后能够恢复在切换之前的程序执行位置,每个线程都需要有自己独立的程序计数器,并且不能互相**扰,否则就会影响到程序的正常执行次序。因此,可以这么说,程序计数器是每个线程所私有的。
在JVM规范中规定,如果线程执行的是非native方法,则程序计数器中保存的是当前需要执行的指令的地址;如果线程执行的是native方法,则程序计数器中的值是undefined。
由于程序计数器中存储的数据所占空间的大小不会随程序的执行而发生改变,因此,对于程序计数器是不会发生内存溢出现象(OutOfMemory)的。
栈帧是啥
这个问题涉及到的内容比较多,
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构。它是虚拟机运行时数据区中的虚拟机栈的栈元素。
栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。
每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机里面从入栈到出栈的过程。
可以看一下这篇博客的讲解:Java —— 运行时栈帧结构
这里有两个值需要注意一下:
max_stack 代表了操作数栈(Operand Stacks)深度的最大值。在方法执行的任意时刻,操作数栈都不会超过这个深度。虚拟机运行的时候需要根据这个值来分配栈帧(Stack Frame)中的操作栈深度。
max_locals 代表了局部变量表所需的存储空间,在这里,max_locals 的单位是 Slot,Slot 是虚拟机为局部变量分配内存所使用的最小单位。对于 byte、char、float、int、short、boolean 和 returnAddress 等长度不超过 32 位的数据类型,每个局部变量占用 1 个 Slot,
而 double 和 long 这两种 64 位的数据类型则需要两个 Slot 来存放。方法参数(包括实例方法中的隐藏参数 “this”)、显式异常处理器的参数(Exception Handler Parameter,就是 try-catch 语句中 catch 块所定义的异常)、方法体中定义的局部变量都需要使用局部变量表来存放。
另外,并不是在方法中用到了多少个局部变量,就把这些局部变量所占 Slot 之和作为 max_locals 的值,原因是局部变量表中的 Slot 可以重写,当代码执行超出一个局部变量的作用域时,这个局部变量所占的 Slot 可以被其他局部变量所使用,
Javac 编译器会根据变量的作用域来分配 Slot 给各个变量使用,然后计算出 max_locals 的大小。
程序运行的某个时刻,Java中没有被调用代码,是否被加载了?如果加载了,在哪儿存放?
程序代码都会被存放在方法区
为什么反射,动态代理会导致方法区压力过大?
标记复制和标记整理的区别是啥
像Serial,ParNew,Parallel Scaveng这类年轻代的垃圾回收器,都是使用的标记复制,Serial Old、Parallel Old老年代的垃圾回收器却是使用的标记整理,CMS算是标记清除和标记整理的混合使用。
这个写的很好,可以参考: 并发垃圾收集器(CMS)为什么没有采用标记-整理算法来实现?