Java内存区域

  • 程序计数器(Program Counter Register):记录当前线程所执行字节码的行号指示器。字节码解释器工作时,判断是循环,分支,跳转,异常等条件,然后更新这个计数器的值来选取下一条要执行的指令。

    • 这个部分是线程私有的,各线程之间不会相互影响
  • Java虚拟机栈(JVM Stacks):是Java方法执行的内存模型,每个方法在执行中会创建一个栈帧,用于存储局部变量表,操作数栈,动态链接,方法出口等信息。如果该方法中调用了别的方法,相当于新方法入栈;而方法执行完成后,相当于该方法出栈。

    • 这个部分也是线程私有的

    • 局部变量表存放原始类型和引用对象的指针。所需的内存空间在编译时就确定了。

  • 本地方法栈(Native Method Stack):与Java虚拟机栈类似,不过是存放Native方法的栈帧。

  • Java堆(Java Heap):是Java虚拟机所管理的内存中最大的一块。用于存放对象实例,几乎所有对象实例在这里分配内存。

    • Java堆是所有线程共享的一块内存区域。

    • 这里是垃圾收集器管理的主要区域,也被称为GC堆。

    • 还可以细分为新生代和老生代。见垃圾收集

  • 方法区(Method Area):用于存储已被虚拟机加载的类信息,常量,静态变量,即时编译器编译后的代码等数据。这块内存可以选择不实现垃圾收集,但有时也是很重要的,主要针对常量池的回收和对类型的卸载。

    • 所有线程共享的内存
  • 运行时常量池(Runtime Constant Pool):是方法区的一部分 ,用于存放编译器生成的各种字面量和符号引用。

  • 直接内存:不属于Java内存,但NIO中的Buffer和Channal,可以使用Native函数库来直接分配堆外内存。


GC算法(什么样的对象该死)

  • 引用计数算法:给对象添加一个引用计数器,每当有一个地方引用它时,计数值加1.当引用失效时,计数器减1,任何时刻的计数器为0的对象就是不可用的。python,FlashPlayer等就是这种算法。

  • 可达性分析算法:利用图论的性质。从一系列GC Roots对象为起点,向下搜索,走过的路径为引用链,当一个对象不在路径上(即对象不可达或未连通),则证明对象不可用,可以被判断为可回收。这是java,C#的判断对象是否存活的算法。

  • GC Roots包括:虚拟机栈中中引用的对象,方法区类静态属性引用的对象,方法区中常量引用的对象,本地方法栈中JNI引用的对象。


GC过程=>生存还是毁灭

堆的回收

  • 即使是可达性算法中不可达的对象,也并非一定会死,至少要经历两次标记过程才会真正死亡:

  • 如果发现对象是不可达的,标记第一次并且进行一次筛选。筛选条件是是否有必要执行finialize()方法,当对象没有覆盖finalize()方法,或者finalize()已经被调用过了,则视为没有必要执行。

  • 如果被判定有必要执行finalize()方法,那么该对象将会放置在一个F-Queue的队列中,并在稍后由一个由虚拟机自动建立的、低优先级的Finalizer线程去执行它的finalize()。但不保证会运行结束。

  • 因此在finalize()中对象还有逃脱的机会(仅供演示,没有意义)

public class FinalizeEscapeGC {
public static FinalizeEscapeGC SAVE_HOOK = null; public void isAlive(){
System.out.println("yes ,i am still alive");
} @Override
protected void finalize() throws Throwable {
super.finalize();
System.out.println("finalize method executed");
FinalizeEscapeGC.SAVE_HOOK = this;
} public static void main(String...args) throws Throwable {
SAVE_HOOK = new FinalizeEscapeGC(); //对象第一次自救,成功
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (SAVE_HOOK != null){
SAVE_HOOK.isAlive();
}else {
System.out.println("i am dead");
} //由于finalize()只能调用一次,所以自救失败
SAVE_HOOK = null;
System.gc();
Thread.sleep(500);
if (SAVE_HOOK != null){
SAVE_HOOK.isAlive();
}else {
System.out.println("i am dead");
} } }

方法区的回收:废弃常量和无用的类

  • 例,常量池中有一个abc的字符串,但是没有任何一个String对象引用abc。有必要的话,这个常量会被清除。

  • 无用的类判断,同时满足下列条件:

    • 1.该类的所有实例被回收(Java堆中不存在该类的实例)
    • 2.加载该类的ClassLoader已经被回收
    • 3.该类对应的Class对象没有被任何地方引用,无法在任何地方通过反射访问该类的方法。

垃圾收集算法的发展(见深入理解JVM ==P69)

1.标记-清除算法

  • 首先标记出需要回收的对象,在标记完成后统一回收。

  • 有效率问题(标记和清除的效率都不高)

  • 有空间问题(内存中对象分布不均)

2.复制算法

  • 将可用内存划分为大小相等的两块,每次只使用一块。当一块用完了,就将还存活的对象复制到另外一半上面。然后对已使用的内存空间进行清理。

  • 对此的扩展:

    • 分为一块80%的Eden空间和两块10%的Survivor空间

    • 每次使用Eden和一块Survivor。

    • 当回收时,将Eden和Survivor中还存活的对象复制到另一块Survivor上面。

3.标记-整理算法

  • 首先标记出所有可回收的对象

  • 将不可回收对象向内存首部移动

  • 然后清理掉 ,除首部存活对象之外的内存

垃圾收集器

新生代:Serial,ParNew,Parallel Scavenge

老生代:CMS,Serial Old,Parallel Old

一站式:G1

  • Serial收集器:采用复制算法,它在进行垃圾收集时,会停掉所有工作线程。Client默认

  • ParNew收集器:采用复制算法,多线程收集版本的Serial,和Serial基本一致。Server首选

  • Parallel Scavenge收集器:也是使用复制算法的多线程收集器。吞吐量优先的收集器,使得垃圾收集的占CPU运行比最小

  • Serial Old收集器:使用标记-整理算法,它在进行垃圾收集时,会停掉所有工作线程。Client默认

  • Parallel Old收集器:Parallel Scavenge收集器的老生代版本,多线程+标记-整理算法,如果新生代使用了Parallel Scavenge,可以使用Parallel Old来代替Serial Old来改善性能

  • CMS收集器(重点):并发低停顿收集器,以获取最短回收停顿为目标的收集器,多应用于互联网服务端。算法是标记-清除

    • 初始标记 => 并发标记 => 重新标记 => 并发清除

    • 初始标记,重新标记仍会Stop the World,但时间会很短

    • 时间最长的并发标记和并发清除可以与用户线程一起工作

    • 缺点:1.对CPU资源敏感。2.无法处理浮动垃圾。3.产生大量空间碎片

  • G1收集器(最前沿产品):面向服务端应用的垃圾收集器

    • 并发和并行:来缩短Stop-The-World的时间

    • 分代收集:可以使用不同的策略去收集新生代和老生代的垃圾

    • 空间整合:不会产生大量的空间碎片,有助于程序的长时间运行

    • 可预测的停顿:可以指定在M毫秒中用于垃圾收集的时间不超过N毫秒

    • G1收集器将Java堆不再视为新生代和老生代,而是分成多个大小相等的独立区域(Region)。并维护一个优先队列,每次根据允许的收集时间,优先回收价值大的区域。

GC日志理解

[GC (Allocation Failure) [PSYoungGen: 8192K->1000K(9216K)] 8192K->4441K(19456K), 0.0849028 secs]
[Times: user=0.20 sys=0.00, real=0.09 secs] [Full GC (Ergonomics) [PSYoungGen: 9192K->0K(9216K)] [ParOldGen: 10232K->10214K(10240K)] 19424K->10214K(19456K),
[Metaspace: 3289K->3289K(1056768K)], 0.2874280 secs]
[Times: user=0.64 sys=0.02, real=0.28 secs]
  • 停顿类型:

    • Full GC: Stop-the-world停顿

    • GC: 并发停顿

  • 收集区域

    • DefNew: Serial收集器在新生代

    • ParNew: ParNew收集器在新生代

    • PSYoungGen: Parallel Scavenge收集器在新生代

    • DefOld:

    • ParOldGen:


内存分配和回收策略

  • Young中存放新生的对象(Eden:Survivor = 8:1)

  • Old中存放生命周期长的对象

  • Permanent中存放永久保存的,如Class和Meta信息

1.分代GC

  • 频繁收集Young

  • 较少收集Old

  • 基本不收集Perm

  • Minor GC(收集Young区域)后,会将存活对象移到Survivor区,对象过大则放到Old,所以Eden会为空

  • Full GC/Major GC:发生在老生代的GC,速度比Minor GC慢10倍以上

2.对象分配

  • 对象优先在Eden分配,当Eden空间不足,将会发起一次Minor GC。收集垃圾,并转移对象。

  • 大对象直接进入老年代,(避免短命的大对象)经常出现大对象会导致内存还有空间就提前触发GC

  • 长期存活的对象将进入老年代:虚拟机给每个对象设定一个Age计数器,每熬过一次Minor GC就Age加1。默认到15岁,会被移入老年代

  • 动态对象年龄判定: 当Survivor空间中相同年龄的对象内存总和达到一半,年龄大于等于该年龄的对象可以直接进入老年代。不必达到规定年龄

  • 空间分配担保:发生GC前,虚拟机会检查老年代最大可用的连续空间是否大于新生代所有对象总空间。如果成立,会进行Minor GC,否则查看是否有担保。


类加载器

  • 类加载的时机:加载=>验证=>准备=>解析=>初始化=>使用=>卸载

  • 有5种情况必须马上进行类的初始化:

    • 遇到new、getstatic、putstatic、invokestatic这4条字节码

    • 使用reflect包,进行反射调用的时候

    • 当初始化一个类的时候,发现其父类还没有被加载

    • 当使用JDK1.7的动态语言支持

    • 在类初始化阶段会将static final String的变量提升到掉用类的常量池中

public class ConstClass{
static {
System.out.print("ConstClass init");
}
public static final String HELLOWORLD = "hello world";
} public class NotInitialization{
public static void main(String...args){
System.out.println(ConstClass.HELLOWORLD);
}
} //只会输出hello world
//没有初始化语句
//转化为NotInitialization对自身常量池的引用
//这两个类在编译后就没有任何联系了
  • 子类继承父类并在子类中访问父类,会初始化父类,而不初始化子类

public class SuperClass{
static{
System.out.println("SuperClass init!");
}
public static int value = 123;
} public class SubClass extends SuperClass{
System.out.println("SubClass init");
}
} ==>
public class NotInitialzation{
public static void main(String...args){
System.out.println(Sunclass.value);
}
}
//输出SuperClass init!
//因为对于静态字段,只有定义这个字段的类才会被初始化 ==>
public class NotInitialzation{
public static void main(String...args){
SuperClass[] src = new SuperClass[10];
}
}
//无输出
//不会触发SuperClass的初始化阶段
//会触发[L SuperClass的初始化阶段,这是一个虚拟机创建的一维数组类

类加载器演示

  • 对于任意一个类,都需要由加载它的类加载器和这个类本身一同来确立其在JVM的唯一性。每一个类加载器,都拥有一个独立的类名称空间。

  • 比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义。否则,即使这两个类来源于同一个Class文件,被同一虚拟机加载,只要类加载器不同,则这两个类一定不同。

public class A {
public void aa(){
System.out.println("aaaaaaaaaaaaaaa");
} public static void main(String...args) throws Exception {
ClassLoader loader = new ClassLoader() {
@Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
try {
String fileName = name.substring(name.lastIndexOf(".")+1)+".class";
InputStream is = getClass().getResourceAsStream(fileName);
if (is == null){
return super.loadClass(name);
}
byte[] b = new byte[is.available()];
is.read(b);
return defineClass(name,b,0,b.length);
}catch (IOException e){
throw new ClassNotFoundException(name);
}
}
}; Object obj = loader.loadClass("aaa.A").newInstance();
A a = (A)obj;
a.aa();
System.out.println(obj.getClass());
System.out.println(obj instanceof A); System.out.println(A.class.getClassLoader());
System.out.println(new A().getClass().getClassLoader());
System.out.println(obj.getClass().getClassLoader());
}
} class aaa.A
false
sun.misc.Launcher$AppClassLoader@14dad5dc
sun.misc.Launcher$AppClassLoader@14dad5dc
aaa.A$1@1f32e575
  • obj instanceof A 一句中obj为自定义类加载器所加载的A类的对象。而A类是由系统的类加载器加载的。

  • 可见:A.class和A对象是由sun.misc.LauncherAppClassLoader@14dad5dc加载的,而obj是由aaa.AAppClassLoader@14dad5dc加载的,而obj是由aaa.A1@1f32e575加载的。


双亲委派模型

  • 启动类加载器(Bootstrap ClassLoader): 这个类负责将存放在<JAVA_HOME>/lib目录中的,或者被-Xbootclasspath参数所指定的路径中的,可被虚拟机识别的类库(如rt.jar)加载到虚拟机内存中。

  • 扩展类加载器(sun.misc.Launcher$ExtClassLoader): 它负责加载<JAVA_HOME>/lib/ext目录中的,或者被java.ext.dirs系统变量所指定的路径中的所有类库,开发者可以直接使用

  • 应用程序类加载器(sun.misc.Launcher$AppClassLoader): 这个类加载器是ClassLoader.getSystemClassLoader()的返回值,所以也称为系统类加载器。负责加载ClassPath(用户类路径)上所指定的类库。如果没有自定义的类加载器,这是程序中默认的类加载器。

双亲委派模型

  • 双亲委派模型要求除了顶层的启动类加载器外,其余类加载器都要有自己的父加载器,以组合关系来实现

  • 工作过程:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此。因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器反馈自己无法完成这个加载请求(搜索范围内没有找到)时,子加载器才会尝试自己去加载。

  • 好处:Java类随着它的类加载器一起具备了一种带有优先级的层次关系。例如java.lang.Object, 它存放在rt.jar之中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的启动类加载器进行加载。因此Object类在程序的各种类加载器环境中都是同一个类。(否则,由各个类加载器自行加载,则会出现多个不同的Object类。)

  • 简单实现过程

protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
Class c = findLoadedClass(name); //首先,检查请求的类是否被加载过了
if (c == null){ //如果没有被加载过
try{
if(parent != null){
c = parent.loadClass(name,false); //如果有父类加载器,则由父类加载器来加载(可能会递归)
}else{
c = findBootstrapClassOrNull(name); //没有父类加载器,由BootStrap ClassLoader加载
}
}catch(ClassNotFoundException e){
//如果父类加载器
//说明父类加载器无法完成加载请求
}
if (c == null){
//在父类加载器无法加载时
//再调用本身的findClass方法进行类加载
c = findClass(name);
}
} if(resolve){
resolveClass(c); //?
} return c;
}

Java虚拟机1的更多相关文章

  1. 深入Java虚拟机--判断对象存活状态

    程序计数器,虚拟机栈和本地方法栈 首先我们先来看下垃圾回收中不会管理到的内存区域,在Java虚拟机的运行时数据区我们可以看到,程序计数器,虚拟机栈,本地方法栈这三个地方是比较特别的.这个三个部分的特点 ...

  2. 【深入Java虚拟机】之四:类加载机制

    类加载过程     类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载七个阶段.它们开始的顺序如下图所示: 其中类加载的过程包括了加载.验 ...

  3. 《深入理解Java虚拟机》类文件结构

    上节学习回顾 在上一节当中,主要以自己的工作环境简单地介绍了一下自身的一些调优或者说是故障处理经验.所谓百变不离其宗,这个宗就是我们解决问题的思路了. 本节学习重点 在前面几章,我们宏观地了解了虚拟机 ...

  4. 《深入理解Java虚拟机》调优案例分析与实战

    上节学习回顾 在上一节当中,主要学习了Sun JDK的一些命令行和可视化性能监控工具的具体使用,但性能分析的重点还是在解决问题的思路上面,没有好的思路,再好的工具也无补于事. 本节学习重点 在书本上本 ...

  5. 《深入理解Java虚拟机》虚拟机性能监控与故障处理工具

    上节学习回顾 从课本章节划分,<垃圾收集器>和<内存分配策略>这两篇随笔同属一章节,主要是从理论+实验的手段来讲解JVM的内存处理机制.好让我们对JVM运行机制有一个良好的概念 ...

  6. JVM学习(1)——通过实例总结Java虚拟机的运行机制

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及到的知识点总结如下: JVM的历史 JVM的运行流程简介 JVM的组成(基于 Java 7) JVM调优参数:-Xmx和-Xms ...

  7. Elasticsearch Java 虚拟机配置详解

    Elasticsearch对Java虚拟机进行了预先的配置.通常情况下,因为这些配置的选择还是很谨慎的,所以你不需要太关心,并且你能立刻使用ElasticSearch. 但是,当你监视ElasticS ...

  8. 如何写出让java虚拟机发生内存溢出异常OutOfMemoryError的代码

    程序小白在写代码的过程中,经常会不经意间写出发生内存溢出异常的代码.很多时候这类异常如何产生的都傻傻弄不清楚,如果能故意写出让jvm发生内存溢出的代码,有时候看来也并非一件容易的事.最近通过学习< ...

  9. Java虚拟机(JVM)以及跨平台原理详细的介绍

    相信大家已经了解到Java具有跨平台的特性,可以"一次编译,到处运行",在Windows下编写的程序,无需任何修改就可以在Linux下运行,这是C和C++很难做到的.那么,跨平台是 ...

  10. Java虚拟机浅探

    简介 对于java开发人员了来说,对java虚拟机肯定有着或多或少的了解.因为有了虚拟机的存在,才会使得java的内存管理变得那么方便,不再像C++那样使用new/delete来直接管理内存.知名的j ...

随机推荐

  1. 制作签名jar放置到前端资源目录下

    给jar包打签名keytool -genkey -keystore myKeystore -alias jwstest查看签名信息jarsigner -keystore myKeystore data ...

  2. ck

    ck

  3. C++学习基础十三——struct和class的区别

    来自:http://blog.sina.com.cn/s/blog_48f587a80100k630.html C++中的struct是对C中struct进行了扩展,它不单是一个包含不同数据类型的数据 ...

  4. yarn application -kill application_id yarn kill 超时任务脚本

    需求:kill 掉yarn上超时的任务,实现不同队列不同超时时间的kill机制,并带有任务名的白名单功能 此为python脚本,可配置crontab使用 # _*_ coding=utf-8 _*_ ...

  5. centos 共享文件目录

    # yum install nfs-utils # mkdir /storage # cat /etc/exports /storage *(fsid=0,rw,sync,no_root_squash ...

  6. canal 入门(基于docker)

    第一步:安装MySQL:(可以参考:https://my.oschina.net/amhuman/blog/1941540) 命令: sudo docker run -it -d --restart ...

  7. 浅谈 volatile 的实现原理

    在并发编程中我们一般都会遇到这三个基本概念:原子性.可见性.有序性.我们稍微看下volatile 原子性 原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行. ...

  8. 机器学习入门-文本特征-使用LDA主题模型构造标签 1.LatentDirichletAllocation(LDA用于构建主题模型) 2.LDA.components(输出各个词向量的权重值)

    函数说明 1.LDA(n_topics, max_iters, random_state)  用于构建LDA主题模型,将文本分成不同的主题 参数说明:n_topics 表示分为多少个主题, max_i ...

  9. 02-body标签中相关标签-1

    主要内容: 字体标签: h1~h6.<font>.<u>.<b>.<strong><em>.<sup>.<sub> ...

  10. day14-函数

    1.定义函数 一个函数就是封闭一个功能def 函数名(): 函数代码注意:函数名不要用默认的关键字.否则会将默认关键字函数覆盖掉. 命名规则与变量相同,使用字母.数字.下划线组成,不能以数字开关 2. ...