技术

如何使用RedisTemplate访问Redis数据结构 MySQL重要知识点 OAuth2认证授授权流程 分布式锁 服务调用 MQ的介绍 SpringCloud 使用链 Eureka 的点对点通信 介绍Eureka RabbitMQ与其它MQ的对比 Springboot 启动过程分析 Springboot 入门 Linux内存管理 自定义CNI IPAM 扩展Kubernetes 副本一致性 spring redis 源码分析 kafka实践 spring kafka 源码分析 Linux进程调度 让kafka支持优先级队列 Codis源码分析 Redis源码分析 C语言学习 《趣谈Linux操作系统》笔记 Kubernetes安全机制 jvm crash分析 Prometheus 学习 Kubernetes监控 Kubernetes 控制器模型 容器日志采集 容器狂占cpu怎么办? 容器狂打日志怎么办? Kubernetes资源调度-scheduler 时序性数据库介绍及对比 influxdb入门 maven的基本概念 《Apache Kafka源码分析》——server Kubernetes objects之编排对象 源码分析体会 自动化mock AIOps说的啥 从DevOps中挖掘docker的价值 《数据结构与算法之美》——算法新解 Kubernetes源码分析——controller mananger Kubernetes源码分析——apiserver Kubernetes源码分析——kubelet Kubernetes整体结构 ansible学习 Kubernetes源码分析——从kubectl开始 jib源码分析之Step实现 kubernetes实践 线程排队 jib源码分析之细节 从一个签名框架看待机制和策略 跨主机容器通信 jib源码分析及应用 docker环境下的持续构建 docker环境下的持续发布 一个容器多个进程 kubernetes yaml配置 marathon-client 源码分析 《持续交付36讲》笔记 程序猿应该知道的 mybatis学习 无锁数据结构和算法 《Container-Networking-Docker-Kubernetes》笔记 活用linux 命令 为什么很多业务程序猿觉得数据结构和算法没用? 串一串一致性协议 当我在说PaaS时,我在说什么 《数据结构与算法之美》——数据结构笔记 swagger PouchContainer技术分享体会 harbor学习 用groovy 来动态化你的代码 《深入剖析kubernetes》笔记 精简代码的利器——lombok 学习 java 语言的动态性 rxjava3——背压 rxjava2——线程切换 spring cloud 初识 JVM4——《深入拆解java 虚拟机》笔记 《how tomcat works》笔记 commons-pipeline 源码分析 hystrix 学习 rxjava1——概念 Redis 学习 TIDB 学习 分布式计算系统的那些套路 Storm 学习 AQS3——论文学习 Unsafe Spark Stream 学习 linux 文件系统 mysql 批量操作优化 《自己动手写docker》笔记 java8 实践 中本聪比特币白皮书 细读 区块链泛谈 比特币 大杂烩 总纲——如何学习分布式系统 forkjoin 泛谈 hbase 泛谈 看不见摸不着的cdn是啥 《jdk8 in action》笔记 程序猿视角看网络 calico 问题排查 bgp初识 mesos 的一些tips mesos 集成 calico calico AQS2——粗略的代码分析 我们能用反射做什么 web 跨域问题 《clean code》笔记 compensable-transaction 源码分析 硬件对软件设计的影响 elasticsearch 初步认识 mockito简介及源码分析 线上用docker要解决的问题 《Apache Kafka源码分析》——Producer与Consumer 停止容器 dns隐藏的一个坑 《mysql技术内幕》笔记2 《mysql技术内幕》笔记1 log4j学习 为什么netty比较难懂? 回溯法 apollo client源码分析及看待面向对象设计 java系并发模型的发展 从一个marathon的问题开始的 docker 环境(主要运行java项目)常见问题 Scala的一些梗 OpenTSDB 入门 spring事务小结 事务一致性 javascript应用在哪里 netty中的future和promise 《netty in action》读书笔记 netty对http2协议的解析 ssl证书是什么东西 一些tricky的code http那些事 苹果APNs推送框架pushy apple 推送那些事儿 编写java框架的几大利器 JVM3——java内存模型 java concurrent 工具类 java exception java io涉及到的一些linux知识 network channel network byte buffer 测试环境docker化实践 通用transport层框架pigeon netty(七)netty在框架中的使用套路 Nginx简单使用 《Linux内核设计的艺术》小结 从Go并发编程模型想到的 mesos深入 Macvlan Linux网络源代码学习2 《docker源码分析》小结 对web系统的一些理解 docker中涉及到的一些linux知识 hystrix学习 Linux网络源代码学习 Docker网络五,docker网络的回顾 zookeeper三重奏 数据库的一些知识 Spark 泛谈 commons-chain netty(六)netty回顾 Thrift基本原理与实践(三) Thrift基本原理与实践(二) Thrift基本原理与实践(一) Future 回调 Docker0.1.0源码分析 基于spring boot和Docker搭建微服务 通过Docker Plugin来扩展Docker Engine java gc Docker网络四,基于Centos搭建Docker跨主机网络 google guava的一些理解 Jedis源码分析 Redis概述 Docker回顾 深度学习是个什么鬼 Docker网络三,基于OVS实现Docker跨主机网络 Linux网络命令操作 JTA与TCC 换个角度看待设计模式 Scala初识 netty(四)netty对http协议的实现(废弃) netty(三)netty框架泛谈 向Hadoop学习NIO的使用 以新的角度看数据结构 AQS1——并发相关的硬件与内核支持 使用Ubuntu要做的一些环境准备 Docker网络二,libnetwork systemd 简介 那些有用的sql语句 异构数据库表在线同步 spring aop 实现原理简述——背景知识 quartz 源码分析 基于docker搭建测试环境(二) spring aop 实现原理简述 我们编程的那些潜意识 自己动手写spring(八) 支持AOP 自己动手写spring(七) 类结构设计调整 分析log日志 一次代码调试的过程 自己动手写spring(六) 支持FactoryBean 自己动手写spring(九) 总结 自己动手写spring(五) bean的生命周期管理 自己动手写spring(四) 整合xml与注解方式 自己动手写spring(三) 支持注解方式 自己动手写spring(二) 创建一个bean工厂 自己动手写spring(一) 使用digester varnish 简单使用 docker volume 关于docker image的那点事儿 基于docker搭建测试环境 分布式配置系统 JVM2——JVM和传统OS对比 git spring rmi和thrift maven/ant/gradle使用 再看tcp mesos简介 缓存系统——具体组件 缓存系统 java nio的多线程扩展 多线程设计模式/《Concurrency Models》笔记 回头看Spring IOC IntelliJ IDEA使用 Java泛型 vagrant 使用 Go 常用的一些库 Netty(一)初步了解 java mina Golang开发环境搭建(Windows下) java nio入门 ibatis自动生成类和文件 Python初学 Goroutine 调度模型猜想 一些编程相关的名词 虚拟网络 《程序员的自我修养》小结 VPN(Virtual Private Network) Hadoop安装与调试 Kubernetes持久化存储 Kubernetes 其它特性 访问Kubernetes上的服务 Kubernetes副本管理 Kubernetes pod 组件 使用etcd + confd + nginx做动态负载均衡 nginx安装与简单使用 在CoreOS集群上搭建Kubernetes 如何通过fleet unit files 来构建灵活的服务 CoreOS 安装 定制自己的boot2docker.iso CoreOS 使用 Go初学 JVM1——jvm小结 硬币和扑克牌问题 LRU实现 virtualbox 使用 os->c->java 多线程 容器类概述 zabbix 使用 zabbix 安装 Linux中的一些点 关于集群监控 ThreadLocal小结 我对Hadoop的认识 haproxy安装 docker快速入门

标签


JVM1——jvm小结

2014年10月27日

前言

从表到里学习JVM实现要了解JVM是如何实现的,首先必须要知道JVM到底是什么、不是什么,表面上应该提供怎样的功能。

大纲

  1. jvm 在java 体系中的位置
  2. 类的加载
  3. 类的执行
  4. 类的回收

jvm 在java 体系中的位置

jdk 安装目录含义

Class Loaders in Java

Class loaders are responsible for loading Java classes during runtime dynamically to the JVM (Java Virtual Machine). Also, they are part of the JRE (Java Runtime Environment). Hence, the JVM doesn’t need to know about the underlying files or file systems in order to run Java programs thanks to class loaders. 潜台词:Class loaders 是jre 类库的一部分但不是JVM 的一部分

类加载——按类名加载

与c/c++语言不同,c的二进制代码是c代码 + 库函数 编译链接的结果,运行时直接被加载到内存,当然也可以先加载一部分,通过缺页机制按页加载,加载哪一页跟地址有关系。而对于java,实际的“可执行文件”是jvm,像shell一样是个解释器,jvm加载java 代码开始执行,就像shell读入人的指令开始执行。换句话说,如果条件允许,jvm启动起来,像shell一样空转都是可以的。

所以java有一个类加载过程,按名称找到Class文件并加载到内存,并对数据进行校验,转化解析和初始化,最终形成可以被虚拟机直接使用的java类型,这就是虚拟机的类加载机制。

JVM类加载器与ClassNotFoundException和NoClassDefFoundError在”加载“阶段,虚拟机需要完成以下三件事:

  1. 通过一个类的全限定名来获取此类的二进制字节流。类似的 maven的基本概念 中提到URL construction scheme 概念,根据一个jar 的groupId + artifactId + version 即可构造一个http url ,从maven remote Repository 下载jar 文件。
  2. 将字节流代表的静态存储结构转换为方法区的运行时数据结构
  3. 在内存中创建一个代表此类的java.lang.Class对象,作为方法区此类的各种数据的访问入口。

类加载器的双亲委派模型

ClassLoader源码注释:The ClassLoader class uses a delegation model to search for classes and resources.

双亲委派模型要求除了顶层的启动类加载器外,其余的类加载器都必须有自己的父类加载器,类加载器间的父子关系不会以继承关系实现,而是以组合的方式来复用父类加载的代码。

双亲委派模型的工作过程:当一个类加载器收到类加载请求的时候,它会首先把这个请求委托给父类加载器去执行,因此所有的类加载请求最终都会传送到顶层的启动类加载器中,只有当父类加载器也无法找到时才会交给自己去加载。

双亲委派模型的关键就是定义了类的加载过程,先尝试用父类加载器加载,再使用自定义加载器加载,以确保关键的类不被篡改。

使用场景:

  1. 热部署
  2. 代码加密
  3. 类层次划分

延迟加载

class X{
    static{   System.out.println("init class X..."); }
    int foo(){ return 1; }
    Y bar(){ return new Y(); }
}

The most basic API is ClassLoader.loadClass(String name, boolean resolve)

Class classX = classLoader.loadClass("X", resolve);

If resolve is true, it will also try to load all classes referenced by X. In this case, Y will also be loaded. If resolve is false, Y will not be loaded at this point.

ClassNotFoundException和NoClassDefFoundError

Why am I getting a NoClassDefFoundError in Java?

  1. ClassNotFoundException This exception indicates that the class was not found on the classpath.
  2. NoClassDefFoundError, This is caused when there is a class file that your code depends on and it is present at compile time but not found at runtime. Look for differences in your build time and runtime classpaths. 引起的原因比较少见,还未掌握到精髓。

ClassLoader 隔离

笔者曾写过一个框架,用户在代码中通过注解使用。注解参数包括类的全类名(用户自定义的策略类),框架通过注解拿到用户的全类名,加载类,然后调用执行。

但当框架给scala小组使用时,scala小组因使用的play框架的classloader是spring classload的子类。用户自定义策略类是scala实现的,写在用户的项目中。

框架实现主流程,其中的某个环节,load 用户自定义的策略类执行。此时,框架代码Class.forName(class name)去load scala class name就力不从心了。为何呀?

Java中隔离容器的实现

  1. 当在class A中使用了class B时,JVM默认会用class A的class loader去加载class B。
  2. 每个class loader 有一个自己的search class 文件的classpath 范围。
  3. class的 加载不是一次性加载完毕的,而是根据需要延迟加载的(上文提到过)。
  4. 如果class B 不在class loader的classpath search 范围,则会报ClassNotFoundException

在 Java 虚拟机中,类的唯一性是由类加载器实例以及类的全名一同确定的(。即便是同一串字节流,经由不同的类加载器加载,也会得到两个不同的类。在大型应用中,往往借助这一特性,来运行同一个类的不同版本

与Spring ioc 隔离的对比 Spring IOC 级联容器原理探究。PS:有意思的是,classloader 和 spring ioc 都称之为容器,都具有隔离功能,这背后是否有一个统一的逻辑在?都是class loader,只是class 来源不同,加载后的组织方式不同

JVM内存区域新画法

我觉得《深入理解java虚拟机》,那张jvm内存区域图只是体现了内存区域的组成,我这个可能更好点:

Alt text

一个cpu对应一个线程,一个线程一个栈,或者反过来说,一个栈对应一个线程,所有栈组成栈区。我们从cpu的根据pc指向的指令的一次执行开始:

  1. cpu执行pc指向方法区的指令
  2. 指令=操作码+操作数,jvm的指令执行是基于栈的,所以需要从栈帧中的“栈”区域获取操作数,栈的操作数从栈帧中的“局部变量表”和堆中的对象实例数据得到。
  3. 当在一个方法中调用新的方法时,根据栈帧中的对象引用找到对象在堆中的实例数据,进而根据对象实例数据中的方法表部分找到方法在方法区中的地址。根据方法区中的数据在当前线程私有区域创建新的栈帧,切换PC,开始新的执行。

PermGen ==> Metaspace

Permgen vs Metaspace in JavaPermGen (Permanent Generation) is a special heap space separated from the main memory heap.

  1. The JVM keeps track of loaded class metadata in the PermGen.
  2. all the static content: static methods,primitive variables,references to the static objects
  3. bytecode,names,JIT information
  4. before java7,the String Pool

With its limited memory size, PermGen is involved in generating the famous OutOfMemoryError. What is a PermGen leak?

Metaspace is a new memory space – starting from the Java 8 version; it has replaced the older PermGen memory space. The garbage collector now automatically triggers cleaning of the dead classes once the class metadata usage reaches its maximum metaspace size.with this improvement, JVM reduces the chance to get the OutOfMemory error.

垃圾收集算法

不同的区域存储不同性质的数据,除了程序计数器区域不会OOM外,其它的都有可能因为存储本区域数据过多而OOM。

jvm 提供自动垃圾回收机制,但免费的其实是最贵的,一些追求性能的框架会自己进行内存管理。资源的分配与回收——池

如何判断对象已经死亡

说白了,判断还有“引用”引用它么?

  1. 引用计数法

    记录对象被引用的次数

  2. 可达性分析算法

    以一系列GC Roots对象作为起点,从这写节点向下检索,当GC Roots到这些对象不可达时,则证明此对象是不可用的。

回收已死对象所占内存区域

当我们知道哪些对象可以回收时,它们分散在堆的各个地方,如何提高回收效率呢?一次回收完成后,理想状态是:内存是“整齐”的,活着的在一边,空闲的在一边。

  1. 标记-清除算法

    • 实现: 第一遍,标记堆中哪些对象需要被回收;第二遍,回收被标记的对象。
    • 特点:效率不高,一次回收后,堆区碎片化
  2. 复制算法

    • 实现:将区域分成两块(或多块),先紧着一块使用,这块用完后,将活着的对象复制到另一块,然后回收这一整块。
    • 特点:一部分区域会被浪费,如果对象都是“朝生夕死”的,则非常适合
  3. 标记-整理算法

    • 实现:让所有活着的对象都向边界一端移动,清理端边界以外的堆区域
  4. 分代收集算法

    • 实现:将堆区根据对象生存期分为几块,比如分为新生代和老年代,新生代采用“复制算法”,老年代采用“标记-清理”或“标记-整理”算法。

极客时间《深入拆解Java虚拟机》垃圾回收的三种方式

  1. 清除sweep,将死亡对象占据的内存标记为空闲。
  2. 压缩,将存活的对象聚在一起
  3. 复制,将内存两等分, 说白了是一个以空间换时间的思路。

基本假设:部分的 Java 对象只存活一小段时间,而存活下来的小部分对象则会存活很长一段时间。这个假设造就了 Java 虚拟机的分代回收思想。PS:想提高效率就要限定问题域(优化都是针对特定场景的优化),限定问题域就要充分的发掘待解决问题的特征。

上面三种回收算法,各有各的优缺点,既然优缺点不可避免,那就是将它们用在特定的场合扬长避短。java 虚拟机将堆分为新生代和老年代,并且对不同代采用不同的垃圾回收算法