本文参考下面三篇文章,后面两篇需翻墙

如需要直接了解协议内容

  • [RFC6749](https://tools.ietf.org/html/rfc6749#section-4.2)

OAuth2.0基本概念

OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。

OAuth2.0应用场景

企业开放平台,对外输出自己的基础服务。

目前很常见的当你使用小程序或公众号时,他们会直接使用你微信的名称和头像,微信名称和头像等信息是你保存在微信服务器上的信息,只有经过你同意,微信才会将这部分信息交给第三方的小程序或公众号使用,这个场景下就需要授权。

OAuth2.0协议参与者

  • RO (resource owner): 资源所有者,对资源具有授权能力的人。如如应用场景中的用户自己。
  • RS (resource server): 资源服务器,它存储资源,并处理对资源的访问请求。如应用场景中的微信服务,用户的信息保存在微信的服务器上。
  • Client: 第三方应用,它获得RO的授权后便可以去访问RO的资源。如各种小程序和公众号。
  • AS (authorization server): 授权服务器,它认证RO的身份,为RO提供授权审批流程,并最终颁发授权令牌(Access Token)。

OAuth2.0协议基础流程

OAuth Abstract Protocol Flow

(A)用户打开客户端以后,客户端要求用户给予授权。

(B)用户同意给予客户端授权。

(C)客户端使用上一步获得的授权,向认证服务器申请令牌。

(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。

(E)客户端使用令牌,向资源服务器申请获取资源。

(F)资源服务器确认令牌无误,同意向客户端开放资源。

OAuth2.0模式

  • 授权码模式(authorization code)
  • 简化模式(implicit)
  • 密码模式(resource owner password credentials)
  • 客户端模式(client credentials)

每种模式的交互图可参考:Diagrams And Movies Of All The OAuth 2.0 Flows

OAuth2.0模式选择方法

OAuth模式选择方法

OAuth2.0实践中的问题

  1. OAuth是一个授权协议,并不是认证协议,但是授权的过程中会包含认证。

    认证(authentication): who one is。

    授权(authorization): who grant what permissions to whom

  2. 针对的看


本文参考disconf说明文档:http://disconf.readthedocs.io/zh_CN/latest

1. disconf-web安装准备

首先到github上下载disconf的源代码,其中disconf-web就是服务端源码,然后安装下列工具

1.1. 安装 Mysql(Ver 14.12 Distrib 5.0.45, for unknown-linux-gnu (x86_64) using EditLine wrapper) 1.2. 安装 Tomcat(apache-tomcat-7.0.50) 1.3. 安装 Nginx(nginx/1.5.3) 1.4. 安装 zookeeeper (zookeeper-3.3.0) 1.5. 安装 Redis (2.4.5)

我是在mac上通过homebrew进行的安装,

    brew install mysql
    brew install tomcat
    brew install nginx 
    brew install zookeeper
    brew install redis         

homwbrew 安装的程序文件夹(默认:/usr/local/Cellar/ )中有*.plist文件,软连接到~/Library/LaunchAgents/可以让服务开启自动启动.(tomcat没有这个文件)

检查nginx是否启动

    ps -ef | grep nginx 未启动则启动

检查redis是否启动

    ps -ef | grep redis 未启动则启动

检查zookeeper是否启动

    ps -ef | grep zookeeper 未启动则启动

检查apache是否启动


1. 简介

本文介绍使用SpringMVC的后端服务如何通过配置来支持多种返回值类型(xml,json,html,excel)

这里的代码使用的是springboot,下载地址:https://github.com/xiagn825/springboot-todolist/tree/springboot-ContentNegotiation

2. 基础概念

2.1 HttpHeader中Content-Type和Accept设置的区别

Accept:接口要返回给客户端的数据格式

    curl --header 'Accept:application/json' http://localhost:8080/todo

Content-Type:客户端发送给服务器端的数据格式

    curl -X PUT --header 'Content-Type:application/json' -d '{"title":"周末日程","content":"睡觉"}'  http://localhost:8080/todo

2.2 SpringMVC生成输出的两种方式

1) 当服务端使用Restful的方式,只为客户端的ajax或其他服务端请求提供数据时,通常会使用@ResponseBody来标识你的返回,这时候Spring使用HttpMessageConverter来把返回的对象格式化成所需的格式。

2) 当你需要提供表现层(比如:HTML),这时候SpringMVC使用ViewResolver来将处理你的返回。

有时候你的应用程序这两者都要提供

2.3 SpringMVC输出格式判定

很多时候为了支持多个系统或多个终端,你需要让相同的数据已不同的表现形式输出。

SpringMVC使用ContentNegotationStrategy来判定用户请求希望得到什么格式的数据。

ContentNegotationStrategy通过三种方式来识别用户想要返回什么样的数据

  1. 通过请求URL后缀:http://myserver/myapp/accounts/list.html 返回html格式
  2. 通过请求的参数:http://myserver/myapp/accounts/list?format=xls 该设置默认不开启,默认key是format。
  3. 通过HTTP Header的Accept:Accept:application/xml 优先级由上至下

请看如下配置

@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    configurer.favorPathExtension(false)
            .favorParameter(true)
            .parameterName("mediaType")
            .defaultContentType(MediaType.APPLICATION_JSON)
            .mediaType("xml", MediaType.APPLICATION_XML)
            .mediaType("html", MediaType.TEXT_HTML)
            .mediaType("json", MediaType.APPLICATION_JSON);
}

在你工程的WebMvcConfig中加入以上配置,表示关闭URL后缀的规则,打开请求参数规则并设置请求参数为’mediaType’,默认的返回格式是json,还支持返回xml,html。

这三个组件是用来处理返回不同格式输出的关键

  • Request Mappings: 决定不同的请求到不同的方法并返回不同的格式.

  • View Resolution: 根据类型返回合适的表示层.

  • HttpMessageConverters: 将request中的参数转换成java对象,将java对象转换成相应的输出格式到response.

2.4 RequestMappings

2.4.1 RequestMappingHandlerMapping

我们在spring中通常使用的就是RequestMappingHandlerMapping,根据RequestMappingInfo,细化匹配条件,整体的查找过程如下:

AbstractHandlerMethodMapping实现接口getHandlerInternal

  1. 使用UrlPathHelper查找request对应的path

  2. 查找path对应的HandlerMethod

    2.1 从urlMap中直接等值匹配查找匹配条件RequestMappingInfo

    2.2 如果等值查找到匹配条件,将其添加到match条件中

    2.3 如果没有找到匹配条件,使用所有的handlerMethod的RequestMappingInfo进行匹配

    2.4 对匹配到的Match进行排序,取出最高优先级的Match,并核对是否是唯一的最高优先级

    2.5 对匹配到条件,没有匹配到条件的两种情况,分别进行封装

  3. 封装HandlerMethod,确保bean中存的是实例    ContentNegotiationManager其中提供了针对miniType的match条件比较,使框架可以匹配到最合适的处理方法。

2.5 HttpMessageConverter

2.5.1 The Default Message Converters

SpringMvc默认会加载下列HttpMessageConverters:

ByteArrayHttpMessageConverter – converts byte arrays
StringHttpMessageConverter – converts Strings
ResourceHttpMessageConverter – converts org.springframework.core.io.Resource for any type of octet stream
SourceHttpMessageConverter – converts javax.xml.transform.Source
FormHttpMessageConverter – converts form data to/from a MultiValueMap<String, String>.
Jaxb2RootElementHttpMessageConverter – converts Java objects to/from XML (added only if JAXB2 is present on the classpath)
MappingJackson2HttpMessageConverter – converts JSON (added only if Jackson 2 is present on the classpath)
MappingJacksonHttpMessageConverter – converts JSON (added only if Jackson is present on the classpath)
AtomFeedHttpMessageConverter – converts Atom feeds (added only if Rome is present on the classpath)
RssChannelHttpMessageConverter – converts RSS feeds (added only if Rome is present on the classpath)

我们如果返回的是使用@ResponseBody来标识的,那么框架会使用HttpMessageConverter来处理返回值,默认的xmlCoverter不是特别好用,依赖返回实体对象上的@XmlRootElement注解,不是很方便所以引入辅助类库,并自定义MessageConverter这样可以直接将返回的对象处理成xml格式。

Gradle import library

compile group: 'org.springframework', name: 'spring-oxm', version: '4.3.9.RELEASE'
compile group: 'com.thoughtworks.xstream', name: 'xstream', version: '1.4.10'

configuration

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    converters.add(createXmlHttpMessageConverter());
    super.configureMessageConverters(converters);
}
private HttpMessageConverter<Object> createXmlHttpMessageConverter() {
    MarshallingHttpMessageConverter xmlConverter =
            new MarshallingHttpMessageConverter();
    XStreamMarshaller xstreamMarshaller = new XStreamMarshaller();
    xmlConverter.setMarshaller(xstreamMarshaller);
    xmlConverter.setUnmarshaller(xstreamMarshaller);
    return xmlConverter;
}

2.6 View Resolution

2.6.1 页面render(freemarker)

当需要返回页面时就需要由合适的viewResolver来绘制画面,这里采用freemarker作为页面引擎。

Gradle import library

compile("org.springframework.boot:spring-boot-starter-freemarker")

Thread

构造方法

Thread 类提供六种构造方法,内部都是调用了下面的私有方法

private void init(ThreadGroup g, Runnable target, String name,long stackSize)

由方法参数可见我们可以通过构造函数指定的线程属性是

  1. 线程组:线程组,当参数为空时首先检查系统是否存在Security(System.getSecurityManager()),存在则使用Security的线程组,否则使用父线程所在的线程组。按照Effective Java 73条的记述不推荐使用线程组。

总而言之,线程组并没有提供太多有用的功能,而且他们提供的许多功能还都是由缺陷的,我们最好把线程组看作是一个不成功的实验,你可以忽略掉他们,就当他们不存在一样,如果你正在设计的一个类需要处理线程的逻辑组,或许就应该使用线程池executor

  1. 线程执行对象:Runnable对象,如果不传则应该继承Thread类并覆盖其run方法
  2. 线程名字:默认值是“Thread-”+n ,表示线程的名字。
  3. 线程私有栈大小:默认值是0,如果指定的话这个参数也是高度依赖于平台的,通常当指定一个大的值时表示该线程可以实现一个更大的递归深度(不会抛出StackOverflowError),当指定一个小的值时表示同一时间可以运行更多的线程(OutOfMemoryError).但是在一些平台,这个参数是无效的.因此使用时需要极其小心。

其他属性

  1. 线程优先级:setPriority 设定范围1-10
  2. 线程是否是守护线程:setDaemon 当不存在其他线程时守护线程将被自动终止,例如垃圾回收线程。
  3. 线程异常处理:setUncaughtExceptionHandler 设定默认的异常处理,getDefaultUncaughtExceptionHandler方法是thread类的静态方法用来设定所有thread的默认异常处理。

生命周期方法

  1. 启动:start(),导致此线程开始执行,JAVA虚拟机会调用该线程的run方法,这是会有两个线程同时执行,当前线程和运行线程run方法的线程,不要重复调用start方法,一个线程运行结束后也不要在重新启动它,也不要直接使用run方法,这样run方法仍然是在当前线程下运行。
  2. 休眠:sleep(long millis)/sleep(long millis, int nanos) 静态方法让当前线程休眠一段时间,精度受制于系统的计时器和调度器,休眠期间不会丢弃任何的监听器,如果线程在休眠期间被中断则会抛出InterruptedException异常
  3. 中断:interrupt() 当前线程可以任意中断自身,当中断其他线程时,可能会抛出SecurityException;当要被中断的线程处在被对象监控器调用wait,或者自身的join,sleep方法时,设置的中断状态会被清除,并抛出InterruptedException;当线程被一个可中断的I/O通道阻塞时,线程的中断状态会被设置,线程会接收到ClosedByInterruptException;如果线程正在java.nio.channels.Selector中被阻塞,则中断状态会被设定,线程会立即从选中操作中返回,有可能携带费0值,就像选择器的唤醒方法被调用一样。当不是以上这些状态时,中断状态被正确设定,也不会有任何副作用。
  4. 检查线程是否处于中断状态:静态方法interrupted和isInterrupted,其中interrupted方法被调用时会重置中断状态因此建议使用isInterrupted
  5. 让出:yield() 向调度器提示当前线程愿意产生其当前对处理器的使用。调度程序可以随意忽略此提示。产量是一种启发式尝试,以改善线程之间的相对进展,否则会过度利用CPU。它的使用应结合详细的剖析和基准测试,以确保它实际上具有预期的效果。使用此方法很少适合。它可能有用于调试或测试目的,它可能有助于重现由于竞争条件的错误。在设计诸如java.util.concurrent.locks包中的并发控制结构的并发控制结构时,它也可能很有用。
  6. 等待线程结束:join 其他线程等待当前线程执行完毕。
  7. 另外还有目前不再推荐使用的的暂停(suspend),恢复(resume),停止(stop)方法.

如何实现线程的停止,暂停和恢复

大多数stop的使用应该被替换为代码,只需修改一些变量来指示目标线程应该停止运行。 目标线程应该定期检查这个变量,并且如果变量指示它要停止运行,则以有序的方式从其run方法返回。 为了确保停止请求的及时通信,变量必须是volatile(或者访问变量必须同步)。

原代码

private Thread blinker;

public void start() {
    blinker = new Thread(this);
    blinker.start();
}

public void stop() {
    blinker.stop();  // UNSAFE!
}

public void run() {
    while (true) {
        try {
            Thread.sleep(interval);
        } catch (InterruptedException e){
        }
        repaint();
    }
}

修改后

public class CustomerRunnable implements Runnable {
    private volatile Thread blinker;
    
    private volatile boolean threadSuspended;
    
    public void start() {
        blinker = new Thread(this);
        blinker.start();
    }
    
    public void stop() {
        System.out.println("stop thread");
        synchronized(this) {
            blinker = null;
            notify();
        }
    
    }
    
    public void suspend() {
        System.out.println("suspend thread");
        synchronized(this) {
            threadSuspended = true;
        }
    }

    public void resume() {
        System.out.println("resume thread");
        synchronized(this) {
            threadSuspended = false;
            notify();
        }
    }

    @Override
    public void run() {
        Thread currentThread = Thread.currentThread();
        while (blinker == currentThread) {
            try {
                synchronized(this) {
                    while (threadSuspended && blinker == currentThread) {
                        wait();
                    }
                }
                Thread.sleep(100);
            } catch (InterruptedException e){
            }
            repaint();
        }
    }

    private void repaint() {
        System.out.println(Thread.currentThread().getName()+ " : " +System.currentTimeMillis());
    }

    public static void main(String args[]) {
        CustomerRunnable customerRunnable = new CustomerRunnable();
        customerRunnable.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        customerRunnable.suspend();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        customerRunnable.resume();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        customerRunnable.stop();
    }
}

进程与线程

进程: 是计算机中已运行程序的实体。进程为曾经是分时系统的基本运作单位。在面向进程设计的系统(如早期的UNIX,Linux 2.4及更早的版本)中,进程是程序的基本执行实体;在面向线程设计的系统(如当代多数操作系统、Linux 2.6及更新的版本)中, 进程本身不是基本运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。

线程: 是比进程更轻量级的调度执行单位,线程的引入可以把一个进程的资源分配和执行调度分开,各个线程既可以共享资源(内存地址,文件I/O) 又可以独立调度(独享CPU)。它的出现可以使计算机充分的发挥并发计算能力.

JAVA线程实现

操作系统实现线程主要有3中方式:

  • 使用内核线程(kernel-level Thread)实现
  • 使用用户线程(User Thread)实现
  • 使用用户线程加轻量级进程(Light Wright Process)混合实现

JAVA本身因为其跨平台性,因此会为我们屏蔽这些线程实现上的差异,通常来说一条Java线程就映射到一条轻量级进程中。 具体点,并且和其他所有元素一样,都是对象,Java提供了两种方式来创建线程:

Runnable接口

Runnable给意图执行线程的类提供了实现标准,继承该接口的类必须定义一个没有任何参数的run方法。 接口是给那些希望在活动时执行代码的对象提供公共协议,举个例子,Thread实现了Runnable。 活跃的简单来说是一个线程已经启动但是还没有停止。 此外,Runnable接口为希望活跃的类提供了避免子类化Thread的方法。一个类实现Runnable接口可以不需要子类化Thread通过将自己传递给实例化Thread的实例来运行。大多数情况,Runnable接口当你只希望重写run方法不需要重写其他方法时被使用,这是很重要的因为类不应该被子类化除非我们想要修改或增强其基础行为。

Thread类

一个Thread时程序里的执行线程。java虚拟机允许应用程序同时执行多个线程。 每个线程都有一个优先级。高优先级的线程优先执行与低优先级的。每个线程都可以被当做守护线程 新的线程会继承被创建时线程的属性。 当Java虚拟机启动时,通常会有一个非守护线程(main函数等启动类的某个方法),虚拟机会持续执行线程直到发生了下面的情况

  • 正在运行的类调用了exit方法并且安全管理器允许了退出操作
  • 所有的非守护线程都已经结束

JAVA线程的基本属性

Thread类中有一些保存信息的属性,这些属性可以用来标示线程,显示线程的状态或者控制线程的优先级。

  • ID:保存了线程的唯一标示,不允许修改。
  • Name:保存线程的名字。
  • Priority:保存了线程对象的优先级。线程的优先级是从1到10,1是最低级,10是最高级。
  • Status:保存了线程的状态。线程的状态有6种:new,runnable,blocked,waiting,time waiting,terminated。

关于线程状态之间的转化见下图 JAVA thread state machine


JAVA garbage collector

原文地址

在2014年对于有两件事对大多数开发着来说仍然是个迷-垃圾回收和理解异性。关于后者我懂得不多,所以我想谈谈前者。特别是JAVA8在这个领域已经做了很大的改变和提高,特别是移除了永久代和一些新的令人兴奋的变化。

当我们谈论垃圾回收,我们中的大多数都知道这个概念并且在每天你的编程过程中使用它。即使如此,对于垃圾回收有很多我们不清楚的地方。其中最大的错误概念是JVM只有一个垃圾回收器,而事实上JVM提供了4中不同的各有优缺点的回收器。JVM并不会自动来选择,需要依赖你自己在吞吐量和程序响应速度两方面去抉择。

大部分堆中的对象都是朝生夕死的,将堆分成不同的段进行分代管理是4种垃圾收集的共同点。接下来我回直接来描述他们的不同点和优缺点。

串行收集器(The Serial Collector)

串行收集器时最简单的一个,你基本上不会使用它,它主要时在单线程并且内存很小的环境下使用,收集器在工作时会暂停应用线程,因此它不适合使用在服务器环境中。 如何使用:-XX:+UseSerialGC

并行/吞吐量收集器(The Parallel/Throughput collector)

接下来是并行收集器。这是JVM默认的收集器。正如它的名字,它最大的优点使用多线程去扫描和压缩堆。问题是并行收集器在执行新生代GC和完整GC时会停止应用线程。并行收集最适合可以忍受应用暂停并且尝试优化收集器的CPU使用率。

同步标记清除收集器(The CMS Collector)

CMS(concurrent-mark-sweep)它的算法是使用多个同步线程扫描堆进行标记和对不使用对象进行清理。这个算法在两种情况下会暂停应用线程:初始化标记根节点和当同步算法运行后对在这段时间堆中的改变进行再标记。 使用此收集器最大的问题是遇到保证失败,这是因为老年代预留的空间不足以存放新生代升级过来的对象。如果收集时需要升级新生代到老年代,但是没有足够的空间去存放这些对象时,就会触发全GC,造成应用程序暂停,这是CMS收集器极力避免的。为了确保这种情况不会发生,你需要增大老年代或者整个堆的大小并且分配更多的线程给收集器,以便让对象回收的速度大于分配。 与并行收集器相比,该算法会占用更多的CPU,以便分配更多的线程来做扫描和收集。对于很多长时间运行的服务应用来说,这种算法的好处时减少停顿时间。

如何使用:-XX:+USeParNewGC

假设你的应用内存小于4G,并且你可以分配更多的CPU资源来避免应用停顿你可以选择CMS,如果应用内存大于4G推荐你使用最新的算法-G1收集器。

G1收集器(The G1 Collector)

G1收集器首次被介绍是在JDK7u4,它被设计用来处理大于4GB内存的应用程序,G1收集器使用多个线程来扫描每一个独立的区域,区域的大小是1MB-32MB。G1收集器首先扫描出包含最多垃圾对象的区域进行标记。 策略会避免产生堆耗尽导致后台线程没有完成对未使用对象的扫描,从而减少STW出现的情况。另一个好处是在不STW的情况下压缩移动堆。 在过去几年中,大堆已经成为一个相当有争议的领域,许多开发人员从每个机器模型的单个JVM迁移到更多的微服务,组件化架构,每个机器具有多个JVM。这是由许多因素驱动的,包括隔离不同应用程序部分的需求,简化部署并避免将应用程序类重新加载到内存中所需的成本。 即使如此,对JVM而言,最大的驱动因素之一就是希望避免那些长期“停止世界”的停顿(在大型集合中可能需要很多秒钟)发生在大堆中。Docker等容器技术也加速了这一点,使您能够在相同的物理机器上轻松部署多个应用程序。

如何使用:–XX:+UseG1GC

JAVA8与G1收集器

另一个美丽的优化是在java8u20里G1收集器的字符串不重复。当字符串占据了我们堆的大部分空间,新的优化可以使G1收集器唯一标示那些不知出现一次的字符串,使他们都指向同一个char数组,以避免相同字符串的多个副本在堆内无效率地驻留。

如何使用:-XX:+UseStringDeduplication

JAVA8和永久代

Java8的一个最大的改变就是移除了堆中的永久代(存储类的元数据,字符串和静态变量),这个在老的java版本中要求开发者对那些加载大量类的应用针对这个区域进行优化和调整。这多年来成为了OutOfMemory异常的源头,所以JVM本身对其进行处理时非常好的补充。当然,这样也不会减少开发者希望将自己的应用解耦到多个JVM的浪潮


继续参考《深入理解java虚拟机》来回顾下java的垃圾收集器相关的知识(第三章)


垃圾回收

垃圾回收可以说时java中的一大特色,那么java时如何自动判断什么样的对象应该被回收的呢? 常用的算法时下面两种

  1. 引用计数法
  2. 可达性分析法

引用计数法

引用计数法的实现比较简单:给对象添加一个引用计数器,每当有一个地方引用对象,计数器值就加1;当引用失效计数器就减1;计数器为0的对象就是不可能再被使用的。 但是主流的java虚拟机里没有采用这种算法来管理内存,因为该算法很难解决对象之间循环引用的问题。

可达性分析法

目前主流的Java虚拟机都采用该算法。这个算法的基本思路是通过一系列的“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(reference chain),当一个对象到“GC Roots”没有任何引用链时,则证明该对象时不可用的。 java语言中可以作为“GC Roots”的对象是

  • 虚拟机栈中的引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI引用的对象

java 引用说明

上面两种算法最终判定对象是否存活都与“引用”有关,那么JAVA时如何定义引用的呢。 早期JDK中对引用的定义很传统:如果reference类型中的数据中存储的数值代表的时另外一块内存的起始地址,就称这块内存代表一个引用 JDK1.2之后,java对引用的概念进行了扩充,将引用分为

  • 强引用(Strong Reference)
  • 软引用(Soft Reference)
  • 弱引用(Weak Reference)
  • 虚引用(Phanton Reference)

垃圾收集算法

可以识别需要回收的对象后,我们还需要可以收集清理它们的算法

  • 标记-清除算法:清除后内存不连续,当有大对象进行分配时会遇到内存不足频繁full Gc
  • 复制算法:效率高,内存利用率不高
  • 标记-整理算法:效率

HotSpot的算法实现

前面介绍了如何识别对象是否生存和垃圾收集算法,而在虚拟机上实现这些算法时,必须对算法的执行效率有严格的考量,才能保证每次垃圾回收的准确和高效。

枚举根节点

要进行可达性分析就需要先收集到所有的“GC Roots”节点,目前很多应用仅仅方法区就有数百兆,如果要逐个检查这里面的引用,那么必然=会消耗很多时间。而且为了“GC Roots”数据的准备进行该动作时必须停顿所有java执行线程(Stop The World)。 为了减少枚举时间,hotspot的实现中,存在一组被称为OopMap的数据结构来达到这个目的,在类加载完成的时候,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来。

安全点(safe point)

在OopMap的协助下,HotSpot可以快速准确的完成GC Roots枚举,但是如果为每一条指令都生成对应的OopMap,将会需要大量的额外空间,这样GC的成本将会变得非常高。

所以,虚拟机只有在指令序列复用的地方(方法调用,循环跳转,异常跳转)会产生一个被称为安全点的记录,OopMap只会在该位置记录,也就时程序只有到达安全点才可以执行GC。

安全区(Safe Region)

安全点基本解决了所有的GC进入问题,但是如果当前线程被中断或者挂起要如何GC呢?为了解决这种情况,虚拟机中还有安全区的概念。 安全区:在一段代码片段中,引用关系不会发生变化。


垃圾收集器

根据不同的应用,所产生的内存对象不同,要满足的业务场景也不一样,因此没有完美的垃圾收集器,需要根据实际情况去选择.

垃圾收集器时之前列举的垃圾收集算法的具体实现。

java的堆内存根据对象的生命周期也会分为新生代和老年代。新生代的对象朝生夕死,回收率高,老年代的对象存活时间长,回收率低。 两者往往会使用不同的垃圾收集器。

  • 新生代收集器
    • Serial 收集器:单线程,简单,Client模式下的首选
    • ParNew收集器:Serial的多线程版本
    • Parallel Scavenge收集器:吞吐量优先,适合后台运算多交互少的任务
  • 老年代收集器
    • Serial Old收集器:“标记-整理”,作为CMS收集失败后的后备方案
    • Parallel Old收集器:多线程+“标记-整理”,吞吐量优先
    • CMS收集器:“Concurrent Mark Sweep” 最短停顿时间,server端首选,“标记清除”
      • 初始标记(Stop The World)
      • 并发标记
      • 重新标记(Stop The World)
      • 并发清除
  • G1收集器

垃圾收集器参数总结

参数 描述
UseSerialGC 虚拟机client模式的默认配置,使用Serial+Serial Old收集器
UseParNewGC 采用ParNew+Perial Old收集器组合
UseConcMarkSweepGC 采用ParNew+CMS+Serial OLD收集器组合
UseParallelGC 虚拟机server模式下默认值,采用Parallel Scavenge + Serial OLD收集器组合
UseParallelOldGC 采用Parallel Scavenge + Parallel Old收集器组合
SurvivorRatio 新生代中Eden区域与Survivor区域的容量配比,默认是8
PretenureSizeThreshold 直接晋升老年代的对象大小
MaxTenuringThreshold 晋升到老年代的对象年龄。每个对象在坚持过一次Minor GC之后,年龄就+1
UseAdaptiveSizePolicy 动态调整Java堆中各个区域的大小以及进入老年代的年龄
HandlePromotionFailure 是否允许担保失败,即老年代的剩余空间不足以应付新生代的整个Eden和Survivor区的所有对象
ParallelGCThreads 设置并行执行GC的线程数
GCTimeRatio GC时间占总时间的比率,默认值为99,即允许1%的GC时间,仅在Parallel Scavenge收集器时有效
MaxGCPauseMillis 设置GC的最大停顿时间,仅在Parallel Scavenge收集器时有效
CMSInitiatingOccupancyFraction 设置CMS收集器在老年代空间被使用多少后触发垃圾回收,默认值68%
UseCMSCompactAtFullCollection 设置CMS收集器在完成垃圾收集后是否要进行一次内存整理
CMSFullGCsBeforeCompacation 设置CMS收集器在进行若干次垃圾收集后再启动一次内存整理

内存分配与回收策略

  • 对象优先在Eden分配
  • 大对象直接进入老年代
  • 长期存活对象进入老年代
  • 动态对象年龄判定
  • 空间分配担保

最近在电脑里装了linux,使用时发现无线网络在电脑休眠后就再也连不上了。。。

使用的linux版本是ubuntu14.04,网卡是rtl8723be

下面时修复步骤

  1. liunx版本查看命令

     cat /proc/version
    
  2. 网卡信息查看

     lspci | grep -i net
    

通常输出两行第一行是有限网卡,第二行时无线网卡

  1. 安装需要依赖的包

     sudo apt-get install build-essential git
    
  2. 在github上clone该工程

     git clone https://github.com/lwfinger/rtlwifi_new/
    
  3. 进入文件夹

     cd rtlwifi_new
    
  4. make该工程

     make
    
  5. 安装

     sudo make install
    
  6. 重启系统

     sudo reboot
    
  7. 如果还是会断线的话,在网卡配置中加下面的配置,使其不进入休眠

     echo "options rtl8723be fwlps=0" | sudo tee /etc/modprobe.d/rtl8723be.conf
    
  8. 如果liunx内核版本升级了,需要更新驱动重新编译安装

    cd rtlwifi_new
    make clean
    git pull
    make clean && make
    sudo make install
    sudo reboot
    

[TOC]

Sublime Text3 作为一个优秀的文本编辑器,拥有很多的扩展插件。我们可以利用这些插件为Sublime Text 增加扩展的功能. 插件如何安装:https://packagecontrol.io/installation

前端开发相关

  1. Emmet

文档书写

  1. MarkDown Editing+MarkDown Preview(Markdown书写工具)

    简单用法:

    • 按Ctrl + N 新建一个文档

    • 按Ctrl + Shift + P

    • 使用Markdown语法编辑文档

    • 语法高亮,输入ssm 后回车(Set Syntax: Markdown)

    • 按Ctrl + Shift + P

    • 输入mp 后回车(Markdown Preview: current file in browser)

    • 此时就可以在浏览器里看到刚才编辑的文档了

  2. Side​Bar​Enhancements

其他有用的插件


继续参考《深入理解java虚拟机》来回顾下java的内存分配相关的知识(第二章)

java内存区域图

线程私有区


pc register (program counter register)/程序计数器


每一个线程都有自己的程序计数器与线程在同时建立,在任何时间,每个java虚拟机线程都只会执行一个方法,叫做当前方法(current method),程序计数器纪录的就是当前方法指令在方法区内的地址。这是因为java虚拟机是多线程的,线程会轮流切换的执行,为了线程切换后能恢复到正确的执行位置,所以每个线程都有程序计数器。

Note: 当java虚拟机执行一个本地方法时,程序计数器值为undefined。该区域有足够的存储空间因此不会有OutOfMemory的情况.

stack/java虚拟机栈


与程序计数器一样,java虚拟机栈也是线程独有的,与线程生命周期相同,栈中包含许多栈桢.当一个方法被调用时就会创建一个桢并放入栈中,当方法调用完成桢回被回收。

帧是一种数据结构,该数据结构包含表示当前方法中线程状态的多种数据。

  • Operand Stack/操作数栈:用来为java函数的调用提供参数,获得栈顶方法执行后的返回值。
  • Local variable array/本地变量数组: 这个数组中包含原始基本类型,引用和返回地址,数组的大小是编译期动态计算的
  • Run-time constant pool reference/运行时常量引用: 这个空间用来把象征方法的引用转译成真正的内存引用

    看下面的例子

      public int add(int a, int b){
    		return a + b;
      }
    
      public void functionA(){
          // some code without function call
    		int result = add(2,3); //call to function B
          // some code without function call
      }
    

    java frame 执行顺序

    frameA是代表functonA的栈桢,当开始调用add的时候回新建栈桢frameB并把frameB变成当前桢,FrameB的本地变量数组存储在被弹出的frameA的操作数栈的空间上.当add执行结束后,frameB被回收当前桢又变成frameA,add的返回值放到frameA的操作数栈上。
    Note:在虚拟机运行时栈的大小时可以动态扩展的,当递归调用导致栈的深度无法扩展时java会抛出 StackOverflowError.


    /**
     * VM Args: -Xss256k
     *
     * stack length:1898
     * Exception in thread "main" java.lang.StackOverflowError
     * at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:12)
     *
     *
     * Created by xiagn on 16-4-7.
     */
    public class JavaVMStackSOF {

        private int stackLength = 1;

        public void stackLeak() {
            stackLength++;
            stackLeak();
        }

        public static void main(String[] args) throws Throwable {
            JavaVMStackSOF oom = new JavaVMStackSOF();
            try {
                oom.stackLeak();
            } catch(Throwable e) {
                System.out.println("stack length:" + oom.stackLength);
                throw e;
            }
        }

    }
 

native method stack/本地方法栈


这个栈是java通过jni调用其他语言的栈,栈的行为依赖与低层的操作系统。

共有区


heap/堆


堆是所有java虚拟机线程共有的存储区,他是在虚拟机启动时创建的。所有类的实例和数组的存储空间都分配在堆中。这个区域使用垃圾回收机制来管理,根据一定的回收策略回收内存,不同的jvm回收的策略不同。堆所占的空间也是动态分配的,可以通过参数来配置最大和最小堆内存(java -Xms=512m -Xmx=1024m)

Note: 当内存超出时会抛出OutOfMemoryError异常

异常例:

import java.util.ArrayList;
import java.util.List;

/**
 	 * VM args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
 	 *
 	 * java.lang.OutOfMemoryError: Java heap space
 	 * Dumping heap to java_pid70499.hprof ...
 	 * Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
 	 * Heap dump file created [27959428 bytes in 0.244 secs]
 	 *
 	 */
public class HeapOOM {

	static class OOMObject {

	}

	public static void main(String[] args) {

    	List<OOMObject> list = new ArrayList<OOMObject>();

    	while(true) {
        	list.add(new OOMObject());
    	}
	}
}

Method area/方法区


方法区时所有java虚拟机线程共享的,他在虚拟机启动时通过classloader创建。这些方法中的数据在内存中直到load他们的loader被释放。

方法区存储: * 类信息(字段和方法的数量,父类,接口名字,版本等) * 方法和构造函数的字节码 * 运行时常量池

Note:需要注意的是在java7以前,oracle hotsopt jvm使用PermGen永久代来存储方法区,PermGen的大小可以通过(java:-XX:PermSize=10M -XX:MaxPermSize=10M)来设置,如果超过最大限制,则会出现 java.lang.OutOfMemoryError: PermGen space 但是从JAVA8开始,hotspot jvm使用一个单独的原生内存空间来保存方法区,它被叫做Metaspace,最大的空间就是系统所有可用内存,可以使用(java: -XX:MaxMetaspaceSize=10m)来设置大小,如果超过了最大限制则会出现java.lang.OutOfMemoryError: Metaspace

异常例:


	import net.sf.cglib.proxy.Enhancer;
	import net.sf.cglib.proxy.MethodInterceptor;
	import net.sf.cglib.proxy.MethodProxy;
	import java.lang.reflect.Method;

	/**
 	 * Java1.8 VM Args:  -XX:MaxMetaspaceSize=10m
 	 * 
 	 * Java1.7 VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
 	 *
 	 *
 	 * Created by xiagn on 16-4-7.
 	 */
	public class JavaMethodAreaOOM {

   		static class OOMObject { }

   		public static void main(String[] args) {
       while(true) {
           Enhancer enhancer = new Enhancer();
           enhancer.setSuperclass(OOMObject.class);
           enhancer.setUseCache(false);
           enhancer.setCallback(new MethodInterceptor() {
               @Override
               public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                   return methodProxy.invokeSuper(o, objects);
               }
           });
           enhancer.create();
       }
   	}

	}
 

Runtime constant pool/运行时常量池


运行时常量池是方法区的一部分,当类,方法,字段被使用时,JVM使用运行时常量池来检索内存的实际地址。 字符串字面亮和原生常量都在里面。