(借鉴自:https://www.cnblogs.com/aspirant/p/6841955.html)
jvm
就是java虚拟机(包含一套字节码指令集,一组寄存器,一个栈,一个垃圾回收堆和一个存储方法域),之所以可以跨平台,是因为你可以认为我们的代码仅仅是在虚拟机上跑,和不同的操作系统适配是整个java平台的任务。(不过最终还是需要经由他把字节码转化成不同平台的机器指令再执行的)
jre
就是java平台,环境(jvm是jre的一部分)
jdk
是开发者用来编译、调试程序用的开发包。jdk也是java程序需要在jre上运行。
jvm执行程序的过程
加载.class文件
管理并分配内存
执行垃圾收集
java代码执行过程
.java---(通过compiler编译器)--->.class(字节码文件)->class loader->execution engine(执行引擎)
\--->运行时数据区--^
jvm内存结构
java栈
堆
本地方法栈
程序计数器
方法区
程序计数器(pc)
计算机组成原理中有这个东西。而且在讲到线程的定义的时候也会有这个东西:每个线程都有自己的pc。虽然是多线程,但是同一时刻依然只能跑一个线程。其实多线程是通过线程轮流切换来实现多线程的效果的。通俗点pc就是记录了这个线程中指令已经执行到哪个位置了。
java栈
里面存放的是一个个栈帧。每个栈帧对应一个被调用的方法,栈帧中包括局部变量表、操作数栈、指向这个方法的运行时常量池的引用、方法返回地址和附加信息。
本地方法栈
jni咯。所以这部分内存可以说是开放给c和c++的。这也是安卓要学ndk和jni的原因。
堆
存储对象和数组(数组的引用是放在java栈的)。同时堆是由程序员自己决定分配还是释放的。java栈是编译器为你决定的。存储了每个类的信息(包括类的名称、方法信息、字段信息)、静态变量、常量以及编译器编译后的代码等。方法区中有一个非常重要的部分就是运行时常量池,它是每一个类或接口的常量池的运行时表示形式,在类和接口被加载到JVM后,对应的运行时常量池就被创建出来。当然并非Class文件常量池中的内容才能进入运行时常量池,在运行期间也可将新的常量放入运行时常量池中,比如String的intern方法。
(终于到重点了)
怎么确定垃圾对象
1.引用计数法
给对象添加一个计数器,每当有一个地方引用这个对象了,计数器+1,每当失效一个引用的时候,计数器-1,当减到0的时候,代表这个对象失效了。(方法很不错,但是 很难解决对象之间循环引用的问题,所以不采用)
2.可达性分析算法(这个也比较符合正常人的逻辑思维)
一般作为GC Root的对象有下列几种
栈帧中的本地变量表
静态属性引用的对象
常量引用的对象
jni引用的对象
(所以只要你的起点定义的很好(GC Root在对象图之外),那就不会出现对象间循环引用的问题)
原文有一个自救的例子,就算被置为null了,也可以通过重写finapze()来拯救自己,不过只有一次机会。(不是应该finalize吗)?不过这个就不用深究了。只需要知道,就算被判定为可回收的对象,也要经过两次筛选,对象才能最终消亡。所以我个人认为,对象不用了,我打算置null,免去他搜寻和筛选的工作,或许可以加快程序运行效率呢?
强弱软虚
软,是内存不够,即将发生OOM了才会回收它。
弱,是下一次垃圾回收器运行时,就会把它回收。
(终于到了最重头的戏了)
典型的垃圾回收算法
1.Mark-Sweep(标记-清除)算法
缺点在于,容易产生空间碎片,不利于之后的大对象存放(操作系统的知识,这里应该是页式?段式?忘了)。
2. Copying(复制)算法
为了解决上面那个缺点,这里就很厉害了。把内存分等大小两块,使用只在一块上使用,当你要回收的时候,把存活的对象复制到另外一边,在统一清理另一区域。缺点在于,本来1个T,你只能用512G了,你说难不难受。
3. Mark-Compact(标记-整理)算法
为了解决上面的缺点,这里更厉害了。把存活的对象往一端移动,再清理可回收的对象。
4.Generational Collection(分代收集)算法
这个就更厉害了。把内存分为新生代和老年代(常驻的东西)。所以新生代需要频繁的清理,采用Copying算法,不过不可能按照1:1了,太浪费珍贵的内存了。是8:1:1的模式。8是Eden,两个1是Survivor,每次使用是8+1(其中一个Survivor),再把存货对象挪到剩下的那个1中,清理8+1中可回收对象。而老年代每次只回收少量,所以采取 Mark-Compact算法。
阿里面试题,重中之重:新生代和老年代更具体的关系
所谓的新生代和老年代是针对于分代收集算法来定义的,新生代又分为Eden和Survivor两个区。加上老年代就这三个区。数据会首先分配到Eden区 当中(当然也有特殊情况,如果是大对象那么会直接放入到老年代(大对象是指需要大量连续内存空间的java对象)。),当Eden没有足够空间的时候就会 触发jvm发起一次Minor GC。如果对象经过一次Minor GC还存活,并且又能被Survivor空间接受,那么将被移动到Survivor空 间当中。并将其年龄设为1,对象在Survivor每熬过一次Minor GC,年龄就加1,当年龄达到一定的程度(默认为15)时,就会被晋升到老年代 中了,当然晋升老年代的年龄是可以设置的。
(JVM真是优美的具有深度的东西,java真是优美的语言)